🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

决策笔记 iuuuuuuuu/dsh-agent-notes

DeepSeek Harness
iuuuuuuuu14 天前有提交已提供中文文档扫描:含敏感能力

把改动缘由与取舍沉淀成可校验的仓库笔记

30/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/17 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add iuuuuuuuu/dsh-agent-notes
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:待复验
是什么
dsh 原生插件 · dev
装得上吗
曾在 dsh 0.2.0-rc.2 下真实安装成功,但当前 dsh 已更新至 0.2.1-rc.5,本站尚未在新版下复验
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 14 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 90 处,展示前 70 条)
  • 读取本地文件
    lib/notes.js:68
    if (existsSync(skillsRoot)) {
  • 读取本地文件
    lib/notes.js:70
    for (const entry of readdirSync(skillsRoot, { withFileTypes: true })) {
  • 读取本地文件
    lib/notes.js:73
    if (existsSync(scripts)) candidates.push(scripts);
  • 读取本地文件
    lib/notes.js:92
    if (existsSync(legacyScripts)) candidates.push(legacyScripts);
  • 读取本地文件
    lib/notes.js:95
    try { return statSync(dir).isDirectory(); } catch { return false; }
  • 读取本地文件
    lib/notes.js:142
    if (existsSync(join(current, '.git'))) return current;
  • 执行外部命令固定命令
    lib/notes.js:226
    const result = spawnSync('git', ['remote', 'get-url', 'origin'], {
  • 执行外部命令固定命令
    lib/notes.js:315
    const result = spawnSync('git', ['worktree', 'list', '--porcelain'], {
  • 写入本地文件
    lib/notes.js:520
    if (statSync(from).isFile()) copyFileSync(from, join(tmp, name));
  • 写入本地文件
    lib/notes.js:524
    writeFileSync(join(tmp, 'package.json'), '{"type":"module"}', 'utf8');
  • 写入本地文件
    lib/notes.js:539
    if (applied.length > 0) writeFileSync(target, code, 'utf8');
  • 删除本地文件递归
    lib/notes.js:548
    cleanup: () => { try { rmSync(tmp, { recursive: true, force: true }); } catch { } }
  • 执行外部命令来自配置/变量
    lib/notes.js:984
    child = spawn(command, args, {
  • 写入本地文件
    lib/notes.js:1234
    mkdirSync(dir, { recursive: true });
  • 读取本地文件
    lib/state.js:74
    if (!existsSync(file)) return fallback;
  • 读取本地文件
    lib/state.js:75
    const parsed = JSON.parse(readFileSync(file, 'utf8'));
  • 写入本地文件
    lib/state.js:92
    mkdirSync(dirname(file), { recursive: true });
  • 写入本地文件
    lib/state.js:94
    writeFileSync(temp, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
  • 写入本地文件
    lib/state.js:95
    renameSync(temp, file);
  • 读取本地文件
    lib/skill.js:220
    const raw = readFileSync(SKILL_FILE, 'utf8');
  • 读取本地文件
    lib/skill.js:265
    if (!existsSync(SKILL_FILE)) return { candidates: [], complete: true };
  • 读取本地文件
    lib/skill.js:271
    if (!existsSync(SKILL_FILE)) return undefined;
  • 发起网络请求
    lib/self-update.js:57
    const response = await fetch(url, { signal });
  • 读取本地文件
    lib/self-update.js:175
    if (!existsSync(file)) return undefined;
  • 读取本地文件
    lib/self-update.js:176
    const value = readFileSync(file, 'utf8').trim();
  • 写入本地文件
    lib/self-update.js:192
    writeFileSync(join(skillDir, SYNCED_VERSION_FILE), `${version}\n`, 'utf8');
  • 读取本地文件
    lib/self-update.js:255
    current = existsSync(target) ? readFileSync(target, 'utf8') : undefined;
  • 读取本地文件
    lib/self-update.js:255
    current = existsSync(target) ? readFileSync(target, 'utf8') : undefined;
  • 写入本地文件
    lib/self-update.js:273
    mkdirSync(dirname(target), { recursive: true });
  • 写入本地文件
    lib/self-update.js:275
    writeFileSync(temp, entry.content, 'utf8');
  • 写入本地文件
    lib/self-update.js:277
    renameSync(temp, target);
  • 删除本地文件
    lib/self-update.js:280
    try { rmSync(temp, { force: true }); } catch { }
  • 读取本地文件
    lib/version-notice.js:54
    const raw = readFileSync(`${PACKAGE_ROOT}package.json`, 'utf8');
  • 读取本地文件
    lib/project-skill.js:38
    entries = readdirSync(dir, { withFileTypes: true });
  • 读取本地文件
    lib/project-skill.js:62
    return existsSync(file) ? readFileSync(file, 'utf8') : undefined;
  • 读取本地文件
    lib/project-skill.js:62
    return existsSync(file) ? readFileSync(file, 'utf8') : undefined;
  • 读取本地文件
    lib/project-skill.js:97
    if (!existsSync(projectDir)) return result;
  • 读取本地文件
    lib/project-skill.js:99
    if (!existsSync(join(projectDir, 'SKILL.md'))) return result;
  • 写入本地文件
    lib/project-skill.js:178
    mkdirSync(dirname(target), { recursive: true });
  • 写入本地文件
    lib/project-skill.js:179
    writeFileSync(target, content, 'utf8');
  • 写入本地文件
    lib/projection.js:284
    mkdirSync(dirname(file), { recursive: true });
  • 写入本地文件
    lib/projection.js:286
    writeFileSync(temp, `${JSON.stringify({ global: value })}\n`, 'utf8');
  • 写入本地文件
    lib/projection.js:287
    renameSync(temp, file);
  • 读取本地文件
    lib/projection.js:302
    if (!existsSync(file)) return null;
  • 读取本地文件
    lib/projection.js:303
    const parsed = JSON.parse(readFileSync(file, 'utf8'));
  • 读取本地文件
    lib/board-route.js:363
    if (!existsSync(BOARD_HTML)) {
  • 读取本地文件
    lib/board-route.js:367
    const html = readFileSync(BOARD_HTML, 'utf8');
  • 读取本地文件
    lib/board-route.js:440
    if (!existsSync(target) || !statSync(target).isFile()) continue;
  • 读取本地文件
    lib/board-route.js:440
    if (!existsSync(target) || !statSync(target).isFile()) continue;
  • 读取本地文件
    lib/board-route.js:441
    sendJson(res, 200, { path: rel, body: readFileSync(target, 'utf8'), root });
  • 读取本地文件
    lib/archive-layout.js:166
    return `sha256:${createHash('sha256').update(readFileSync(absolutePath)).digest('hex')}`;
  • 读取本地文件
    lib/archive-layout.js:208
    if (!existsSync(archivedRoot)) return { errors: [], artifacts: [] };
  • 读取本地文件
    lib/archive-layout.js:216
    entries = readdirSync(archivedRoot, { withFileTypes: true });
  • 读取本地文件
    lib/archive-layout.js:230
    children = readdirSync(join(archivedRoot, entry.name), { withFileTypes: true });
  • 读取本地文件
    lib/archive-layout.js:250
    content = readFileSync(join(archivedRoot, rel), 'utf8');
  • 读取本地文件
    lib/archive-layout.js:270
    if (existsSync(manifestPath)) {
  • 读取本地文件
    lib/notes-layout.js:158
    if (!existsSync(notesRoot)) {
  • 读取本地文件
    lib/notes-layout.js:166
    entries = readdirSync(notesRoot, { withFileTypes: true });
  • 读取本地文件
    lib/notes-layout.js:251
    entries = readdirSync(lifecycleDir, { withFileTypes: true });
  • 读取本地文件
    lib/notes-layout.js:313
    entries = readdirSync(classDir, { withFileTypes: true });
  • 读取本地文件
    lib/board.js:58
    if (!existsSync(root)) return [];
  • 读取本地文件
    lib/board.js:62
    if (!existsSync(lifecycleDir)) continue;
  • 读取本地文件
    lib/board.js:65
    classDirs = readdirSync(lifecycleDir, { withFileTypes: true }).filter((e) => e.isDirectory());
  • 读取本地文件
    lib/board.js:75
    files = readdirSync(classPath, { withFileTypes: true }).filter((e) => e.isFile());
  • 读取本地文件
    lib/board.js:85
    stat = statSync(full);
  • 读取本地文件
    lib/board.js:87
    text = readFileSync(full, 'utf8');
  • 读取本地文件
    build.mjs:40
    const pkg = JSON.parse(readFileSync(join(baseDir, 'package.json'), 'utf8'));
  • 读取本地文件
    build.mjs:41
    const source = readFileSync(join(baseDir, 'src', 'client.js'), 'utf8');
  • 写入本地文件
    build.mjs:72
    mkdirSync(join(baseDir, 'lib'), { recursive: true });
  • 写入本地文件
    build.mjs:73
    writeFileSync(join(baseDir, 'lib', 'client.js'), bundle, 'utf8');
扫描于 2026/10/31(1 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

README

由 DeepSeek 最新模型翻译生成
dsh-agent-notes 把「为什么这样改、放弃了什么」变成仓库里可机械校验的决策笔记,装进 DeepSeek Harness。 方法提炼自 czm15053/write-notes-like-deepseek(它又提炼自 DeepSeek Harness 的 .agents/notes/ 实践)。 本插件把那套方法做成 DSH 原生插件:技能、会话开关、真门禁、上游合并。 它解决什么 代码能表达「系统现在怎么跑」,表达不了「为什么必须这样跑、以及放弃了什么」。于是每个新会话都会:自作聪明地「优化」掉一段当年为避免死锁而刻意写别扭的代码;为赶局部需求打穿全局结构;把三个月前已证明会内存溢出的方案再提一遍。 本插件让规矩绕不开:和代码同仓库、同一次改动、机械校验、旧决定封存带封印。 安装 从本地目录装进你的 profile(开发期) dsh plugin add --profile <你的profile> /path/to/dsh-agent-notes 或从 GitHub dsh plugin add --profile <你的profile> github:iuuuuuuuu/dsh-agent-notes 注意:plugin add 只写依赖,不会把插件加进启用列表。
展开完整文档(README 剩余 1 节)
dsh-agent-notes

把「为什么这样改、放弃了什么」变成仓库里可机械校验的决策笔记,装进 DeepSeek Harness。

方法提炼自 czm15053/write-notes-like-deepseek(它又提炼自 DeepSeek Harness 的 .agents/notes/ 实践)。
本插件把那套方法做成 DSH 原生插件:技能、会话开关、真门禁、上游合并。

它解决什么

代码能表达「系统现在怎么跑」,表达不了「为什么必须这样跑、以及放弃了什么」。于是每个新会话都会:自作聪明地「优化」掉一段当年为避免死锁而刻意写别扭的代码;为赶局部需求打穿全局结构;把三个月前已证明会内存溢出的方案再提一遍。

本插件让规矩绕不开:和代码同仓库、同一次改动、机械校验、旧决定封存带封印。

安装

从本地目录装进你的 profile(开发期)
dsh plugin add --profile <你的profile> /path/to/dsh-agent-notes

或从 GitHub
dsh plugin add --profile <你的profile> github:iuuuuuuuu/dsh-agent-notes

注意:plugin add 只写依赖,不会把插件加进启用列表。 实测确认:它把包名写进 profiles/<profile>/package.json 的 dependencies,但 dsh.profile.bundles 里没有这一项 —— 而 DSH 是按 bundles 逐层加载插件的(每层读该包自己的 cordis.patch.yml)。所以只 add 的话插件装了却不生效,--dump-config 里也看不到它。

还需要手动把包名加进 bundles:

// $DSH_HOME/profiles/<你的profile>/package.json
"dsh": {
"profile": {
"bundles": [
"@deepseek-ai/dsh-base",
"@deepseek-ai/dsh-web-app",
"dsh-agent-notes" // ← 加这一行 ] } }

然后重启 DSH。验证是否真加载:dsh --profile <你的profile> --dump-config 里应出现 # == dsh-agent-notes 与 - id: agent-notes。若只在依赖里而不在 bundles 里,启动日志不会有报错,插件却完全没跑 —— 这是最容易踩空的一步。

用起来

/agent-notes 查看开关与笔记树状态 /agent-notes on 开启本会话的决策笔记(持久化,重载后保持) /agent-notes on --global 改全局默认(影响所有未单独覆盖的会话) /agent-notes reset 清除本会话覆盖,回到全局默认 /agent-notes init 在当前仓库建出 .agents/notes 骨架 /agent-notes gate 跑真门禁(也可 gate tree / gate format / gate archived)

默认是关闭的

装好插件后,新会话和旧会话默认都不启用,需要时手动打开(点胶囊,或 /agent-notes on)。这样装上插件不会悄悄改变任何既有会话的行为。

想让所有会话默认开启,二选一:

/agent-notes on --global # 运行时改全局默认,落盘持久化

或在插件配置里写 enabled: true(仅在该配置生效的 profile 里)。

会话级开关优先级高于全局:单独关掉某个会话后,改全局也不会影响它。

界面上的状态提示

不用敲命令也能一眼看到当前会话是否生效:输入框工具行左侧有一枚绿色胶囊「● 笔记规范」,就挨着「完全权限/工作区内修改」那一排,点击即切换;关闭时胶囊变灰。

它读的是会话投影(agentNotes),所以刷新、重连、切换会话都自动正确。
点击开关走的是官方斜杠命令通道(/agent-notes on|off),因此和手敲命令 完全等价,并且开关操作零 token。

设计说明:投影只折叠宿主自己写的 command/run 事件,本插件不写会话日志。
原因是插件零 @deepseek-ai 依赖,拿不到 appendPluginRecord;而退路
session.append('plugin:…') 实测会写出 ignorable=undefined 的残废记录
(pluginRecordOf 读不回),看着成功实则污染日志。走命令事件则完全绕开这个坑。

笔记看板:对话区多一个「笔记」tab

对话 / 轨迹 旁边会多出一个 「笔记」 tab,里面就是上游那套成品看板(架构基线 / 演进时间线 / 避坑智库 / 决策清单四个视图),不自绘 UI:

tab 里用 iframe 加载 vendored 的上游资产 skills/agent-notes/assets/agent-notes-board.html,宿主只负责把当前会话仓库的笔记实时注入它预留的 window.__INLINE_DATA__ 插槽。

这样做的理由:官方看板是 76K 的成熟 UI(交叉引用入度、因果链路、多维筛选),自己重画一套只会更丑,而且必然与上游越走越远。走 iframe 后视觉与交互完全跟随上游更新,本插件只维护数据适配层(lib/board.js 的 buildInlineData)。

它读的是实时磁盘内容,不是静态快照:宿主注册只读 HTTP 路由(/agent-notes/ui、/agent-notes/board、/agent-notes/note、/agent-notes/health),每次切到该 tab 现读。

为什么不用会话投影:投影的 view 必须是同步纯函数,只能读会话日志折叠出的
state,读不到文件系统(已核对源码 view: state => wire.view(state))。

安全边界:路由只接受固定路径,只读 notesRoot 之下,并拒绝任何 ..;会话解析不出笔记根时返回 409,绝不猜一个路径,也不退回进程目录。

注入数据必须防住两件事(都有回归测试):

1. 笔记正文里出现 、 本仓库 --(插件启动时异步)--> 用户机器

两段都不再需要人记得。任何一段失败都会让 job 变红,不会静默跳过。

为什么用 git ls-remote 而不是 GitHub API:未认证请求 60 次/小时就 403,定时任务天天跑必撞墙。git 协议没这个限制。

为什么拿到 commit 而不直接锚分支名:jsDelivr 对分支名的清单缓存会滞后,锚 commit 才能拿到该次提交的真实树。

副本更新后,模型什么时候看到新规则

lib/skill.js 的 get() 每次调用都现读磁盘,没有缓存。所以:

- 自更新写入新内容后会调 invalidate(),宿主丢弃缓存的技能目录,模型下一次加载技能就是新内容,不需要重启 DSH;
- 但技能内容不是每轮都注入的 —— 它按需加载(SkillDefinition 语义),已加载过的会话可能还持有旧正文。

判据:让模型重新触发一次技能加载即可;要绝对确定就重启宿主。

自检与测试 sh
npm test # 行为测试(真跑子进程、真写文件)
npm run gate:fault-injection # 证明门禁会红,而不是恒绿
npm run verify # 包自检:清单 / patch / 零依赖 / 双语文档与徽标 / vendored 与上游哈希
npm run sync:latest # 上游是否已漂移(会真变红)
npm run sync:check # 副本与钉住 ref 是否逐字相同
npm run evidence # 实机证据:用 DSH 自带的真实 SkillRegistry 验证注册
npm run check # 以上四条串跑

fault-injection 会为每条门禁注入它该拦的故障(非法分类、坏文件名、缺备选方案、implemented 残留提案标题、篡改/删除已封印归档……),断言必须非零退出;再清掉故障断言回到零。任何一条对故障无反应,该脚本自身非零退出——它自己也引入了一条必然失败的断言验证过会红。

npm run evidence 与集成测试需要找到 DSH 安装位置。会自动探测 $DSH_HOME、~/.dsh 及其各 profile 的 node_modules;桌面端内置安装等非标准位置请显式指定:

sh
DSH_DIST=/path/to/dsh/lib/bin.js npm run evidence

探测不到时 evidence 明确失败(退出码 2),不假装通过;集成测试则降级为同契约承接器,并在输出里标注实际用的是哪种模式。

设计约束(为什么零依赖)

本插件不 import 任何 @deepseek-ai/。原因是实测的:profile 的 node_modules 里 @deepseek-ai 只解析到 cosmokit/schemastery,插件运行时拿不到 DSH 的类型包。因此:

- 宿主 API 全部按鸭子类型使用(ctx.skills / ctx.tools / ctx.commands / ctx.systemPrompt / ctx.agents),缺失时对应能力降级关闭而不是崩;
- 只用 node 内建模块;
- 工具用裸对象注册(DSH 接受原始 ToolDefinition,MCP 工具就是这么进来的)。

tools/verify-plugin.mjs 会把这条约束当门禁检查:谁往 lib/ 里加了 @deepseek-ai/ import,自检立刻红。

组件

| 文件 | 职责 |
| lib/index.js | 组装;会话级规范注入的挂载/卸载 |
| lib/state.js | 全局默认 + 会话覆盖双持久化(原子写) |
| lib/notes.js | 笔记树探测、真门禁执行、脚手架 |
| lib/skill.js | agent-notes 技能 provider(剥 frontmatter) |
| lib/policy.js | 注入的规范文本 |
| lib/commands.js | /agent-notes 命令 |
| lib/tools.js | agent_notes_gate / agent_notes_status 工具 |
| skills/agent-notes/ | vendored 上游内容(SKILL.md、references、templates、scripts、assets) |
| tools/ | 上游合并、自检、故障注入、测试 |

已知限制

- 看板模板引 Google Fonts 外链,离线环境下会退化到系统字体;功能不受影响。
- 上游内容同步依赖 jsDelivr 可访问;不可达时 sync 会明确失败,不会静默跳过。
- DSH 处于 developer preview,peerDependencies 未声明(本插件不依赖 DSH 包),但宿主 API 若变更需跟随。
- 会话开关按会话 id持久化;会话被删除后,其覆盖项仍留在状态文件中,目前不做 GC。
- 宿主仓库的 CI 接入需自行配置;插件不代为修改仓库。

许可

MIT。vendored 上游内容版权归其作者,见 skills/agent-notes/ 内注释与上游仓库。
预览分享卡片长什么样 ↓iuuuuuuuu/dsh-agent-notes
分享 · DeepSeek Harness Hub
iuuuuuuuu/dsh-agent-notes

把改动缘由与取舍沉淀成可校验的仓库笔记

回到顶部