project-node-memory 0.0.0-stage → 1.0.1
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/LICENSE +21 -0
- package/README.md +204 -2
- package/README_EN.md +201 -0
- package/cordis.patch.yml +5 -0
- package/lib/client.js +1621 -0
- package/lib/engine/commands.js +84 -0
- package/lib/engine/diag.js +220 -0
- package/lib/engine/mdlib.js +283 -0
- package/lib/engine/parse.js +67 -0
- package/lib/engine/registry.js +241 -0
- package/lib/engine/rename.js +203 -0
- package/lib/engine/render.js +138 -0
- package/lib/engine/scaffold.js +123 -0
- package/lib/engine/search.js +138 -0
- package/lib/engine/write.js +140 -0
- package/lib/host/prompt.js +111 -0
- package/lib/host/routes.js +304 -0
- package/lib/host/runtime.js +108 -0
- package/lib/host/skill.js +160 -0
- package/lib/host/tools.js +475 -0
- package/lib/index.js +60 -0
- package/package.json +62 -4
- package/skills/project-node-memory-guide/SKILL.md +59 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dsh-nodekb contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,205 @@
|
|
|
1
|
-
#
|
|
1
|
+
# project-node-memory
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> 中文 · [English](README_EN.md)
|
|
4
|
+
|
|
5
|
+
把 **Markdown 目录当成知识库** 的 DSH 插件:为 Agent 提供 9 个 `kb_*` 工具,为人类提供知识节点面板(侧栏入口名「项目节点知识库」)。
|
|
6
|
+
|
|
7
|
+
没有索引数据库、没有向量、没有后台进程。完整知识库位于项目下的 `.nodekb/` 目录——一堆 md 文件,删掉插件之后它仍然是普通 md。
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
项目/
|
|
11
|
+
└── .nodekb/
|
|
12
|
+
├── index/entry.md ## 3.1 编译契约
|
|
13
|
+
├── doc/03-build.md <node name="fact:abi" type="fact">…</node>
|
|
14
|
+
├── lessons/001.md
|
|
15
|
+
└── diag_ignore.json 可选:豁免样板块
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 它能为 Agent 带来什么
|
|
19
|
+
|
|
20
|
+
Agent 默认靠 `read` / `grep` / `glob` 找东西,代价是**试探**:grep 到 5 个文件,逐个 read,才发现真正要的只有其中一段。本插件把这层收敛成四件事:
|
|
21
|
+
|
|
22
|
+
| 能力 | 对应工具 | 省下什么 |
|
|
23
|
+
| ----------------------------------- | ---------------------- | ------------------------------------------------- |
|
|
24
|
+
| 先看结构再读正文 | `kb_read mode=toc` | 一次 ~200 token 的目录视图替代整篇 read |
|
|
25
|
+
| 稳定 TID 寻址(`doc:03-build:3.1`) | `kb_read target=…` | 不受行号漂移影响;改文件不打断引用 |
|
|
26
|
+
| 引用就地展开(转写) | `kb_read mode=subtree` | 一次拿到装配好的上下文,不用手动拼 |
|
|
27
|
+
| 结构自检 | `kb_diag` | 悬空引用 / 引用位置型段落 / 循环 / 孤儿,改完就查 |
|
|
28
|
+
|
|
29
|
+
返回文本体积设有硬闸:`mode=subtree` 超过 `maxlen`(默认 50000 字符)直接报错,并告知改用 `toc` / `node`,而不是悄悄截断。
|
|
30
|
+
|
|
31
|
+
## 怎么让 Agent 真的用它
|
|
32
|
+
|
|
33
|
+
工具描述只在 Agent 已经决定「我要沉淀知识」时才被读到;要让它**优先**选这条路,靠四层:
|
|
34
|
+
|
|
35
|
+
1. **系统提示词段落**(默认开,`prompt.enabled=false` 可关)——每轮都在,规则只有五行:要沉淀就用 `.nodekb/` + `kb_*`,别拿 `write` 手搭 `docs/*.md`。
|
|
36
|
+
段落常驻约 450 字,实测 `GET /nodekb/api/prompt` 可自证是否注册成功。
|
|
37
|
+
|
|
38
|
+
2. **把「建库」变成一次调用**——`kb_init` 直接铺出 `doc/ facts/ lessons/ todo/ index/entry.md` 与 `.nodekb/README.md` 里的落位约定(幂等,已存在不覆盖)。没有这一步,Agent 会自己发明目录结构。
|
|
39
|
+
|
|
40
|
+
3. **写也走工具**——`kb_write` 写前校验 TID / `<node>` 独占一行 / 路径不越界,写后立刻跑 diag 把悬空引用与重复块报回来。`write` 没有任何一层会拦,所以规则只能靠记。
|
|
41
|
+
|
|
42
|
+
4. **随包技能 `project-node-memory-guide`(按需加载)**——提示词段落只有五行,写法规程(文件怎么分、TID 怎么起、先读什么再写什么、三个反例)放在 `skills/project-node-memory-guide/SKILL.md`。
|
|
43
|
+
装好即生效,**不需要任何手工步骤**:宿主 `skills` 服务可用时插件用 `ctx.skills.register()` 把正文注册在内存里(不用时零提示词开销,禁用插件即撤回)。`skill.enabled=false` 可关;`GET /nodekb/api/skill` 自证是否注册成功。
|
|
44
|
+
|
|
45
|
+
> 注:DSH 的 skill 文件发现只扫 `$DSH_HOME/skills`、`<workspace>/.dsh/skills` 与 preset 里显式声明的路径,**不会**去扫 npm 包内的 `skills/` 目录。所以「把 SKILL.md 打进包」本身不会让它被发现——要么注册进内存(本插件的做法),要么启动时把文件写进用户的 skill 根目录(会在家目录留残留)。
|
|
46
|
+
|
|
47
|
+
## 装
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
dsh plugin add project-node-memory # npm 发布后
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
从源码装(开发用):
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm pack # 产出 project-node-memory-<版本>.tgz
|
|
57
|
+
dsh plugin add ./project-node-memory-<版本>.tgz
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
> 打包后再 `add` 那个 `.tgz`,不要直接 `add` 源码目录:目录形式装的是 **link 依赖**,目录被移动或删除后 DSH 会起不来。Host 侧改动需要重启 DSH;面板改动重新安装即热载。
|
|
61
|
+
|
|
62
|
+
## 配置
|
|
63
|
+
|
|
64
|
+
全部可选,写在 `cordis.patch.yml` 的该行 `config:` 下:
|
|
65
|
+
|
|
66
|
+
| 键 | 默认 | 作用 |
|
|
67
|
+
| ---------------- | ----------------------------------------------- | ------------------------------------------------------------- |
|
|
68
|
+
| `workspace` | 会话工作区 → 进程 cwd | 覆盖「工作区」,库发现与路径白名单都以它为界 |
|
|
69
|
+
| `libraries` | 无 | `名字: 目录`,显式登记工作区之外的库(目录下需有 `.nodekb/`) |
|
|
70
|
+
| `registries` | 工作区 `.nodekb/registry.json` + 用户全局注册表 | 额外的 `kb_registry.json`(与 Python 版同格式) |
|
|
71
|
+
| `prompt.enabled` | `true` | 是否注册系统提示词段落 |
|
|
72
|
+
| `skill.enabled` | `true` | 是否把随包 Skill 注册进宿主 `skills` 服务 |
|
|
73
|
+
|
|
74
|
+
## 面板
|
|
75
|
+
|
|
76
|
+
侧边栏多一个图标(名字「项目节点知识库」),点开是中栏三列:
|
|
77
|
+
|
|
78
|
+
- **左**:上半是节点树(`标题 · 类型 · 字符数`),顶部搜索框回车换成命中列表;下半是**图设置**——布局方向(上下/左右)、展开层数、类型筛选(点一下隐藏该类型)、全部展开 / 适应视图
|
|
79
|
+
- **中**:**节点图**。父左子右(或上下)的确定性树形布局,节点框按类型描边取色,滚轮缩放、拖动平移、点节点选中、双击折叠该支;右下角有 `+ / − / 适应` 和当前缩放比例
|
|
80
|
+
- **右**:上半是节点内容(**markdown 渲染**,`[[TID]]` 可点),下半是节点属性(tid、类型、父、源文件行号、字符数、子节数、被谁引用、文件改名按钮)。「原文 / 源码」两个视图保持等宽纯文本
|
|
81
|
+
|
|
82
|
+
正文的 markdown 是自己写的小渲染器(标题 / 列表 / 代码 / 表格 / 引用 / 强调 / 链接),**LaTeX 走 MathML**:`$…$` 行内、`$$…$$` 与 ```math 围栏是独立公式块。不依赖 KaTeX、不带字体文件,排版交给浏览器原生的 MathML 引擎。支持分数、根号、上下标、希腊字母、∑∏∫、矩阵(pmatrix/bmatrix/vmatrix)、cases、`\left…\right`、`\text{}`、`\mathrm/\mathbf/\mathbb` 等常用子集;不认识的命令按字面显示,不会崩。
|
|
83
|
+
|
|
84
|
+
面板只读,数据走宿主侧注册的 `/nodekb/api/*`。写库就是写 md——用 `write` / `edit` 工具,或者 `kb_write`。
|
|
85
|
+
右栏正文单次最多渲染 10000 字符(超出显示截断提示);宽表格横向滚动,长文本纵向滚动。
|
|
86
|
+
|
|
87
|
+
## 9 个工具
|
|
88
|
+
|
|
89
|
+
| 工具 | 干什么 |
|
|
90
|
+
| --------------- | ---------------------------------------------------------------------------------------------- |
|
|
91
|
+
| `kb_list` | 有哪些库、哪个是默认。不确定传什么 `kb` 时先调它 |
|
|
92
|
+
| `kb_read` | `target` + `mode`(`subtree` 默认 / `node` / `toc` / `raw`)+ `maxlen` |
|
|
93
|
+
| `kb_search` | 中文友好检索(2-gram + 多词加权 + 少见时退化子串),返回稳定 TID、类型、源行、片段 |
|
|
94
|
+
| `kb_diag` | 结构自检;有 error 时直接失败,别读完才发现引用断了 |
|
|
95
|
+
| `kb_backlinks` | 谁引用了它。改 TID / 删节点前先跑 |
|
|
96
|
+
| `kb_init` | 建 `.nodekb` 骨架(幂等),铺出 `doc/ facts/ lessons/ todo/ index/` 与落位约定 |
|
|
97
|
+
| `kb_write` | 写库:`append` / `replace` / `create` / `node`(就地改写一个 `<node>` 块);写前校验、写后自检 |
|
|
98
|
+
| `kb_rename` | 文件改名 + 全库 `[[TID]]` 引用一起改写(含 `[[tid:1.2]]`);写前重读,别人刚改的不覆盖 |
|
|
99
|
+
| `kb_vcs_status` | 库内嵌 git 的快照历史与未提交改动(git 缺失时只说明,不报错) |
|
|
100
|
+
|
|
101
|
+
## 库怎么定位
|
|
102
|
+
|
|
103
|
+
1. `lib` 参数给路径(技能目录,或其下的 `.nodekb`)——必须落在工作区或已注册库内
|
|
104
|
+
2. `kb` 参数给名字(注册表里的名字,或工作区里发现的名字,如某个技能名)
|
|
105
|
+
3. 都不给:工作区自带的 `.nodekb/` → 工作区里发现且唯一的库 → 注册表里唯一的库
|
|
106
|
+
|
|
107
|
+
**自动发现**:给了工作区之后,库里不只在工作区根找,还会按约定位置下钻(有界 BFS,默认 2 层):
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
<ws>/.nodekb → 名字 workspace
|
|
111
|
+
<ws>/.dsh/skills/<技能>/.nodekb → 名字 <技能>
|
|
112
|
+
<ws>/<子目录>/.nodekb → 名字 相对路径
|
|
113
|
+
<ws>/<子目录>/.dsh/skills/<技能>/.nodekb
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
跳过 `node_modules` / `dist` / `build` / `out` / `target` / `coverage` / 点目录,`readdir` 次数有上限,所以面板每次加载都敢调。这就是「知识库跟着项目走」:指到项目根,技能里那些 `.nodekb/` 自己会浮出来。
|
|
117
|
+
|
|
118
|
+
注册表与 Python 版 `kb_registry.json` 同格式,默认读工作区下的 `.nodekb/registry.json` 与用户目录里的全局注册表。插件配置也可以直接内联:
|
|
119
|
+
|
|
120
|
+
```yaml
|
|
121
|
+
- insert:
|
|
122
|
+
- id: nodekb
|
|
123
|
+
name: project-node-memory
|
|
124
|
+
config:
|
|
125
|
+
libraries:
|
|
126
|
+
my-notes: D:\notes # 名字 → 目录(目录下需有 .nodekb/)
|
|
127
|
+
registries:
|
|
128
|
+
- D:\shared\kb_registry.json
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`config.workspace` 可覆盖「工作区」,默认取会话工作区、再退回进程 cwd。
|
|
132
|
+
|
|
133
|
+
面板拿不到会话上下文,所以它默认跟随**最近一次 `kb_*` 工具调用所在的工作区**(Agent 读过哪个项目,面板就显示哪个项目);顶栏的目录下拉里直接列出 DSH 用过的项目目录(标了各自发现几个库),点右上「目录」也可以手填路径。选择存在浏览器 localStorage,只影响面板,不影响工具。
|
|
134
|
+
|
|
135
|
+
## 语法
|
|
136
|
+
|
|
137
|
+
```md
|
|
138
|
+
## 3.1 编译契约 → 节点 tid = doc:03-build:3.1(位置型,不能当引用目标)
|
|
139
|
+
<node name="fact:abi" type="fact" parent="doc:03-build">
|
|
140
|
+
正文 ← 显式节点,引用只展开它自己
|
|
141
|
+
</node>
|
|
142
|
+
|
|
143
|
+
[[fact:abi]] → 引用;渲染时就地展开
|
|
144
|
+
[[doc:03-build]] → 引文件/文件夹 = 展开整支子树
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
硬规则只有两条:`<node>` / `</node>` 必须各占整行;显式节点名不能和文件派生 TID 撞。
|
|
148
|
+
|
|
149
|
+
## 自检(不装 DSH 也能跑)
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
node scripts/check.mjs # 235 项:清单契约 / 工具 / 路由 / 客户端槽位 / 改名改写 / 建库写库 / 提示词段落 / 随包技能
|
|
153
|
+
node test/parity.mjs # 与 Python 原实现逐条对拍(需要 python 与 nodekb 源码)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`scripts/check.mjs` 在 vm 里跑客户端 bundle,捕获 `__ModuleLoader__.load({id, factory})`,
|
|
157
|
+
断言 `id === package.json.name`(写错这个 id 会报 `bundle loaded without registering`)。
|
|
158
|
+
|
|
159
|
+
装进 DSH 之后,四个端点就地自证(`http://127.0.0.1:<端口>/nodekb/api/`):
|
|
160
|
+
|
|
161
|
+
| 端点 | 说明什么 |
|
|
162
|
+
| ---------------- | ------------------------------------------------------ |
|
|
163
|
+
| `/version` | 活着的处理器是哪一版(热载后旧路由可能还在,用这个认) |
|
|
164
|
+
| `/tools` | 宿主收下几个 `kb_*`,被拒的是哪个 |
|
|
165
|
+
| `/prompt` | 系统提示词段落挂上没有 |
|
|
166
|
+
| `/skill` | 随包 Skill 有没有进宿主技能表 |
|
|
167
|
+
| `/client-report` | 浏览器侧槽位注册到了哪一步(图标不出现时看这个) |
|
|
168
|
+
|
|
169
|
+
## 边界
|
|
170
|
+
|
|
171
|
+
- **写操作有边界**:`kb_init` 只建骨架(不覆盖),`kb_write` 只写库内路径(越界直接拒绝),`kb_rename` 负责改名的引用搬迁。其它随意改写仍可走 `write` / `edit`,只是没有校验。
|
|
172
|
+
- **不做自动记忆**:不抓会话、不后台扫描。库里的内容是人(或 Agent 显式)放进去的。
|
|
173
|
+
- **零依赖**:不 import 任何 `@deepseek-ai/*`,不声明 peerDependencies。
|
|
174
|
+
- 缓存按全库 md 的 `mtime/size` 签名失效,不依赖 `fs.watch`。
|
|
175
|
+
|
|
176
|
+
## 对 DSH 版本的依赖面
|
|
177
|
+
|
|
178
|
+
插件只碰宿主 8 个 API,全部集中在 `lib/host/`。哪一条变了、坏到什么程度,都在这张表里:
|
|
179
|
+
|
|
180
|
+
| 宿主 API | 用在哪 | 变了会怎样 | 已有的隔离 |
|
|
181
|
+
| ------------------------------------------------------- | ------------------------ | ----------------------------------------------------------- | --------------------------------------------------------------------- |
|
|
182
|
+
| `apply(ctx, config)` + `inject = ['tools','webServer']` | `lib/index.js` | 服务名不存在 → 整行不激活(**静默不加载**,DSH 仍正常启动) | 静态 inject,不抛错 |
|
|
183
|
+
| `ctx.tools.register(def)` | `lib/host/tools.js` | 定义字段被拒收 → 该工具消失 | **逐条注册**,一条失败不影响其余;`GET /nodekb/api/tools` 报 `failed` |
|
|
184
|
+
| `ctx.webServer.register({kind:'prefix',…})` | `lib/host/routes.js` | 注册被拒 → 面板无数据,工具照旧 | 异常被吞,`globalThis` 跳板让旧路由转发到新处理器 |
|
|
185
|
+
| `ctx.inject([...], cb)` | `prompt.js` / `skill.js` | 动态注入不可用 → 段落/技能不注册 | 退回同步取值;仍失败只告警 |
|
|
186
|
+
| `ctx.systemPrompt.section({name,order,text})` | `lib/host/prompt.js` | 段落不出现 → Agent 不优先用它(工具仍在) | 失败被吞,`/api/prompt` 自证 |
|
|
187
|
+
| `ctx.skills.register({…})` | `lib/host/skill.js` | 写法规范不进技能目录 | 失败被吞,`/api/skill` 自证 |
|
|
188
|
+
| `exec.workspace / cwd / session` | `lib/host/runtime.js` | 定位不到工作区 → 默认库位置错 | 4 级回退:config → env → exec → 上次工具用过的 → 进程 cwd |
|
|
189
|
+
| `window.__ModuleLoader__.load({id,factory})` | `lib/client.js` | id 不匹配 → `bundle loaded without registering` | 自检断言 id === 包名 |
|
|
190
|
+
| `ctx.slots.inject` / `slots.register` | `lib/client.js` | 槽名或 props 改名 → 图标不出现 | 走 official `inject`,失败写 `/api/client-report` 并在面板内自述 |
|
|
191
|
+
|
|
192
|
+
**引擎与数据格式不依赖 DSH 任何东西**(纯 `node:fs` + `node:path`),所以数据侧不会被宿主版本变化波及。
|
|
193
|
+
|
|
194
|
+
## 卸载之后
|
|
195
|
+
|
|
196
|
+
`.nodekb/` 就是普通 Markdown 目录,没有任何私存格式:
|
|
197
|
+
|
|
198
|
+
- 直接 `read` / `grep` / `edit` 照常,`[[TID]]` 退化成普通文本(不再展开、不再校验)。
|
|
199
|
+
- Python 版 nodekb(MCP)读同一个库,格式一致——`test/parity.mjs` 16 项对拍守的就是这条。
|
|
200
|
+
- 重新装回任意后续版本,库不需要迁移。
|
|
201
|
+
- 唯一会变糙的是**维护性**:手改文件后没有 `diag` 兜底,改名要靠手搬引用(`kb_rename` 不在时没人帮你改 `[[old]]`)。
|
|
202
|
+
|
|
203
|
+
## License
|
|
204
|
+
|
|
205
|
+
MIT
|
package/README_EN.md
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# project-node-memory
|
|
2
|
+
|
|
3
|
+
> English · [中文](README.md)
|
|
4
|
+
|
|
5
|
+
A DSH plugin that treats **a directory of Markdown as a database**: nine `kb_*` tools for the agent, and a center-column panel for humans (sidebar entry: 「项目节点知识库」).
|
|
6
|
+
|
|
7
|
+
> Renamed from `dsh-nodekb` at 0.2.0. Tool names (`kb_*`) and the on-disk format are unchanged.
|
|
8
|
+
|
|
9
|
+
No index database, no embeddings, no background process. The library *is* the `.nodekb/` directory in your project — a pile of Markdown files that stay ordinary Markdown if you uninstall the plugin.
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
project/
|
|
13
|
+
└── .nodekb/
|
|
14
|
+
├── index/entry.md ## 3.1 Compile contract
|
|
15
|
+
├── doc/03-build.md <node name="fact:abi" type="fact">…</node>
|
|
16
|
+
├── lessons/001.md
|
|
17
|
+
└── diag_ignore.json optional: whitelist boilerplate blocks
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Why an agent needs it
|
|
21
|
+
|
|
22
|
+
By default an agent reaches for `read` / `grep` / `glob`, and pays for **trial and error**: grep finds five files, it reads each one, and only then discovers that one paragraph was the answer. nodekb collapses that into four moves:
|
|
23
|
+
|
|
24
|
+
| Capability | Tool | What it saves |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| Look at structure before text | `kb_read mode=toc` | A ~200-token outline instead of a full read |
|
|
27
|
+
| Stable TID addressing (`doc:03-build:3.1`) | `kb_read target=…` | No line-number drift; moving text doesn't break references |
|
|
28
|
+
| In-place reference expansion (transclusion) | `kb_read mode=subtree` | Assembled context in one call instead of manual stitching |
|
|
29
|
+
| Structure self-check | `kb_diag` | Dangling refs, refs to positional sections, cycles, orphans — check right after editing |
|
|
30
|
+
|
|
31
|
+
There is a hard size gate too: a `mode=subtree` render above `maxlen` (default 50,000 characters) fails loudly and tells you to use `toc` / `node` instead of silently truncating.
|
|
32
|
+
|
|
33
|
+
## Making the agent actually use it
|
|
34
|
+
|
|
35
|
+
A tool description is only read *after* the agent has already decided "I should record knowledge". Getting it to prefer this path takes four layers:
|
|
36
|
+
|
|
37
|
+
1. **A system-prompt section** (on by default, `prompt.enabled=false` to disable). It is present every turn, and the rule is five lines: to record something use `.nodekb/` + `kb_*`, don't hand-roll `docs/*.md` with `write`. The section is ~450 characters; `GET /nodekb/api/prompt` proves whether it registered.
|
|
38
|
+
2. **Make "create a library" one call.** `kb_init` lays out `doc/ facts/ lessons/ todo/ index/entry.md` plus the placement conventions in `.nodekb/README.md` (idempotent, never overwrites). Without it, the agent invents its own directory structure.
|
|
39
|
+
3. **Writes go through a tool too.** `kb_write` validates TID syntax, that `<node>` / `</node>` sit on their own lines, and that the path stays inside the library — then runs `diag` immediately and reports dangling refs and duplicate blocks. Plain `write` has no such layer, so the rules survive only by memory.
|
|
40
|
+
4. **A bundled skill, `project-node-memory-guide`** (loaded on demand). The prompt section is five lines; the writing conventions (how to split files, how to name TIDs, what to read before writing, three anti-patterns) live in `skills/project-node-memory-guide/SKILL.md`. It works out of the box with **no manual step**: when the host exposes a `skills` service, the plugin registers the body in memory via `ctx.skills.register()` (no prompt cost until used, withdrawn when the plugin is disabled). `skill.enabled=false` disables it; `GET /nodekb/api/skill` proves whether it registered.
|
|
41
|
+
|
|
42
|
+
> Note: DSH discovers skills only from `$DSH_HOME/skills`, `<workspace>/.dsh/skills`, and paths declared explicitly in a preset. It **does not** scan a `skills/` directory inside an npm package, so shipping `SKILL.md` in the tarball is not enough by itself. Either register it in memory (this plugin) or copy it into the user's skill root at startup (which leaves files behind in the home directory).
|
|
43
|
+
|
|
44
|
+
## Install
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
dsh plugin add project-node-memory # once published to npm
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
From source (development):
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm pack # produces project-node-memory-<version>.tgz
|
|
54
|
+
dsh plugin add ./project-node-memory-<version>.tgz
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
> Install the packed `.tgz`, not the source directory: a directory install is a **link dependency**, and moving or deleting that directory stops DSH from starting. Host-side changes need a DSH restart; panel changes hot-reload on reinstall.
|
|
58
|
+
|
|
59
|
+
## Configuration
|
|
60
|
+
|
|
61
|
+
All optional, under `config:` on the plugin's row in `cordis.patch.yml`:
|
|
62
|
+
|
|
63
|
+
| Key | Default | Effect |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| `workspace` | session workspace → process cwd | Overrides "the workspace"; library discovery and the path whitelist are scoped to it |
|
|
66
|
+
| `libraries` | none | `name: directory` pairs registering libraries outside the workspace (each needs a `.nodekb/`) |
|
|
67
|
+
| `registries` | workspace `.nodekb/registry.json` + user-global registry | Extra `kb_registry.json` files (same format as the Python build) |
|
|
68
|
+
| `prompt.enabled` | `true` | Register the system-prompt section |
|
|
69
|
+
| `skill.enabled` | `true` | Register the bundled skill with the host `skills` service |
|
|
70
|
+
|
|
71
|
+
## Panel
|
|
72
|
+
|
|
73
|
+
The sidebar gains an icon; opening it gives a three-column center panel (modeled on nodekb's MCP WebUI):
|
|
74
|
+
|
|
75
|
+
- **Left**: node tree (`title · type · chars`), a search box that swaps in a hit list on Enter; below it **graph settings** — layout direction (vertical/horizontal), expansion depth, type filters (click to hide a type), expand-all / fit-view.
|
|
76
|
+
- **Middle**: the **node graph**. A deterministic tree layout (parents left, children right — or top-down), node boxes stroked by type, wheel to zoom, drag to pan, click to select, double-click to collapse a branch; `+ / − / fit` and the current zoom ratio sit bottom-right.
|
|
77
|
+
- **Right**: node content on top (**Markdown rendered**, LaTeX included, `[[TID]]` clickable) and node properties below (tid, type, parent, source file line, chars, child sections, backlinks, rename button). The "raw" and "source" views stay monospace plain text.
|
|
78
|
+
|
|
79
|
+
The Markdown renderer is a small home-grown one (headings, lists, code, tables, quotes, emphasis, links). **LaTeX goes through MathML**: `$…$` inline, `$$…$$` and ```math fences as display blocks. No KaTeX, no font files — layout is handed to the browser's native MathML engine. Supported subsets include fractions, roots, super/subscripts, Greek letters, ∑∏∫, matrices (pmatrix/bmatrix/vmatrix), cases, `\left…\right`, `\text{}`, `\mathrm/\mathbf/\mathbb`; unknown commands render literally instead of crashing.
|
|
80
|
+
|
|
81
|
+
The panel is read-only; data flows through host routes under `/nodekb/api/*`. Writing a library means writing Markdown — via `write` / `edit`, or `kb_write`.
|
|
82
|
+
The right column renders at most 10,000 characters per view (with a truncation notice); wide tables scroll horizontally and long text scrolls vertically.
|
|
83
|
+
|
|
84
|
+
## The nine tools
|
|
85
|
+
|
|
86
|
+
| Tool | What it does |
|
|
87
|
+
|---|---|
|
|
88
|
+
| `kb_list` | Which libraries exist and which is default. Call this first when unsure what `kb` to pass |
|
|
89
|
+
| `kb_read` | `target` + `mode` (`subtree` default / `node` / `toc` / `raw`) + `maxlen` |
|
|
90
|
+
| `kb_search` | CJK-friendly retrieval (2-gram index + multi-term weighting + substring fallback), returns stable TID, type, source line, snippet |
|
|
91
|
+
| `kb_diag` | Structure self-check; fails outright on errors so you don't finish a read before noticing a broken ref |
|
|
92
|
+
| `kb_backlinks` | Who references this node. Run before renaming a TID or deleting a node |
|
|
93
|
+
| `kb_init` | Scaffold `.nodekb` (idempotent): `doc/ facts/ lessons/ todo/ index/` plus placement conventions |
|
|
94
|
+
| `kb_write` | Write into the library: `append` / `replace` / `create` / `node` (rewrite one `<node>` block in place); validates before writing, self-checks after |
|
|
95
|
+
| `kb_rename` | Rename a file and rewrite every `[[TID]]` pointing at it (including `[[tid:1.2]]`); re-reads before writing, so concurrent edits are never clobbered |
|
|
96
|
+
| `kb_vcs_status` | Snapshot history and uncommitted changes of the library's embedded git repo (explains itself when git is missing) |
|
|
97
|
+
|
|
98
|
+
## How a library is located
|
|
99
|
+
|
|
100
|
+
1. `lib` — a path (a skill directory or its `.nodekb`), which must fall inside the workspace or a registered library.
|
|
101
|
+
2. `kb` — a name (from a registry, or discovered in the workspace, e.g. a skill name).
|
|
102
|
+
3. Neither — the workspace's own `.nodekb/` → the single library discovered in the workspace → the single registered library.
|
|
103
|
+
|
|
104
|
+
**Discovery**: given a workspace, it looks beyond the root at conventional locations (bounded BFS, depth 2 by default):
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
<ws>/.nodekb → name "workspace"
|
|
108
|
+
<ws>/.dsh/skills/<skill>/.nodekb → name "<skill>"
|
|
109
|
+
<ws>/<subdir>/.nodekb → name = relative path
|
|
110
|
+
<ws>/<subdir>/.dsh/skills/<skill>/.nodekb
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
It skips `node_modules` / `dist` / `build` / `out` / `target` / `coverage` / dot-directories and caps `readdir` calls, so the panel can afford to run it on every load. That is what "the knowledge base travels with the project" means: point at a project root and the `.nodekb/` directories inside skills surface on their own.
|
|
114
|
+
|
|
115
|
+
Registries use the same format as the Python build's `kb_registry.json`; by default the plugin reads `.nodekb/registry.json` in the workspace and the global registry in the user directory. Libraries can also be declared inline:
|
|
116
|
+
|
|
117
|
+
```yaml
|
|
118
|
+
- insert:
|
|
119
|
+
- id: nodekb
|
|
120
|
+
name: project-node-memory
|
|
121
|
+
config:
|
|
122
|
+
libraries:
|
|
123
|
+
my-notes: D:\notes # name → directory (must contain .nodekb/)
|
|
124
|
+
registries:
|
|
125
|
+
- D:\shared\kb_registry.json
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`config.workspace` overrides "the workspace", which otherwise comes from the session and falls back to the process cwd.
|
|
129
|
+
|
|
130
|
+
The panel has no session context, so it follows **the workspace of the most recent `kb_*` tool call** (whichever project the agent read, the panel shows). The directory dropdown in the header lists projects DSH has used, annotated with how many libraries each contains; the 「目录」 button lets you type a path. The choice is stored in browser localStorage and affects the panel only, never the tools.
|
|
131
|
+
|
|
132
|
+
## Syntax (the whole of it)
|
|
133
|
+
|
|
134
|
+
```md
|
|
135
|
+
## 3.1 Compile contract → node tid = doc:03-build:3.1 (positional, cannot be a reference target)
|
|
136
|
+
<node name="fact:abi" type="fact" parent="doc:03-build">
|
|
137
|
+
body ← explicit node; a reference expands only this
|
|
138
|
+
</node>
|
|
139
|
+
|
|
140
|
+
[[fact:abi]] → reference; expanded in place at render time
|
|
141
|
+
[[doc:03-build]] → referencing a file/folder = expand its whole subtree
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Only two hard rules: `<node>` / `</node>` must each occupy an entire line, and an explicit node name must not collide with a file-derived TID.
|
|
145
|
+
|
|
146
|
+
## Self-check (no DSH required)
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
node scripts/check.mjs # 235 checks: manifest contract / tools / routes / client slots / rename rewriting / scaffold + write / prompt section / bundled skill
|
|
150
|
+
node test/parity.mjs # case-by-case parity against the Python implementation (needs python and nodekb sources)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
`scripts/check.mjs` runs the client bundle in a `vm`, captures `__ModuleLoader__.load({id, factory})`, and asserts `id === package.json.name` (getting that id wrong yields `bundle loaded without registering`).
|
|
154
|
+
|
|
155
|
+
Once installed, five endpoints testify in place (`http://127.0.0.1:<port>/nodekb/api/`):
|
|
156
|
+
|
|
157
|
+
| Endpoint | What it proves |
|
|
158
|
+
|---|---|
|
|
159
|
+
| `/version` | Which version the live handler is (after a hot reload the old route may still be serving) |
|
|
160
|
+
| `/tools` | How many `kb_*` tools the host accepted, and which were rejected |
|
|
161
|
+
| `/prompt` | Whether the system-prompt section is attached |
|
|
162
|
+
| `/skill` | Whether the bundled skill reached the host skill table |
|
|
163
|
+
| `/client-report` | How far client-side slot registration got (look here when the icon is missing) |
|
|
164
|
+
|
|
165
|
+
## Boundaries
|
|
166
|
+
|
|
167
|
+
- **Writes are bounded**: `kb_init` only scaffolds (never overwrites), `kb_write` only writes inside the library (out-of-bounds paths are refused), `kb_rename` handles reference migration on rename. Anything else can still go through `write` / `edit` — just without validation.
|
|
168
|
+
- **No automatic memory**: it does not capture sessions or scan in the background. Whatever is in the library was put there by a human or by an explicit agent action.
|
|
169
|
+
- **Zero dependencies**: imports nothing from `@deepseek-ai/*`, declares no peer dependencies.
|
|
170
|
+
- The cache invalidates on a whole-library `mtime/size` signature; it does not use `fs.watch`.
|
|
171
|
+
|
|
172
|
+
## Host API surface (version-compatibility)
|
|
173
|
+
|
|
174
|
+
The plugin touches eight host APIs, all under `lib/host/`. What breaks, and how badly, is this table:
|
|
175
|
+
|
|
176
|
+
| Host API | Used in | If it changes | Isolation already in place |
|
|
177
|
+
|---|---|---|---|
|
|
178
|
+
| `apply(ctx, config)` + `inject = ['tools','webServer']` | `lib/index.js` | Service renamed → the row never activates (**silently absent**; DSH still boots) | Static inject never throws |
|
|
179
|
+
| `ctx.tools.register(def)` | `lib/host/tools.js` | Definition rejected → that one tool disappears | **Registered one by one**; `GET /nodekb/api/tools` reports `failed` |
|
|
180
|
+
| `ctx.webServer.register({kind:'prefix',…})` | `lib/host/routes.js` | Registration refused → panel has no data, tools unaffected | Errors swallowed; a `globalThis` trampoline forwards old routes to the newest handler |
|
|
181
|
+
| `ctx.inject([...], cb)` | `prompt.js` / `skill.js` | Dynamic injection unavailable → section/skill not registered | Falls back to a direct service lookup; failure only warns |
|
|
182
|
+
| `ctx.systemPrompt.section({name,order,text})` | `lib/host/prompt.js` | Section missing → agent won't prefer it (tools still work) | Failure swallowed; `/api/prompt` testifies |
|
|
183
|
+
| `ctx.skills.register({…})` | `lib/host/skill.js` | Conventions skill never appears | Failure swallowed; `/api/skill` testifies |
|
|
184
|
+
| `exec.workspace / cwd / session` | `lib/host/runtime.js` | Workspace unresolvable → wrong default library | Four-stage fallback: config → env → exec → last used → process cwd |
|
|
185
|
+
| `window.__ModuleLoader__.load({id,factory})` | `lib/client.js` | id mismatch → `bundle loaded without registering` | Self-check asserts id === package name |
|
|
186
|
+
| `ctx.slots.inject` / `slots.register` | `lib/client.js` | Slot name or props renamed → icon missing | Uses the official `inject`; failures go to `/api/client-report` and are described in the panel |
|
|
187
|
+
|
|
188
|
+
**The engine and data format depend on nothing from DSH** (pure `node:fs` + `node:path`), so the data side is unaffected by host version churn.
|
|
189
|
+
|
|
190
|
+
## After uninstalling
|
|
191
|
+
|
|
192
|
+
`.nodekb/` is an ordinary Markdown directory with no private format:
|
|
193
|
+
|
|
194
|
+
- `read` / `grep` / `edit` work as before; `[[TID]]` degrades to plain text (no expansion, no validation).
|
|
195
|
+
- The Python build (MCP) reads the same library — the formats are identical, which is what the 16 parity cases in `test/parity.mjs` guard.
|
|
196
|
+
- Reinstalling any later version needs no migration.
|
|
197
|
+
- Only **maintainability** degrades: no `diag` net after hand edits, and renames no longer rewrite `[[old]]` for you while `kb_rename` is gone.
|
|
198
|
+
|
|
199
|
+
## License
|
|
200
|
+
|
|
201
|
+
MIT
|