dsh-notes-board 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE.md +7 -0
- package/README.md +115 -13
- package/index.js +21 -178
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +31 -8
- package/scripts/test-board.mjs +15 -1
- package/src/core/notes.js +486 -0
- package/src/core/tools.js +221 -0
- package/src/host/board.js +184 -0
- package/src/host/notes.js +250 -0
- package/src/host/skills.js +48 -0
- package/vendor/write-notes-like-deepseek/README.md +323 -0
- package/vendor/write-notes-like-deepseek/package.json +21 -0
- package/vendor/write-notes-like-deepseek/scripts/export-assets.sh +40 -0
- package/vendor/write-notes-like-deepseek/scripts/import-dsh-notes.ts +125 -0
- package/vendor/write-notes-like-deepseek/scripts/test-skill-install.mjs +63 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/SKILL.md +195 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/assets/agent-notes-board.html +1779 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/references/archiving.md +109 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/references/classification.md +33 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/references/note-format.md +69 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/references/prose-checklist.md +51 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/references/quality-gate.md +58 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/references/simplification-checklist.md +26 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/references/verification.md +34 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/references/when-to-write.md +83 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/scripts/agent-note-tree.ts +75 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/scripts/archive-agent-note.ts +167 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/scripts/build-board.ts +235 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/scripts/check-note-anchors.ts +103 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/scripts/verify-agent-note-format.ts +158 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/scripts/verify-agent-note-tree.ts +56 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/scripts/verify-archived-agent-notes.ts +127 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/templates/implemented.md +32 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/templates/proposed.md +38 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/templates/rejected.md +27 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/templates/verify-notes.yml +38 -0
package/NOTICE.md
CHANGED
|
@@ -9,6 +9,13 @@
|
|
|
9
9
|
- **授权**:上游仓库当前尚未提供 LICENSE 文件;其生成的看板页面页脚自述「Agent Notes · 工程决策看板 · 遵循 MIT 协议开源」。本仓库据此按 MIT 处理并保留出处标注。若上游后续发布正式授权条款,以彼为准,本文件会同步更新。
|
|
10
10
|
- **改动**:本仓库**不修改**该文件本身。所有适配(`:root` → `:host`、作用域改写、事件接管、路由收拢、数据源注入、视图懒渲染)都发生在构建期,由 `scripts/build-client.mjs` 施加到生成物 `client.js` 上,且每处补丁都带数量断言。
|
|
11
11
|
|
|
12
|
+
## vendor/write-notes-like-deepseek
|
|
13
|
+
|
|
14
|
+
- **来源**:同一上游仓库 [czm15053/write-notes-like-deepseek](https://github.com/czm15053/write-notes-like-deepseek) 的快照。
|
|
15
|
+
- **内容**:`skills/write-notes-like-deepseek/`(SKILL.md、references、templates、`assets/agent-notes-board.html` 与 7 个校验/构建脚本)、`scripts/`、README、package.json。`note_verify` 直接 shell out 跑其中的 `verify-agent-note-tree.ts` / `verify-agent-note-format.ts` / `verify-archived-agent-notes.ts`,规则只有这一个源头。
|
|
16
|
+
- **授权**:同上(上游未提供 LICENSE 文件,按其页面自述按 MIT 处理并保留出处标注)。
|
|
17
|
+
- **改动**:裁掉上游的 `assets/`(22 张约 5 MB 配图)与 `.github/`,不影响校验;其余原样。
|
|
18
|
+
|
|
12
19
|
## 字体
|
|
13
20
|
|
|
14
21
|
原版看板引用 Google Fonts(Space Grotesk / Plus Jakarta Sans / JetBrains Mono),由原版 `<link>` 在运行时加载,本仓库不打包字体文件。字体授权见 Google Fonts 各自条目(均为 SIL Open Font License)。
|
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
把各个工作区 `.agents/notes` 里的工程决策笔记,变成 DeepSeek Harness 左侧栏里的一个原生面板:切换工作区、四个板块浏览、抽屉看正文。
|
|
4
4
|
|
|
5
|
+
同一个插件还带**笔记工具与钩子**:`note_search` / `note_read` / `note_write` / `note_verify` / `note_skip` 五个原生工具,加一个 `turn/end` 钩子——改过代码的轮次结束时,必须落一篇笔记或显式 skip。再带**随包的技能**:`write-notes-like-deepseek` 由插件自己挂给 agent,不用往 `~/.dsh/skills` 手工装。
|
|
6
|
+
|
|
5
7
|
界面直接跑 [write-notes-like-deepseek](https://github.com/czm15053/write-notes-like-deepseek) 的原版看板(`vendor/board.html` 是它的逐字快照),只是把数据来源从「浏览器选目录」换成了宿主注入——所以原版的引用入度、时间线切片、避坑智库、深链、快捷键一个都不少。
|
|
6
8
|
|
|
7
9
|
<!-- 截图占位:欢迎 PR 补一张面板截图 -->
|
|
@@ -20,43 +22,143 @@
|
|
|
20
22
|
|
|
21
23
|
数据全部来自各工作区 `.agents/notes/{implemented,proposed,rejected,archived}/{class}/yyyy-mm-dd-topic.md`。
|
|
22
24
|
|
|
23
|
-
##
|
|
25
|
+
## 另外两件:工具、钩子与技能
|
|
26
|
+
|
|
27
|
+
为什么要有钩子:`write-notes-like-deepseek` 是 skill,属于「模型可能选、也可能不选」——触发不稳定,这不是写得好不好的问题,是形态问题。所以拆两层:
|
|
28
|
+
|
|
29
|
+
| 层 | 负责 | 形态 | 确定性 |
|
|
30
|
+
|---|---|---|---|
|
|
31
|
+
| **什么时候必须做决定** | 每轮结束检查:改了代码却没有记录决定 → 当场要求二选一 | DSH `turn/end` 钩子 | 模型选不了,钩子一定跑 |
|
|
32
|
+
| **做什么** | 搜索 / 读 / 写 / 校验 / 记账 | 5 个工具 | 靠 description 引导 |
|
|
24
33
|
|
|
25
|
-
|
|
34
|
+
钩子只保证「必须做决定」,不保证「必须写」——所以出口有两个:`note_write` 和 `note_skip`。只有一个出口的话,改错别字也要立一篇笔记,立刻变噪声。
|
|
26
35
|
|
|
36
|
+
| 工具 | 读/写 | 什么时候用 |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `note_search` | 读 | 提方案前、想推翻旧决定前、改跨文件契约前。空结果返回 `NO_MATCHING_NOTE`——**是"没记录",不是"没这个决定"**,要换词再搜 |
|
|
39
|
+
| `note_read` | 读 | 拿到路径后看全文;理由通常在 `Alternatives considered` 和 `Consequences` |
|
|
40
|
+
| `note_write` | 写 | 非机械改动落定后写一篇;写前按上游规则校验,不合法**不落盘**并回具体错误 |
|
|
41
|
+
| `note_verify` | 读 | 手改过笔记后、或声称一致之前,跑上游三个校验器 |
|
|
42
|
+
| `note_skip` | 写 | 已判定不需要笔记时,一句话理由记进 `.agents/notes/.skips.log` |
|
|
43
|
+
|
|
44
|
+
`note_write` 的 `body` **不带**标题行和 `Status:` 行——那两行由 `title` / `lifecycle` 生成。
|
|
45
|
+
|
|
46
|
+
文件名固定是 ASCII slug。标题是中文时(本仓库里很常见)必须另传 `slug`,否则直接拒写并提示:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
NEEDS_SLUG: title has no Latin characters to build a filename from; pass slug with 2-5 ASCII words
|
|
27
50
|
```
|
|
28
|
-
|
|
51
|
+
|
|
52
|
+
### 钩子怎么判
|
|
53
|
+
|
|
54
|
+
- `turn/start` 重置本轮状态
|
|
55
|
+
- `tool/call`:
|
|
56
|
+
- 工具名在 `note_write` / `note_skip` 里,或参数/结果里出现 `.agents/notes` → 本轮**已交代**,不再提醒
|
|
57
|
+
- 工具名在 `codeTools` 里(默认 `bash`、`write`、`edit`、`apply_patch` …)且不是只读命令 → 记为**动了代码**
|
|
58
|
+
- `turn/end`:动了代码 **且** 没交代 **且** 本轮没提醒过 **且** 没到 `maxPerSession` → 注入提醒
|
|
59
|
+
|
|
60
|
+
只读命令靠 `readonlyCommandPattern` 排除(`ls`、`cat`、`grep`、`git status/diff/log` …),否则只读排查也会被催。
|
|
61
|
+
|
|
62
|
+
`wake` 默认 **false**:`agent.inject()` 不唤醒代理,提醒会在**下一个** admitted step 生效,不额外烧 token。想让它当场行动就设 `wake: true`(会调 `agent.followup()`)。
|
|
63
|
+
|
|
64
|
+
`note_verify` 直接 shell out 跑上游三个脚本(tree / format / archived),**不重新实现规则**——规则只有一个源头,避免两套实现漂移。Node 24 能直跑 `.ts`,不需要 tsx。
|
|
65
|
+
|
|
66
|
+
### 技能从哪来
|
|
67
|
+
|
|
68
|
+
`write-notes-like-deepseek` 不需要再装到 `~/.dsh/skills`:`src/host/skills.js` 用宿主自带的 `@deepseek-ai/dsh-skill-filesystem` 起一个独立 provider(`providerName: 'agent-notes'`),`bundledSkillDir` 指向 `vendor/write-notes-like-deepseek/skills`。
|
|
69
|
+
|
|
70
|
+
- 技能随包走:装插件即装技能,卸载即消失;升级时和 `note_verify` 用的三个校验器一起换,因为它们本来就是同一份目录,不会分叉。
|
|
71
|
+
- `includeDefaultRoots: false`,这个 provider 只提供随包那一份,不重复索引 `~/.dsh/skills`。
|
|
72
|
+
- 宿主没有这个包、或 `ctx.plugin` 不可用时静默跳过(只写一行 info 日志),面板与工具照常工作。
|
|
73
|
+
|
|
74
|
+
## 安装
|
|
75
|
+
|
|
76
|
+
需要 DeepSeek Harness(DSH)。用 `dsh plugin` 装,`--profile` 换成本机 profile 名(常见的是 `web`):
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
dsh plugin --profile web add dsh-notes-board
|
|
29
80
|
```
|
|
30
81
|
|
|
31
|
-
|
|
82
|
+
它把参数原样转给 profile 目录里的 pnpm,装完自动把这个包登记进 `dsh.profile.bundles`。之后重启该 profile(`dsh web`),左侧栏出现「笔记看板」图标。
|
|
83
|
+
|
|
84
|
+
> 桌面 App 用的 `desktop` profile 由 Electron 独占管理,`dsh plugin` 会直接拒绝:
|
|
85
|
+
> `profile "desktop" is managed exclusively by the Electron application`。
|
|
86
|
+
|
|
87
|
+
> 面板是只读的:Host 侧只注册两条 GET 路由,不写任何文件,也不接受任意路径——只读 workspace registry 里登记过的工作区。写文件只发生在你(或代理)主动调用 `note_write` / `note_skip` 时。
|
|
88
|
+
|
|
89
|
+
本包**零运行时依赖**:`@deepseek-ai/dsh-tools`、`@deepseek-ai/dsh-llm`、`@deepseek-ai/schemastery`、`@deepseek-ai/dsh-skill-filesystem` 全部是可选 `peerDependencies`,由宿主提供。
|
|
32
90
|
|
|
33
|
-
|
|
91
|
+
## 配置
|
|
92
|
+
|
|
93
|
+
`cordis.patch.yml` 里可改的键(profile 级 patch 覆盖,升级不丢):
|
|
94
|
+
|
|
95
|
+
| 键 | 默认 | 含义 |
|
|
96
|
+
|---|---|---|
|
|
97
|
+
| `root` | `''` | 笔记根所在工作区;空 = 代理工作目录,再退到 `process.cwd()` |
|
|
98
|
+
| `reminder.enabled` | `true` | 是否提醒 |
|
|
99
|
+
| `reminder.wake` | `false` | 是否立刻唤醒代理 |
|
|
100
|
+
| `reminder.maxPerSession` | `6` | 单会话提醒上限(是提醒,不是循环) |
|
|
101
|
+
| `codeTools` | 见源码 | 视为"改工作区"的工具名 |
|
|
102
|
+
| `readonlyCommandPattern` | 见源码 | 命中即视为只读 bash |
|
|
34
103
|
|
|
35
104
|
## 工作原理
|
|
36
105
|
|
|
106
|
+
一个包、一条 loader row、一个 `apply`,入口只做组合:
|
|
107
|
+
|
|
37
108
|
```
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
109
|
+
index.js ← 组合层:ctx.inject(['webServer']) → applyBoard ;applyNotes ;mountNotesSkills
|
|
110
|
+
│
|
|
111
|
+
├── src/host/board.js 看板半边(只读)
|
|
112
|
+
│ GET /api/dsh-notes-board/workspaces
|
|
113
|
+
│ GET /api/dsh-notes-board/scan
|
|
114
|
+
│ └── 只回 .agents/notes 的 Markdown 原文(zh 优先去重、>64KB gzip)
|
|
115
|
+
│
|
|
116
|
+
├── src/host/notes.js 笔记半边
|
|
117
|
+
│ ctx.tools.register × 5 + ctx.on('session/event') 的 turn/end 钩子
|
|
118
|
+
│ │
|
|
119
|
+
│ ├── src/core/tools.js 5 个工具的唯一定义
|
|
120
|
+
│ └── src/core/notes.js 结构解析、校验、搜索、skip 账本、跑上游校验器
|
|
121
|
+
│
|
|
122
|
+
└── src/host/skills.js 技能半边
|
|
123
|
+
ctx.plugin(dsh-skill-filesystem, {providerName:'agent-notes', bundledSkillDir: vendor/…/skills})
|
|
44
124
|
```
|
|
45
125
|
|
|
126
|
+
- **三个半边各自 gate 自己的服务**。入口不导出 `inject`,所以缺 `webServer` 的 profile 里看板静默不激活、缺 `tools` 的 profile 里钩子静默不激活、缺 `dsh-skill-filesystem` 的宿主里技能静默跳过,谁都不会把整条插件行拖成加载失败。
|
|
46
127
|
- **Host 不解析笔记**。它只列出工作区、读原文、按相对路径返回。分类、引用入度、四个视图全部交给原版 `parseNote` / `ingestDataset`,解析逻辑只有一份。
|
|
47
128
|
- **Client 不重写 UI**。`scripts/build-client.mjs` 把 `vendor/board.html` 做机械改造后拼进 `client.js`:`:root` → `:host`(挂 shadow root 做样式隔离)、`document.getElementById` → 作用域内 `byId()`、`location.hash` → 面板内自持路由、摘掉「浏览器目录授权」启动块改由 `ingestFiles()` 喂数据、四个视图改成按 tab 懒渲染。每个补丁都带数量断言,上游模板一变,构建直接失败而不是静默错改。
|
|
129
|
+
- **只有一条出口**。`src/core/tools.js` 是 5 个工具的唯一定义,`src/host/notes.js` 是唯一适配层;`note_verify` 与技能 provider 指的都是 `vendor/write-notes-like-deepseek` 那一份,规则不存在第二处副本。
|
|
48
130
|
|
|
49
131
|
### 开发
|
|
50
132
|
|
|
51
133
|
```sh
|
|
52
134
|
npm run build # src/panel.js + vendor/board.html -> client.js(生成物,别手改)
|
|
53
|
-
npm test #
|
|
135
|
+
npm test # 两件套:桩 DOM 跑四视图 + 抽屉五节;入口/钩子/技能 25 条用例
|
|
136
|
+
npm run test:board / test:hooks # 单独跑
|
|
54
137
|
```
|
|
55
138
|
|
|
139
|
+
没有运行时依赖,**不需要 `npm install`**;宿主包全部靠测试里的 `node --import ./test/stubs/register.mjs` 短路成 stub。
|
|
140
|
+
|
|
56
141
|
改界面:改 `src/panel.js`(宿主壳)或 `scripts/build-client.mjs`(补丁表)。
|
|
57
|
-
上游更新:替换 `vendor/board.html`,重跑 `npm run build
|
|
142
|
+
上游更新:替换 `vendor/board.html`,重跑 `npm run build`,按断言失败处修补丁。仓库里另有一份同源的技能资产副本 `vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/assets/agent-notes-board.html`,`npm test` 会断言两份 sha256 一致,防止只换一份导致分叉。
|
|
143
|
+
|
|
144
|
+
### 目录
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
index.js 插件入口(组合层)
|
|
148
|
+
client.js 客户端生成物(别手改)
|
|
149
|
+
src/panel.js 客户端源:React 薄壳 + shadow root 装原版看板
|
|
150
|
+
src/host/board.js 看板半边:两条只读路由
|
|
151
|
+
src/host/notes.js 笔记半边:5 个原生工具 + turn/end 钩子
|
|
152
|
+
src/host/skills.js 技能半边:把随包技能挂成 agent 技能
|
|
153
|
+
src/core/notes.js 结构解析、校验、搜索、skip 账本、跑上游校验器
|
|
154
|
+
src/core/tools.js 5 个工具的唯一定义
|
|
155
|
+
test/ 入口 / 钩子 / 技能挂载测试、宿主 stub
|
|
156
|
+
vendor/ 看板模板快照 + 上游 skill 源码(含三个校验脚本)
|
|
157
|
+
```
|
|
58
158
|
|
|
59
159
|
## 授权与来源
|
|
60
160
|
|
|
61
161
|
- 本仓库代码:MIT,见 [LICENSE](LICENSE)。
|
|
62
162
|
- `vendor/board.html`(看板模板本体)来自 [czm15053/write-notes-like-deepseek](https://github.com/czm15053/write-notes-like-deepseek),其生成的页面内自述「遵循 MIT 协议开源」。原样收录、未改动语义,只在构建期做上述机械改造。详见 [NOTICE.md](NOTICE.md)。
|
|
163
|
+
- `vendor/write-notes-like-deepseek`(技能源码与校验脚本)clone 自同一上游,保留 `skills/`(SKILL.md、references、templates、7 个脚本)与 README、package.json;原仓库的 `assets/`(22 张 5 MB 配图)、`.github/` 已裁掉,不影响校验。
|
|
164
|
+
- 工具 / 钩子 / 技能半边原为独立包 `dsh-agent-notes`(同一作者),2026-11-12 并入本仓库,决策记录见 `.agents/notes/implemented/architecture/2026-11-12-merge-notes-hook-into-notes-board.md`;同日移除 MCP 出口并把技能改为随包挂载,见 `.agents/notes/implemented/simplification/2026-11-12-drop-mcp-surface-mount-bundled-skill.md`。
|
package/index.js
CHANGED
|
@@ -1,185 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* dsh-notes-board ·
|
|
2
|
+
* dsh-notes-board · 插件入口
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* 一个包、一条插件行,拼起三件事:
|
|
5
|
+
* - 看板半边 src/host/board.js:面板 + /api/dsh-notes-board/* 只读路由,依赖 webServer;
|
|
6
|
+
* - 笔记半边 src/host/notes.js:5 个原生笔记工具 + turn/end 钩子,依赖 tools;
|
|
7
|
+
* - 技能半边 src/host/skills.js:把随包的 write-notes-like-deepseek 挂成 agent 技能,
|
|
8
|
+
* 依赖宿主自带的 @deepseek-ai/dsh-skill-filesystem(缺了就静默跳过)。
|
|
7
9
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
+
* 两半依赖的服务不同,所以入口不整体 inject 任一服务:没有 webServer 的 profile 里
|
|
11
|
+
* 看板静默缺席,没有 tools 的 profile 里钩子静默缺席,都不会让整条插件行抛错。
|
|
12
|
+
*
|
|
13
|
+
* 注意 applyNotes 拿到的是**插件自己的 ctx**(不是 ctx.inject 的子 scope):它的
|
|
14
|
+
* session/event 监听必须挂在插件这一层,事件才冒泡得到它。tools 的就绪判断由
|
|
15
|
+
* applyNotes 内部负责。
|
|
10
16
|
*/
|
|
17
|
+
import { applyBoard } from './src/host/board.js'
|
|
18
|
+
import { applyNotes, NotesConfig } from './src/host/notes.js'
|
|
19
|
+
import { mountNotesSkills } from './src/host/skills.js'
|
|
11
20
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
import { basename, join, sep } from 'node:path'
|
|
15
|
-
import { gzipSync } from 'node:zlib'
|
|
16
|
-
|
|
17
|
-
export const inject = ['webServer']
|
|
18
|
-
|
|
19
|
-
const BASE = '/api/dsh-notes-board'
|
|
20
|
-
/* 安全阀,不是业务上限:只防挂错目录(比如挂到家目录)时把进程拖死。
|
|
21
|
-
真触发时响应里带 truncated:true,面板会明确提示,绝不静默截断。 */
|
|
22
|
-
const SAFETY_MAX_FILES = 50000
|
|
23
|
-
const MAX_BODY = 400 * 1024
|
|
24
|
-
|
|
25
|
-
function sendJson(req, res, code, payload) {
|
|
26
|
-
const raw = Buffer.from(JSON.stringify(payload), 'utf8')
|
|
27
|
-
const accepts = String((req && req.headers && req.headers['accept-encoding']) || '')
|
|
28
|
-
if (raw.length > 64 * 1024 && /\bgzip\b/.test(accepts)) {
|
|
29
|
-
const body = gzipSync(raw)
|
|
30
|
-
res.writeHead(code, {
|
|
31
|
-
'content-type': 'application/json; charset=utf-8',
|
|
32
|
-
'content-encoding': 'gzip',
|
|
33
|
-
'content-length': body.length,
|
|
34
|
-
'cache-control': 'no-store',
|
|
35
|
-
})
|
|
36
|
-
res.end(body)
|
|
37
|
-
return
|
|
38
|
-
}
|
|
39
|
-
res.writeHead(code, {
|
|
40
|
-
'content-type': 'application/json; charset=utf-8',
|
|
41
|
-
'content-length': raw.length,
|
|
42
|
-
'cache-control': 'no-store',
|
|
43
|
-
})
|
|
44
|
-
res.end(raw)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function fail(req, res, code, message) {
|
|
48
|
-
sendJson(req, res, code, { ok: false, message })
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function listWorkspaces(ctx) {
|
|
52
|
-
const registry = ctx.get('workspaceRegistry')
|
|
53
|
-
if (!registry || typeof registry.list !== 'function') return []
|
|
54
|
-
const seen = new Set()
|
|
55
|
-
const out = []
|
|
56
|
-
for (const workspace of registry.list()) {
|
|
57
|
-
const path = typeof workspace?.path === 'string' ? workspace.path : ''
|
|
58
|
-
if (!path || seen.has(path)) continue
|
|
59
|
-
seen.add(path)
|
|
60
|
-
out.push({
|
|
61
|
-
id: String(workspace.id ?? path),
|
|
62
|
-
title: String(workspace.title || basename(path) || path),
|
|
63
|
-
path,
|
|
64
|
-
hasNotes: existsSync(join(path, '.agents', 'notes')),
|
|
65
|
-
})
|
|
66
|
-
}
|
|
67
|
-
return out
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function resolveWorkspace(ctx, idOrPath) {
|
|
71
|
-
const wanted = String(idOrPath || '')
|
|
72
|
-
if (!wanted) return undefined
|
|
73
|
-
const all = listWorkspaces(ctx)
|
|
74
|
-
return (
|
|
75
|
-
all.find((w) => w.id === wanted) ||
|
|
76
|
-
all.find((w) => w.path === wanted) ||
|
|
77
|
-
all.find((w) => w.title === wanted)
|
|
78
|
-
)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/** 广度优先收集 .agents/notes 下的 Markdown,跳过点开头目录 */
|
|
82
|
-
async function walkNotes(root) {
|
|
83
|
-
const found = []
|
|
84
|
-
const queue = [{ dir: root, depth: 0 }]
|
|
85
|
-
let truncated = false
|
|
86
|
-
while (queue.length) {
|
|
87
|
-
const current = queue.shift()
|
|
88
|
-
let entries
|
|
89
|
-
try {
|
|
90
|
-
entries = await readdir(current.dir, { withFileTypes: true })
|
|
91
|
-
} catch {
|
|
92
|
-
continue
|
|
93
|
-
}
|
|
94
|
-
for (const entry of entries) {
|
|
95
|
-
if (entry.name.startsWith('.')) continue
|
|
96
|
-
const abs = join(current.dir, entry.name)
|
|
97
|
-
if (entry.isDirectory()) {
|
|
98
|
-
if (current.depth < 5) queue.push({ dir: abs, depth: current.depth + 1 })
|
|
99
|
-
continue
|
|
100
|
-
}
|
|
101
|
-
if (!entry.isFile() || !/\.md$/i.test(entry.name)) continue
|
|
102
|
-
if (found.length >= SAFETY_MAX_FILES) {
|
|
103
|
-
truncated = true
|
|
104
|
-
break
|
|
105
|
-
}
|
|
106
|
-
found.push(abs)
|
|
107
|
-
}
|
|
108
|
-
if (truncated) break
|
|
109
|
-
}
|
|
110
|
-
return { paths: found, truncated }
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
async function readNotes(workspace) {
|
|
114
|
-
const root = join(workspace.path, '.agents', 'notes')
|
|
115
|
-
if (!existsSync(root)) return { root, exists: false, files: [], truncated: false }
|
|
116
|
-
const { paths: absPaths, truncated } = await walkNotes(root)
|
|
117
|
-
// 中英对照的 x.md / x.zh.md 只回一份,按原版看板的规则「zh 优先」
|
|
118
|
-
const byCleanPath = new Map()
|
|
119
|
-
for (const abs of absPaths) {
|
|
120
|
-
const rel = abs.slice(root.length + 1).split(sep).join('/')
|
|
121
|
-
const isZh = /\.zh\.md$/i.test(rel)
|
|
122
|
-
const clean = isZh ? rel.replace(/\.zh\.md$/i, '.md') : rel
|
|
123
|
-
const current = byCleanPath.get(clean)
|
|
124
|
-
if (!current || isZh) byCleanPath.set(clean, { abs, rel, isZh })
|
|
125
|
-
}
|
|
126
|
-
const files = []
|
|
127
|
-
for (const [cleanRel, entry] of byCleanPath) {
|
|
128
|
-
try {
|
|
129
|
-
const info = await stat(entry.abs)
|
|
130
|
-
const text = await readFile(entry.abs, 'utf8')
|
|
131
|
-
files.push({
|
|
132
|
-
path: cleanRel,
|
|
133
|
-
raw: text.length > MAX_BODY ? text.slice(0, MAX_BODY) : text,
|
|
134
|
-
mtime: Math.round(info.mtimeMs / 1000),
|
|
135
|
-
})
|
|
136
|
-
} catch {
|
|
137
|
-
/* 单个文件读失败不影响整体 */
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
files.sort((a, b) => a.path.localeCompare(b.path))
|
|
141
|
-
return { root, exists: true, files, truncated }
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export function apply(ctx) {
|
|
145
|
-
ctx.effect(() =>
|
|
146
|
-
ctx.webServer.register({
|
|
147
|
-
kind: 'exact',
|
|
148
|
-
path: `${BASE}/workspaces`,
|
|
149
|
-
handler(req, res) {
|
|
150
|
-
try {
|
|
151
|
-
sendJson(req, res, 200, { ok: true, workspaces: listWorkspaces(ctx) })
|
|
152
|
-
} catch (error) {
|
|
153
|
-
fail(req, res, 500, error instanceof Error ? error.message : String(error))
|
|
154
|
-
}
|
|
155
|
-
},
|
|
156
|
-
}),
|
|
157
|
-
)
|
|
21
|
+
export const name = 'dsh-notes-board'
|
|
22
|
+
export const Config = NotesConfig
|
|
158
23
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
async handler(req, res) {
|
|
164
|
-
const url = new URL(req.url || '/', 'http://localhost')
|
|
165
|
-
const workspace = resolveWorkspace(ctx, url.searchParams.get('workspace') || '')
|
|
166
|
-
if (!workspace) return fail(req, res, 404, '未登记的工作区')
|
|
167
|
-
try {
|
|
168
|
-
const { root, exists, files, truncated } = await readNotes(workspace)
|
|
169
|
-
sendJson(req, res, 200, {
|
|
170
|
-
ok: true,
|
|
171
|
-
workspace,
|
|
172
|
-
root,
|
|
173
|
-
exists,
|
|
174
|
-
files,
|
|
175
|
-
truncated: !!truncated,
|
|
176
|
-
safetyLimit: SAFETY_MAX_FILES,
|
|
177
|
-
scannedAt: Date.now(),
|
|
178
|
-
})
|
|
179
|
-
} catch (error) {
|
|
180
|
-
fail(req, res, 500, error instanceof Error ? error.message : String(error))
|
|
181
|
-
}
|
|
182
|
-
},
|
|
183
|
-
}),
|
|
184
|
-
)
|
|
24
|
+
export function apply(ctx, config = {}) {
|
|
25
|
+
ctx.inject(['webServer'], (sub) => applyBoard(sub))
|
|
26
|
+
applyNotes(ctx, config)
|
|
27
|
+
mountNotesSkills(ctx)
|
|
185
28
|
}
|
package/locale/en.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"meta": {
|
|
3
3
|
"title": "Notes Board",
|
|
4
|
-
"description": "
|
|
4
|
+
"description": "Three things in one plugin: a Notes Board sidebar panel that renders each workspace's .agents/notes decision records (architecture baseline, evolution timeline, pitfalls, decision list, plus the five-section detail drawer); five native tools (note_search / note_read / note_write / note_verify / note_skip) with an end-of-turn hook that forces every turn touching code to either write a note or explicitly skip; and the bundled write-notes-like-deepseek skill, mounted for the agent."
|
|
5
5
|
}
|
|
6
6
|
}
|
package/locale/zh.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"meta": {
|
|
3
3
|
"title": "笔记看板",
|
|
4
|
-
"description": "
|
|
4
|
+
"description": "一个插件三件事:左侧栏「笔记看板」面板渲染各工作区 .agents/notes 的工程决策笔记(架构基线、演进时间线、避坑智库、决策清单四板块 + 抽屉五节);note_search / note_read / note_write / note_verify / note_skip 五个原生工具与 turn/end 钩子,保证改了代码的轮次结束时必须落一篇笔记或显式 skip;并把随包的 write-notes-like-deepseek 技能挂给 agent。"
|
|
5
5
|
}
|
|
6
6
|
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-notes-board",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "笔记看板:面板渲染各工作区 .agents/notes 的工程决策笔记(四板块 + 抽屉五节),同一插件还提供 note_search/note_read/note_write/note_verify/note_skip 五个原生工具、turn/end 钩子,以及随包的 write-notes-like-deepseek 技能。",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "scccy",
|
|
8
8
|
"keywords": [
|
|
@@ -22,11 +22,12 @@
|
|
|
22
22
|
"url": "https://github.com/scccy/dsh-notes-board/issues"
|
|
23
23
|
},
|
|
24
24
|
"engines": {
|
|
25
|
-
"node": ">=
|
|
25
|
+
"node": ">=22.18"
|
|
26
26
|
},
|
|
27
27
|
"exports": {
|
|
28
28
|
".": "./index.js",
|
|
29
29
|
"./client": "./client.js",
|
|
30
|
+
"./core": "./src/core/notes.js",
|
|
30
31
|
"./locale/*.json": "./locale/*.json",
|
|
31
32
|
"./icon": "./icon.svg"
|
|
32
33
|
},
|
|
@@ -43,6 +44,32 @@
|
|
|
43
44
|
"LICENSE",
|
|
44
45
|
"NOTICE.md"
|
|
45
46
|
],
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "node scripts/build-client.mjs",
|
|
49
|
+
"test": "node scripts/test-board.mjs && node --import ./test/stubs/register.mjs test/dsh-plugin.mjs",
|
|
50
|
+
"test:board": "node scripts/test-board.mjs",
|
|
51
|
+
"test:hooks": "node --import ./test/stubs/register.mjs test/dsh-plugin.mjs"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"@deepseek-ai/dsh-llm": "*",
|
|
55
|
+
"@deepseek-ai/dsh-skill-filesystem": "*",
|
|
56
|
+
"@deepseek-ai/dsh-tools": "*",
|
|
57
|
+
"@deepseek-ai/schemastery": "*"
|
|
58
|
+
},
|
|
59
|
+
"peerDependenciesMeta": {
|
|
60
|
+
"@deepseek-ai/dsh-llm": {
|
|
61
|
+
"optional": true
|
|
62
|
+
},
|
|
63
|
+
"@deepseek-ai/dsh-skill-filesystem": {
|
|
64
|
+
"optional": true
|
|
65
|
+
},
|
|
66
|
+
"@deepseek-ai/dsh-tools": {
|
|
67
|
+
"optional": true
|
|
68
|
+
},
|
|
69
|
+
"@deepseek-ai/schemastery": {
|
|
70
|
+
"optional": true
|
|
71
|
+
}
|
|
72
|
+
},
|
|
46
73
|
"dsh": {
|
|
47
74
|
"bundle": {
|
|
48
75
|
"patch": "./cordis.patch.yml"
|
|
@@ -50,9 +77,5 @@
|
|
|
50
77
|
"client": {
|
|
51
78
|
"platform": "web"
|
|
52
79
|
}
|
|
53
|
-
},
|
|
54
|
-
"scripts": {
|
|
55
|
-
"build": "node scripts/build-client.mjs",
|
|
56
|
-
"test": "node scripts/test-board.mjs"
|
|
57
80
|
}
|
|
58
|
-
}
|
|
81
|
+
}
|
package/scripts/test-board.mjs
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs'
|
|
2
|
+
import { createHash } from 'node:crypto'
|
|
2
3
|
import vm from 'node:vm'
|
|
3
4
|
|
|
4
5
|
import { dirname, resolve } from 'node:path'
|
|
5
6
|
import { fileURLToPath } from 'node:url'
|
|
6
|
-
const
|
|
7
|
+
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
8
|
+
const SRC = resolve(ROOT, 'client.js')
|
|
9
|
+
|
|
10
|
+
// 合并后仓库里有两份同源看板模板:构建源 vendor/board.html,和技能资产副本
|
|
11
|
+
// vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/assets/agent-notes-board.html
|
|
12
|
+
// (后者被上游 test-skill-install.mjs 的存在性断言依赖)。它们必须逐字一致,
|
|
13
|
+
// 否则上游更新只换了一份,面板和技能产物就悄悄分叉了。
|
|
14
|
+
const BUILD_SOURCE = resolve(ROOT, 'vendor', 'board.html')
|
|
15
|
+
const SKILL_ASSET = resolve(ROOT, 'vendor', 'write-notes-like-deepseek', 'skills', 'write-notes-like-deepseek', 'assets', 'agent-notes-board.html')
|
|
16
|
+
const sha = (p) => createHash('sha256').update(readFileSync(p)).digest('hex')
|
|
17
|
+
if (sha(BUILD_SOURCE) !== sha(SKILL_ASSET)) {
|
|
18
|
+
throw new Error(`vendor 里两份 agent-notes-board.html 不一致:${BUILD_SOURCE} vs ${SKILL_ASSET}`)
|
|
19
|
+
}
|
|
20
|
+
console.log(' ok 两份看板模板逐字一致(sha256 ' + sha(BUILD_SOURCE).slice(0, 12) + '…)')
|
|
7
21
|
|
|
8
22
|
const cache = new Map()
|
|
9
23
|
function makeEl(id) {
|