project-node-memory 1.1.4 → 1.2.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/README.md +5 -3
- package/README_EN.md +5 -3
- package/lib/engine/registry.js +10 -5
- package/lib/host/tools.js +17 -40
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -108,8 +108,10 @@ config:
|
|
|
108
108
|
|
|
109
109
|
## 库怎么定位
|
|
110
110
|
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
所有 `kb_*` 工具只有一个定位参数 `kb`,它同时接受**名字**与**路径**:
|
|
112
|
+
|
|
113
|
+
1. `kb` 给路径(技能目录,或其下的 `.nodekb`,绝对或相对工作区)——必须落在工作区或已注册库内
|
|
114
|
+
2. `kb` 给名字(注册表里的名字,或工作区里发现的名字,如某个技能名)
|
|
113
115
|
3. 都不给:工作区自带的 `.nodekb/` → 工作区里发现且唯一的库 → 注册表里唯一的库
|
|
114
116
|
|
|
115
117
|
**自动发现**:给了工作区之后,库里不只在工作区根找,还会按约定位置下钻(有界 BFS,默认 2 层):
|
|
@@ -203,7 +205,7 @@ kb_write mode=node target=<TID> content=… # 逐条改,或直接改 md 再 k
|
|
|
203
205
|
## 自检(不装 DSH 也能跑)
|
|
204
206
|
|
|
205
207
|
```bash
|
|
206
|
-
node scripts/check.mjs #
|
|
208
|
+
node scripts/check.mjs # 280 项:清单契约 / 工具 / 路由 / 客户端槽位 / 改名改写 / 建库写库 / tscl / 提示词段落 / 随包技能
|
|
207
209
|
node test/parity.mjs # 与 Python 原实现逐条对拍(需要 python 与 nodekb 源码)
|
|
208
210
|
```
|
|
209
211
|
|
package/README_EN.md
CHANGED
|
@@ -105,8 +105,10 @@ The right column renders at most 10,000 characters per view (with a truncation n
|
|
|
105
105
|
|
|
106
106
|
## How a library is located
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
Every `kb_*` tool takes exactly one location parameter, `kb`, which accepts **either a name or a path**:
|
|
109
|
+
|
|
110
|
+
1. `kb` = a path (a skill directory or its `.nodekb`, absolute or workspace-relative) — must fall inside the workspace or a registered library.
|
|
111
|
+
2. `kb` = a name (from a registry, or discovered in the workspace, e.g. a skill name).
|
|
110
112
|
3. Neither — the workspace's own `.nodekb/` → the single library discovered in the workspace → the single registered library.
|
|
111
113
|
|
|
112
114
|
**Discovery**: given a workspace, it looks beyond the root at conventional locations (bounded BFS, depth 2 by default):
|
|
@@ -186,7 +188,7 @@ kb_write mode=node target=<TID> content=… # fix one at a time, or edit the md
|
|
|
186
188
|
## Self-check (no DSH required)
|
|
187
189
|
|
|
188
190
|
```bash
|
|
189
|
-
node scripts/check.mjs #
|
|
191
|
+
node scripts/check.mjs # 280 checks: manifest contract / tools / routes / client slots / rename rewriting / scaffold + write / tscl / prompt section / bundled skill
|
|
190
192
|
node test/parity.mjs # case-by-case parity against the Python implementation (needs python and nodekb sources)
|
|
191
193
|
```
|
|
192
194
|
|
package/lib/engine/registry.js
CHANGED
|
@@ -194,6 +194,11 @@ export function discoverLibraries(root, { depth = 2, budget = 300, limit = 60, e
|
|
|
194
194
|
*/
|
|
195
195
|
export function resolveLibrary({ kb, lib, workspace, config } = {}) {
|
|
196
196
|
const ws = path.resolve(workspace || process.cwd())
|
|
197
|
+
// kb 同时接受「库名」与「路径」:带分隔符 / 绝对路径 / 指向 .nodekb 的一律当路径。
|
|
198
|
+
const raw = kb === undefined || kb === null ? '' : String(kb).trim()
|
|
199
|
+
const looksLikePath = !!raw && (path.isAbsolute(raw) || /[\\/]/.test(raw) || /\.nodekb$/i.test(raw))
|
|
200
|
+
const explicit = lib || (looksLikePath ? raw : undefined)
|
|
201
|
+
const kbName = looksLikePath ? undefined : raw
|
|
197
202
|
const exclude = config && config.exclude
|
|
198
203
|
const discover = () => discoverLibraries(ws, { exclude })
|
|
199
204
|
const registries = loadRegistries(
|
|
@@ -202,10 +207,10 @@ export function resolveLibrary({ kb, lib, workspace, config } = {}) {
|
|
|
202
207
|
)
|
|
203
208
|
|
|
204
209
|
// 1) 显式路径
|
|
205
|
-
if (
|
|
206
|
-
const p = path.resolve(ws,
|
|
210
|
+
if (explicit) {
|
|
211
|
+
const p = path.resolve(ws, explicit)
|
|
207
212
|
const nodekb = path.basename(p) === '.nodekb' ? p : path.join(p, '.nodekb')
|
|
208
|
-
if (!isDir(nodekb)) throw new Error(`库目录不存在: ${nodekb}(你传入: ${
|
|
213
|
+
if (!isDir(nodekb)) throw new Error(`库目录不存在: ${nodekb}(你传入: ${explicit})`)
|
|
209
214
|
const allowed = [ws, ...[...registries.values()].map((e) => e.path)]
|
|
210
215
|
if (!allowed.some((root) => isInside(nodekb, root))) {
|
|
211
216
|
throw new Error(
|
|
@@ -217,7 +222,7 @@ export function resolveLibrary({ kb, lib, workspace, config } = {}) {
|
|
|
217
222
|
}
|
|
218
223
|
|
|
219
224
|
// 2) 名字
|
|
220
|
-
const name =
|
|
225
|
+
const name = kbName
|
|
221
226
|
if (name) {
|
|
222
227
|
const entry = registries.get(name)
|
|
223
228
|
if (entry) {
|
|
@@ -234,7 +239,7 @@ export function resolveLibrary({ kb, lib, workspace, config } = {}) {
|
|
|
234
239
|
if (wsLib) return wsLib
|
|
235
240
|
}
|
|
236
241
|
const names = [...registries.keys(), ...discover().map((r) => r.name)]
|
|
237
|
-
throw new Error(`未注册的库: ${name}(可用: ${names.join(', ') || '无'}
|
|
242
|
+
throw new Error(`未注册的库: ${name}(可用: ${names.join(', ') || '无'};kb 也可以直接传库目录路径)`)
|
|
238
243
|
}
|
|
239
244
|
|
|
240
245
|
// 3) 无参:工作区自带 → 工作区里唯一发现 → 唯一的注册表库
|
package/lib/host/tools.js
CHANGED
|
@@ -30,11 +30,7 @@ export function toolState() {
|
|
|
30
30
|
|
|
31
31
|
const KB_PARAM = {
|
|
32
32
|
type: 'string',
|
|
33
|
-
description: '
|
|
34
|
-
}
|
|
35
|
-
const LIB_PARAM = {
|
|
36
|
-
type: 'string',
|
|
37
|
-
description: '库根路径的备用写法:技能目录或其下的 .nodekb 绝对路径(也可相对工作区)。仅当库没有注册名时使用。',
|
|
33
|
+
description: '库:名字(注册表/工作区里的名字)或目录路径(技能目录或其 .nodekb)。省略则用工作区默认库。',
|
|
38
34
|
}
|
|
39
35
|
|
|
40
36
|
function text(t) {
|
|
@@ -60,7 +56,7 @@ function allowedRoots(config, exec) {
|
|
|
60
56
|
/** 库定位失败时补一句「有哪些库可用」,别让模型瞎猜 */
|
|
61
57
|
function locate(config, exec, args) {
|
|
62
58
|
try {
|
|
63
|
-
return openLibrary(config, exec, { kb: args.kb
|
|
59
|
+
return openLibrary(config, exec, { kb: args.kb })
|
|
64
60
|
} catch (e) {
|
|
65
61
|
const hint = libraryHint(config, exec)
|
|
66
62
|
const err = new Error(`${e.message}${hint ? `\n${hint}` : ''}`)
|
|
@@ -100,17 +96,13 @@ export function buildTools(ctx, config) {
|
|
|
100
96
|
tools.push({
|
|
101
97
|
name: 'kb_read',
|
|
102
98
|
description:
|
|
103
|
-
'
|
|
104
|
-
'
|
|
105
|
-
'toc = 只看结构不看正文(每个节点一行:tid · 标题 · 字符数,适合先定位再读);' +
|
|
106
|
-
'node = 只读本节点自身(不含子节点,也不展开子节);raw = 元信息 + 原始内容(含未展开的 [[TID]])。' +
|
|
107
|
-
'比 read 工具好在:一次拿到装配好的上下文,且 TID 不受文件行号漂移影响。超过 maxlen 会报错并提示改用 toc/node。',
|
|
99
|
+
'读知识库的一个节点。target = TID 或其唯一前缀(空/"." = 库根)。mode: subtree(默认,整支投影)| toc(只看结构,' +
|
|
100
|
+
'先定位再读)| node(只本节点)| raw(元信息+原文)。比 read 好在 TID 不随行号漂移;超 maxlen 会报错并提示换 toc/node。',
|
|
108
101
|
parameters: {
|
|
109
102
|
type: 'object',
|
|
110
103
|
properties: {
|
|
111
104
|
target: { type: 'string', description: '节点 TID 或其唯一前缀。空字符串或 "." 表示库根。' },
|
|
112
105
|
kb: KB_PARAM,
|
|
113
|
-
lib: LIB_PARAM,
|
|
114
106
|
mode: {
|
|
115
107
|
type: 'string',
|
|
116
108
|
enum: ['subtree', 'node', 'toc', 'raw'],
|
|
@@ -173,15 +165,13 @@ export function buildTools(ctx, config) {
|
|
|
173
165
|
tools.push({
|
|
174
166
|
name: 'kb_search',
|
|
175
167
|
description:
|
|
176
|
-
'
|
|
177
|
-
'
|
|
178
|
-
'用 kb_search 找到 tid,再用 kb_read 读,比 grep 后逐个 read 省往返。',
|
|
168
|
+
'检索知识库(中文 2-gram + 多词加权 + 子串兜底)。返回稳定 TID、类型、标题、源行、片段。' +
|
|
169
|
+
'先 kb_search 拿 tid 再 kb_read,比 grep 后逐个 read 省往返。',
|
|
179
170
|
parameters: {
|
|
180
171
|
type: 'object',
|
|
181
172
|
properties: {
|
|
182
173
|
query: { type: 'string', description: '检索词或自然语言问句。' },
|
|
183
174
|
kb: KB_PARAM,
|
|
184
|
-
lib: LIB_PARAM,
|
|
185
175
|
type: { type: 'string', description: '只保留该类型的节点(如 doc / todo / handover)。' },
|
|
186
176
|
limit: { type: 'integer', description: '返回条数上限,默认 10。' },
|
|
187
177
|
offset: { type: 'integer', description: '跳过前 N 条,用于翻页。' },
|
|
@@ -214,14 +204,12 @@ export function buildTools(ctx, config) {
|
|
|
214
204
|
tools.push({
|
|
215
205
|
name: 'kb_diag',
|
|
216
206
|
description:
|
|
217
|
-
'
|
|
218
|
-
'warnings =
|
|
219
|
-
'改完 md 之后跑一次,比人工核对可靠。库根可放 diag_ignore.json 豁免样板块。',
|
|
207
|
+
'结构自检。errors = 真损坏(悬空/位置型引用、循环、孤儿、树环、重复 TID、tscl 定义问题);' +
|
|
208
|
+
'warnings = 疑似漂移(同一段文字出现在多处),默认不算失败。写完 md 就跑一次。',
|
|
220
209
|
parameters: {
|
|
221
210
|
type: 'object',
|
|
222
211
|
properties: {
|
|
223
212
|
kb: KB_PARAM,
|
|
224
|
-
lib: LIB_PARAM,
|
|
225
213
|
strict: { type: 'boolean', description: '严格模式:把 warnings 也算失败。' },
|
|
226
214
|
},
|
|
227
215
|
},
|
|
@@ -246,14 +234,13 @@ export function buildTools(ctx, config) {
|
|
|
246
234
|
tools.push({
|
|
247
235
|
name: 'kb_backlinks',
|
|
248
236
|
description:
|
|
249
|
-
'
|
|
250
|
-
'比全库 grep
|
|
237
|
+
'反查:谁引用了这个 id(`[[node=…]]`/`[[tscl=…]]`/旧式 `[[TID]]` 都算)。改 TID 或删节点前先跑,' +
|
|
238
|
+
'比全库 grep 准——只认真实引用语法。',
|
|
251
239
|
parameters: {
|
|
252
240
|
type: 'object',
|
|
253
241
|
properties: {
|
|
254
242
|
target: { type: 'string', description: '节点 TID 或其唯一前缀。' },
|
|
255
243
|
kb: KB_PARAM,
|
|
256
|
-
lib: LIB_PARAM,
|
|
257
244
|
},
|
|
258
245
|
required: ['target'],
|
|
259
246
|
},
|
|
@@ -282,9 +269,8 @@ export function buildTools(ctx, config) {
|
|
|
282
269
|
tools.push({
|
|
283
270
|
name: 'kb_rename',
|
|
284
271
|
description:
|
|
285
|
-
'
|
|
286
|
-
'
|
|
287
|
-
'要么改名后自己跑 kb_diag 逐个修。写前会重读校验,别人刚改过的文件不覆盖、只报出来。',
|
|
272
|
+
'重命名 md 文件并改写全库指向它的引用(含子节点)。TID 由文件名派生,所以用 mv 改名必然留悬空引用;' +
|
|
273
|
+
'写前重读校验,别人刚改过的文件跳过不覆盖。',
|
|
288
274
|
parameters: {
|
|
289
275
|
type: 'object',
|
|
290
276
|
properties: {
|
|
@@ -292,7 +278,6 @@ export function buildTools(ctx, config) {
|
|
|
292
278
|
new_name: { type: 'string', description: '新文件名(.md 可省略,不能带目录)。' },
|
|
293
279
|
dry_run: { type: 'boolean', description: 'true = 只报「会改哪些引用」,不动文件。' },
|
|
294
280
|
kb: KB_PARAM,
|
|
295
|
-
lib: LIB_PARAM,
|
|
296
281
|
},
|
|
297
282
|
required: ['target', 'new_name'],
|
|
298
283
|
},
|
|
@@ -313,13 +298,11 @@ export function buildTools(ctx, config) {
|
|
|
313
298
|
tools.push({
|
|
314
299
|
name: 'kb_vcs_status',
|
|
315
300
|
description:
|
|
316
|
-
'
|
|
317
|
-
'所以这里能看到「库的改动历史」;文件系统上有改动但没进快照时也会列出来。git 不可用时返回说明而不报错。',
|
|
301
|
+
'库内嵌 git 的快照历史与未提交改动(每次解析后自动快照);git 不可用时只说明不报错。',
|
|
318
302
|
parameters: {
|
|
319
303
|
type: 'object',
|
|
320
304
|
properties: {
|
|
321
305
|
kb: KB_PARAM,
|
|
322
|
-
lib: LIB_PARAM,
|
|
323
306
|
},
|
|
324
307
|
},
|
|
325
308
|
output: {
|
|
@@ -365,11 +348,8 @@ export function buildTools(ctx, config) {
|
|
|
365
348
|
tools.push({
|
|
366
349
|
name: 'kb_init',
|
|
367
350
|
description:
|
|
368
|
-
'
|
|
369
|
-
'
|
|
370
|
-
'它一次铺出 doc/ facts/ lessons/ todo/ index/entry.md 与 .nodekb/README.md 里的落位约定,' +
|
|
371
|
-
'比你自己拿 write 从零设计目录结构更省事,也让后续 Agent 知道该往哪写。' +
|
|
372
|
-
'已经有 .nodekb/ 就不要调(先用 kb_list 看)。',
|
|
351
|
+
'建 .nodekb 骨架(幂等,已有的不覆盖):doc/ lessons/ todo/ index/entry.md + 落位约定。' +
|
|
352
|
+
'准备手写 docs/*.md 之前先调这个;已经有 .nodekb/ 就别调(先 kb_list 看)。',
|
|
373
353
|
parameters: {
|
|
374
354
|
type: 'object',
|
|
375
355
|
properties: {
|
|
@@ -403,10 +383,8 @@ export function buildTools(ctx, config) {
|
|
|
403
383
|
tools.push({
|
|
404
384
|
name: 'kb_write',
|
|
405
385
|
description:
|
|
406
|
-
'
|
|
407
|
-
'
|
|
408
|
-
'直接用 write/edit 没有这一层。' +
|
|
409
|
-
'mode=append 追加到文件(不存在则创建)、replace 整篇覆盖、create 只新建、node 就地改写一个 <node> 块。',
|
|
386
|
+
'写库并立刻自检(悬空引用/重复定义/结构错误当场报回来)。写前校验 TID 与 <node> 独占一行;' +
|
|
387
|
+
'用 write/edit 直接改没有这层。mode=append(追加,不存在则建)| replace(整篇)| create(只新建)| node(就地改一个 <node> 块)。',
|
|
410
388
|
parameters: {
|
|
411
389
|
type: 'object',
|
|
412
390
|
properties: {
|
|
@@ -421,7 +399,6 @@ export function buildTools(ctx, config) {
|
|
|
421
399
|
description: '默认:路径→append,TID→node。create 遇到已存在会报错,避免覆盖。',
|
|
422
400
|
},
|
|
423
401
|
kb: KB_PARAM,
|
|
424
|
-
lib: LIB_PARAM,
|
|
425
402
|
},
|
|
426
403
|
required: ['target', 'content'],
|
|
427
404
|
},
|
package/package.json
CHANGED