project-node-memory 1.2.1 → 1.3.2
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 +13 -1
- package/README_EN.md +18 -3
- package/lib/client.js +16 -1
- package/lib/engine/commands.js +7 -1
- package/lib/engine/defs.js +39 -1
- package/lib/engine/diag.js +11 -0
- package/lib/host/prompt.js +5 -6
- package/lib/host/routes.js +35 -0
- package/lib/host/tools.js +33 -16
- package/package.json +1 -1
- package/skills/project-node-memory-guide/SKILL.md +30 -30
package/README.md
CHANGED
|
@@ -183,6 +183,18 @@ config:
|
|
|
183
183
|
|
|
184
184
|
硬规则:`<node>` / `</node>` 必须各占整行;`<tscl>` 内部不做结构解析(里面的 `##` 不算标题);
|
|
185
185
|
同一 `tscl` id 全库只能定义一次,且不能与节点 TID 同名。
|
|
186
|
+
|
|
187
|
+
定义也可以**就地写在句子中间**,渲染时标签被剥掉、值留在原处:
|
|
188
|
+
|
|
189
|
+
```md
|
|
190
|
+
当前版本号为:<tscl id="fact:ver">v1.23.45</tscl>,向下兼容 v1.22。
|
|
191
|
+
ABI 校验见 [[tscl="fact:ver"]]。
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
句内定义**必须单行闭合**(行首的 `<tscl>` 才有跨行保护);定义别写进表格行(`|` 会破格,
|
|
195
|
+
`kb_diag` 会 warning)。要核一个值,`kb_read target=<定义 id>` 直接回
|
|
196
|
+
「定义体 / 定义在哪个节点 / 被谁引用」。
|
|
197
|
+
|
|
186
198
|
旧写法 `[[TID]]` 仍兼容(有定义时当宏、否则当链接),`kb_diag` 会 warning,2.0 移除。
|
|
187
199
|
|
|
188
200
|
## 从 1.0 升到 1.1
|
|
@@ -205,7 +217,7 @@ kb_write mode=node target=<TID> content=… # 逐条改,或直接改 md 再 k
|
|
|
205
217
|
## 自检(不装 DSH 也能跑)
|
|
206
218
|
|
|
207
219
|
```bash
|
|
208
|
-
node scripts/check.mjs #
|
|
220
|
+
node scripts/check.mjs # 292 项:清单契约 / 工具 / 路由 / 客户端槽位 / 改名改写 / 建库写库 / tscl / 提示词段落 / 随包技能
|
|
209
221
|
node test/parity.mjs # 与 Python 原实现逐条对拍(需要 python 与 nodekb 源码)
|
|
210
222
|
```
|
|
211
223
|
|
package/README_EN.md
CHANGED
|
@@ -161,8 +161,23 @@ leave references at every use site, so a single edit updates the whole library.
|
|
|
161
161
|
|
|
162
162
|
Hard rules: `<node>` / `</node>` must each occupy an entire line; nothing inside `<tscl>` is parsed
|
|
163
163
|
as structure (a `##` there is not a heading); a `tscl` id may be defined exactly once and must not
|
|
164
|
-
collide with a node TID.
|
|
165
|
-
|
|
164
|
+
collide with a node TID.
|
|
165
|
+
|
|
166
|
+
A definition may also sit **inline in the middle of a sentence** — the tags are stripped when
|
|
167
|
+
rendered and the value stays where it is written:
|
|
168
|
+
|
|
169
|
+
```md
|
|
170
|
+
Current version: <tscl id="fact:ver">v1.23.45</tscl>, backward compatible with v1.22.
|
|
171
|
+
ABI check: [[tscl="fact:ver"]].
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
An inline definition **must close on the same line** (only a line-start `<tscl>` gets cross-line
|
|
175
|
+
protection), and it must not go into a table row (a `|` inside the body breaks the row; `kb_diag`
|
|
176
|
+
warns). To check one value, `kb_read target=<definition id>` returns the body, where it is defined,
|
|
177
|
+
and who references it.
|
|
178
|
+
|
|
179
|
+
The old `[[TID]]` form still works (macro when a definition exists, link otherwise) with a
|
|
180
|
+
`kb_diag` warning, and goes away in 2.0.
|
|
166
181
|
|
|
167
182
|
## Upgrading from 1.0
|
|
168
183
|
|
|
@@ -188,7 +203,7 @@ kb_write mode=node target=<TID> content=… # fix one at a time, or edit the md
|
|
|
188
203
|
## Self-check (no DSH required)
|
|
189
204
|
|
|
190
205
|
```bash
|
|
191
|
-
node scripts/check.mjs #
|
|
206
|
+
node scripts/check.mjs # 292 checks: manifest contract / tools / routes / client slots / rename rewriting / scaffold + write / tscl / prompt section / bundled skill
|
|
192
207
|
node test/parity.mjs # case-by-case parity against the Python implementation (needs python and nodekb sources)
|
|
193
208
|
```
|
|
194
209
|
|
package/lib/client.js
CHANGED
|
@@ -1190,6 +1190,21 @@
|
|
|
1190
1190
|
setQuery('')
|
|
1191
1191
|
}
|
|
1192
1192
|
|
|
1193
|
+
/** 点开一个 [[tscl="id"]]:跳到定义所在的节点(值就在那一段话里) */
|
|
1194
|
+
function openDef(id) {
|
|
1195
|
+
if (!kb || !id) return
|
|
1196
|
+
get('/defs', { kb: kb, id: id })
|
|
1197
|
+
.then(function (r) {
|
|
1198
|
+
if (!r || !r.owner_tid) { setErr('未找到定义: ' + id); return }
|
|
1199
|
+
setErr('')
|
|
1200
|
+
setHits(null)
|
|
1201
|
+
setQuery('')
|
|
1202
|
+
setSel(r.owner_tid)
|
|
1203
|
+
setView('node')
|
|
1204
|
+
})
|
|
1205
|
+
.catch(function (e) { setErr(e.message) })
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1193
1208
|
function runSearch() {
|
|
1194
1209
|
var q = (query || '').trim()
|
|
1195
1210
|
if (!q) { setHits(null); return }
|
|
@@ -1441,7 +1456,7 @@
|
|
|
1441
1456
|
: null,
|
|
1442
1457
|
)
|
|
1443
1458
|
: view === 'raw' || view === 'source' || view === 'toc'
|
|
1444
|
-
? h(Rich, { text: shownText, onOpen: open })
|
|
1459
|
+
? h(Rich, { text: shownText, onOpen: open, onMacro: openDef })
|
|
1445
1460
|
: h(Md, { text: shownText, onOpen: open })
|
|
1446
1461
|
var cutNotice = textCut
|
|
1447
1462
|
? h(
|
package/lib/engine/commands.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { render_node, render_subtree, toc_lines, node_info, resolve_path, TransclusionError } from './render.js'
|
|
10
10
|
import { search, formatSearch } from './search.js'
|
|
11
11
|
import { diag } from './diag.js'
|
|
12
|
-
import { collectDefs } from './defs.js'
|
|
12
|
+
import { collectDefs, refsToDef, formatDefRead } from './defs.js'
|
|
13
13
|
import { lib_root_tids } from './mdlib.js'
|
|
14
14
|
|
|
15
15
|
// 整支渲染字符上限(0 = 不限)。防止一次调用打爆上下文(root ≈ 23.5 万字符)
|
|
@@ -30,12 +30,18 @@ export function cmdRead(nodes, target, { node = false, raw = false, toc = false,
|
|
|
30
30
|
return { text: e.message, code: 2 }
|
|
31
31
|
}
|
|
32
32
|
if (tid === null) {
|
|
33
|
+
// 不是节点:再看是不是一个 tscl 定义 id(值的老家可能只是一句话)
|
|
34
|
+
const defsMap = defs || collectDefs(nodes).defs
|
|
35
|
+
const def = defsMap.get(target)
|
|
36
|
+
if (def) return { text: formatDefRead(def, refsToDef(nodes, target)), code: 0, def: target }
|
|
33
37
|
const cands = [...nodes.keys()]
|
|
34
38
|
.filter((t) => t.startsWith(target) || t.startsWith(target + ':'))
|
|
35
39
|
.sort()
|
|
36
40
|
.slice(0, 10)
|
|
37
41
|
let msg = `未找到节点: ${target}`
|
|
38
42
|
if (cands.length) msg += ';候选: ' + cands.join('、')
|
|
43
|
+
const defSuffix = [...defsMap.keys()].filter((d) => d.includes(target)).sort().slice(0, 5)
|
|
44
|
+
if (defSuffix.length) msg += ';同名 tscl 定义候选: ' + defSuffix.join('、')
|
|
39
45
|
return { text: msg, code: 2 }
|
|
40
46
|
}
|
|
41
47
|
try {
|
package/lib/engine/defs.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* 无人引用,全部以 problems 形式交回给 diag 定级。
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { extract_defs } from './parse.js'
|
|
11
|
+
import { extract_defs, extractRefs } from './parse.js'
|
|
12
12
|
|
|
13
13
|
const NESTED_OPEN = /<tscl\b[^>]*>(?:(?!<\/tscl>)[\s\S])*?<tscl\b/
|
|
14
14
|
|
|
@@ -58,3 +58,41 @@ export function collectDefs(nodes) {
|
|
|
58
58
|
export function isMultiline(body) {
|
|
59
59
|
return String(body || '').replace(/\s+$/, '').includes('\n')
|
|
60
60
|
}
|
|
61
|
+
|
|
62
|
+
/** 谁在正文里引用了这个定义([[tscl="id"]],或指向它的旧语法裸引用)→ [{tid,title,source,line,legacy}] */
|
|
63
|
+
export function refsToDef(nodes, id) {
|
|
64
|
+
const out = []
|
|
65
|
+
for (const node of nodes.values()) {
|
|
66
|
+
const content = String(node.content || '')
|
|
67
|
+
if (!content.includes(id)) continue
|
|
68
|
+
for (const ref of extractRefs(content)) {
|
|
69
|
+
if (ref.id !== id) continue
|
|
70
|
+
if (ref.kind !== 'tscl' && ref.kind !== 'legacy') continue
|
|
71
|
+
out.push({
|
|
72
|
+
tid: node.tid,
|
|
73
|
+
title: node.title || '',
|
|
74
|
+
source: node.source || '',
|
|
75
|
+
line: content.slice(0, ref.index).split('\n').length,
|
|
76
|
+
legacy: ref.kind === 'legacy',
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return out
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** 定义 → 人读文本(kb_read 按 tscl id 读、面板点宏时跳转用) */
|
|
84
|
+
export function formatDefRead(def, refs = []) {
|
|
85
|
+
const src = String(def.source || '').split(/[\\/]/).filter(Boolean).slice(-2).join('/')
|
|
86
|
+
const lines = [`定义体 <tscl id="${def.id}"> · 定义在 ${def.ownerTid}${src ? `(${src})` : ''}`, '', String(def.body || '').replace(/\s+$/, '')]
|
|
87
|
+
if (def.count > 1) lines.push('', `重复定义 ${def.count} 处(必须只剩一处): ${def.owners.join('、')}`)
|
|
88
|
+
if (refs.length) {
|
|
89
|
+
lines.push('', `被引用 ${refs.length} 处:`)
|
|
90
|
+
for (const r of refs) {
|
|
91
|
+
const at = String(r.source || '').split(/[\\/]/).filter(Boolean).slice(-2).join('/')
|
|
92
|
+
lines.push(`- ${r.tid}${r.title ? ` · ${r.title}` : ''}${at ? `(${at}#L${r.line})` : ''}${r.legacy ? ` ← 旧语法,建议 [[tscl="${def.id}"]]` : ''}`)
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
lines.push('', '尚未被任何 [[tscl="…"]] 引用。')
|
|
96
|
+
}
|
|
97
|
+
return lines.join('\n')
|
|
98
|
+
}
|
package/lib/engine/diag.js
CHANGED
|
@@ -229,6 +229,17 @@ export function diag(nodes, { problems = [], strict = false, libRoot, defs = nul
|
|
|
229
229
|
warnings.push(`位置型 TID 链接(插章节会漂移): ${node.tid} → ${ref.raw}`)
|
|
230
230
|
}
|
|
231
231
|
}
|
|
232
|
+
|
|
233
|
+
// 定义写在表格行里:表格只放单行宏,定义体含 | 会破格
|
|
234
|
+
if (content.includes('<tscl')) {
|
|
235
|
+
const lines = content.split('\n')
|
|
236
|
+
for (let li = 0; li < lines.length; li++) {
|
|
237
|
+
if (!/<tscl\b/.test(lines[li])) continue
|
|
238
|
+
if (/^\s*\|/.test(lines[li])) {
|
|
239
|
+
warnings.push(`定义写在表格行里: ${node.tid} 第 ${li + 1} 行 → <tscl>(定义体含 | 会破格;把定义移到正文段落)`)
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
232
243
|
}
|
|
233
244
|
|
|
234
245
|
for (const def of defsMap.values()) {
|
package/lib/host/prompt.js
CHANGED
|
@@ -14,14 +14,13 @@ export const PROMPT_SECTION_ORDER = 100
|
|
|
14
14
|
export const DEFAULT_PROMPT = [
|
|
15
15
|
'## 项目知识库(project-node-memory)',
|
|
16
16
|
'',
|
|
17
|
-
'
|
|
17
|
+
'当本项目需要沉淀长期知识(设计决策、踩坑、事实常量、待办、交接)时,可用 `.nodekb/` + `kb_*` 工具。',
|
|
18
18
|
'',
|
|
19
|
-
'- 建库:`kb_init`
|
|
20
|
-
'- 写库:`kb_write`(append/replace/create,或对显式节点用 mode=node)。写前校验 TID
|
|
21
|
-
'- 读库:先 `kb_read mode=toc`
|
|
19
|
+
'- 建库:`kb_init` 一次铺出骨架 `doc/ lessons/ todo/ index/entry.md` + 落位约定。已有 `.nodekb/` 就别重建;目录与节点类型都可按项目实际增删。',
|
|
20
|
+
'- 写库:`kb_write`(append/replace/create,或对显式节点用 mode=node)。写前校验 TID 与标签独占一行,写后 `kb_diag` 报悬空引用/重复定义。',
|
|
21
|
+
'- 读库:先 `kb_read mode=toc` 看结构,再按 TID 读一支;找不到就 `kb_search`。别一次性把整库读进上下文。',
|
|
22
22
|
'- 改名前先 `kb_backlinks`,改名用 `kb_rename`(会重写全库引用并跳过被并发改过的文件)。',
|
|
23
|
-
'-
|
|
24
|
-
'- 旧写法 `[[TID]]` 仍能用但 `kb_diag` 会 warning,2.0 移除(老库按 diag 清单逐处改成 `[[node=…]]` / `[[tscl=…]]`)。',
|
|
23
|
+
'- 引用分两种:结构用 `[[node="xxx"]]`(只出一个链接,不复制正文);值只写一处——定义 `<tscl id="xxx:xxx">…</tscl>`,嵌入处写 `[[tscl="xxx:xxx"]]`,同一 id 全库只能定义一次。定义可就近写在正文句子里(单行、别写进表格)。读一个值用 `kb_read target=<定义 id>`。',
|
|
25
24
|
].join('\n')
|
|
26
25
|
|
|
27
26
|
function textOf(config) {
|
package/lib/host/routes.js
CHANGED
|
@@ -11,6 +11,7 @@ import { render_node, render_subtree, toc_tree, node_info, resolve_path } from '
|
|
|
11
11
|
import { search, formatSearch } from '../engine/search.js'
|
|
12
12
|
import { diag } from '../engine/diag.js'
|
|
13
13
|
import { extractRefs } from '../engine/parse.js'
|
|
14
|
+
import { refsToDef, formatDefRead } from '../engine/defs.js'
|
|
14
15
|
import { renameFileNode, formatRename } from '../engine/rename.js'
|
|
15
16
|
import { openLibrary, knownLibraries, whereInfo, workspaceOf } from './runtime.js'
|
|
16
17
|
import { promptState } from './prompt.js'
|
|
@@ -187,6 +188,40 @@ export function registerRoutes(ctx, config) {
|
|
|
187
188
|
return
|
|
188
189
|
}
|
|
189
190
|
|
|
191
|
+
if (url.pathname === `${PREFIX}/defs`) {
|
|
192
|
+
const lib = open()
|
|
193
|
+
const id = q.get('id')
|
|
194
|
+
if (id) {
|
|
195
|
+
const def = lib.defs.get(id)
|
|
196
|
+
if (!def) {
|
|
197
|
+
send(res, 404, { error: `未找到定义: ${id}`, kb: lib.name })
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
const refs = refsToDef(lib.nodes, id)
|
|
201
|
+
send(res, 200, {
|
|
202
|
+
kb: lib.name,
|
|
203
|
+
id,
|
|
204
|
+
owner_tid: def.ownerTid,
|
|
205
|
+
owner_title: def.ownerTitle || '',
|
|
206
|
+
source: def.source,
|
|
207
|
+
body: def.body,
|
|
208
|
+
count: def.count,
|
|
209
|
+
refs,
|
|
210
|
+
text: formatDefRead(def, refs),
|
|
211
|
+
})
|
|
212
|
+
return
|
|
213
|
+
}
|
|
214
|
+
const defs = [...lib.defs.values()].map((d) => ({
|
|
215
|
+
id: d.id,
|
|
216
|
+
owner_tid: d.ownerTid,
|
|
217
|
+
source: d.source,
|
|
218
|
+
chars: String(d.body || '').length,
|
|
219
|
+
count: d.count,
|
|
220
|
+
}))
|
|
221
|
+
send(res, 200, { kb: lib.name, count: defs.length, defs })
|
|
222
|
+
return
|
|
223
|
+
}
|
|
224
|
+
|
|
190
225
|
if (url.pathname === `${PREFIX}/source`) {
|
|
191
226
|
const lib = open()
|
|
192
227
|
const target = q.get('tid') || 'root'
|
package/lib/host/tools.js
CHANGED
|
@@ -13,6 +13,7 @@ import { render_node, render_subtree, toc_lines, node_info, resolve_path } from
|
|
|
13
13
|
import { search, formatSearch } from '../engine/search.js'
|
|
14
14
|
import { diag } from '../engine/diag.js'
|
|
15
15
|
import { extractRefs } from '../engine/parse.js'
|
|
16
|
+
import { refsToDef, formatDefRead } from '../engine/defs.js'
|
|
16
17
|
import { renameFileNode, formatRename } from '../engine/rename.js'
|
|
17
18
|
import { scaffold } from '../engine/scaffold.js'
|
|
18
19
|
import { writeInto } from '../engine/write.js'
|
|
@@ -72,8 +73,7 @@ export function buildTools(ctx, config) {
|
|
|
72
73
|
tools.push({
|
|
73
74
|
name: 'kb_list',
|
|
74
75
|
description:
|
|
75
|
-
'列出当前可用的 nodekb
|
|
76
|
-
'不确定该传什么 kb 参数时先调它。只读、极轻量。',
|
|
76
|
+
'列出当前可用的 nodekb 知识库以及本次调用实际会命中的默认库。不确定该传什么 kb 参数时先用它。',
|
|
77
77
|
parameters: { type: 'object', properties: {} },
|
|
78
78
|
output: {
|
|
79
79
|
schema: {},
|
|
@@ -96,12 +96,15 @@ export function buildTools(ctx, config) {
|
|
|
96
96
|
tools.push({
|
|
97
97
|
name: 'kb_read',
|
|
98
98
|
description:
|
|
99
|
-
'读知识库的一个节点。target = TID 或其唯一前缀(空/"." =
|
|
100
|
-
'
|
|
99
|
+
'读知识库的一个节点。target = TID 或其唯一前缀(空/"." = 库根),也可以是 <tscl id> 定义的名字。' +
|
|
100
|
+
'mode: subtree(默认,整支投影)| toc(只看结构,先定位再读)| node(只本节点)| raw(元信息+原文)。',
|
|
101
101
|
parameters: {
|
|
102
102
|
type: 'object',
|
|
103
103
|
properties: {
|
|
104
|
-
target: {
|
|
104
|
+
target: {
|
|
105
|
+
type: 'string',
|
|
106
|
+
description: '节点 TID 或其唯一前缀;也可以是 <tscl id> 定义的名字(回定义体、定义处、被谁引用)。空字符串或 "." 表示库根。',
|
|
107
|
+
},
|
|
105
108
|
kb: KB_PARAM,
|
|
106
109
|
mode: {
|
|
107
110
|
type: 'string',
|
|
@@ -124,8 +127,26 @@ export function buildTools(ctx, config) {
|
|
|
124
127
|
|
|
125
128
|
const tid = resolve_path(lib.nodes, target)
|
|
126
129
|
if (tid === null) {
|
|
130
|
+
// target 也可能是 tscl 定义 id(值的老家可能只是一句话,没有节点)
|
|
131
|
+
const def = lib.defs.get(target)
|
|
132
|
+
if (def) {
|
|
133
|
+
return {
|
|
134
|
+
tid: target,
|
|
135
|
+
kind: 'def',
|
|
136
|
+
title: def.ownerTitle || '',
|
|
137
|
+
type: '',
|
|
138
|
+
source: def.source || '',
|
|
139
|
+
kb: lib.name,
|
|
140
|
+
mode,
|
|
141
|
+
chars: String(def.body || '').length,
|
|
142
|
+
text: warnPrefix(lib) + formatDefRead(def, refsToDef(lib.nodes, target)),
|
|
143
|
+
}
|
|
144
|
+
}
|
|
127
145
|
const cands = [...lib.nodes.keys()].filter((t) => t.startsWith(target) || t.startsWith(target + ':')).sort().slice(0, 10)
|
|
128
|
-
|
|
146
|
+
const defCands = [...lib.defs.keys()].filter((d) => d.includes(target)).sort().slice(0, 5)
|
|
147
|
+
throw new Error(
|
|
148
|
+
`未找到节点: ${target}${cands.length ? `;候选: ${cands.join('、')}` : ''}${defCands.length ? `;tscl 定义候选: ${defCands.join('、')}` : ''}`,
|
|
149
|
+
)
|
|
129
150
|
}
|
|
130
151
|
|
|
131
152
|
let out
|
|
@@ -165,8 +186,7 @@ export function buildTools(ctx, config) {
|
|
|
165
186
|
tools.push({
|
|
166
187
|
name: 'kb_search',
|
|
167
188
|
description:
|
|
168
|
-
'检索知识库(中文 2-gram + 多词加权 +
|
|
169
|
-
'先 kb_search 拿 tid 再 kb_read,比 grep 后逐个 read 省往返。',
|
|
189
|
+
'检索知识库(中文 2-gram + 多词加权 + 子串兜底)。建议先 kb_search 拿 tid 再 kb_read。',
|
|
170
190
|
parameters: {
|
|
171
191
|
type: 'object',
|
|
172
192
|
properties: {
|
|
@@ -204,8 +224,7 @@ export function buildTools(ctx, config) {
|
|
|
204
224
|
tools.push({
|
|
205
225
|
name: 'kb_diag',
|
|
206
226
|
description:
|
|
207
|
-
'结构自检。errors =
|
|
208
|
-
'warnings = 疑似漂移(同一段文字出现在多处),默认不算失败。写完 md 就跑一次。',
|
|
227
|
+
'结构自检。errors = 真损坏;warnings = 疑似漂移,默认不算失败。建议写完 md 就跑一次。',
|
|
209
228
|
parameters: {
|
|
210
229
|
type: 'object',
|
|
211
230
|
properties: {
|
|
@@ -234,8 +253,7 @@ export function buildTools(ctx, config) {
|
|
|
234
253
|
tools.push({
|
|
235
254
|
name: 'kb_backlinks',
|
|
236
255
|
description:
|
|
237
|
-
'反查:谁引用了这个 id
|
|
238
|
-
'比全库 grep 准——只认真实引用语法。',
|
|
256
|
+
'反查:谁引用了这个 id。建议改 TID 或删节点前先跑。',
|
|
239
257
|
parameters: {
|
|
240
258
|
type: 'object',
|
|
241
259
|
properties: {
|
|
@@ -269,8 +287,7 @@ export function buildTools(ctx, config) {
|
|
|
269
287
|
tools.push({
|
|
270
288
|
name: 'kb_rename',
|
|
271
289
|
description:
|
|
272
|
-
'重命名 md
|
|
273
|
-
'写前重读校验,别人刚改过的文件跳过不覆盖。',
|
|
290
|
+
'重命名 md 文件并改写全库指向它的引用。写前重读校验,别人刚改过的文件跳过不覆盖。',
|
|
274
291
|
parameters: {
|
|
275
292
|
type: 'object',
|
|
276
293
|
properties: {
|
|
@@ -383,8 +400,8 @@ export function buildTools(ctx, config) {
|
|
|
383
400
|
tools.push({
|
|
384
401
|
name: 'kb_write',
|
|
385
402
|
description:
|
|
386
|
-
'
|
|
387
|
-
'
|
|
403
|
+
'写库并立刻自检。写前会校验 TID 与 <node> 规范。' +
|
|
404
|
+
'mode=append(追加,不存在则建)| replace(整篇)| create(只新建)| node(就地改一个 <node> 块)。',
|
|
388
405
|
parameters: {
|
|
389
406
|
type: 'object',
|
|
390
407
|
properties: {
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: project-node-memory-guide
|
|
3
|
-
description: 用 .nodekb/ + kb_* 工具沉淀项目长期知识(设计决策、踩坑、事实常量、待办、交接)时的写法规范——什么时候该写、文件怎么分、TID 怎么起、唯一事实用 tscl
|
|
3
|
+
description: 用 .nodekb/ + kb_* 工具沉淀项目长期知识(设计决策、踩坑、事实常量、待办、交接)时的写法规范——什么时候该写、文件怎么分、TID 怎么起、唯一事实用 tscl 怎么共享、先读什么再写什么。准备创建知识库时先读该技能。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# 写项目节点知识库(`.nodekb/`)
|
|
7
7
|
|
|
8
8
|
## 什么时候该写
|
|
9
9
|
|
|
10
|
-
值得沉淀的:**设计决策与理由**、**踩过的坑(症状 + 根因 +
|
|
10
|
+
值得沉淀的:**设计决策与理由**、**踩过的坑(症状 + 根因 + 对策)**、**事实常量**(版本号、路径、阈值、接口字段)、**待办与交接**、**环境和操作手册**等。
|
|
11
11
|
|
|
12
12
|
不该写的:一次性的调试输出、能从代码直接读出来的内容(函数签名、目录列表)、临时状态。判据是「下次换个人/换个会话,不知道这条会不会重做一遍错事」。
|
|
13
13
|
|
|
14
|
-
##
|
|
14
|
+
## 先读再写
|
|
15
15
|
|
|
16
16
|
```
|
|
17
17
|
kb_read mode=toc # 结构 + 每节字符数,几百 token
|
|
@@ -19,44 +19,45 @@ kb_read target=<TID> # 按需读一支
|
|
|
19
19
|
kb_search query=... # toc 里找不到再搜
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
**不要**一次把整库读进上下文。写之前先确认:这个结论是不是已经写在别处了?是的话改那一处,不要把同一结论写两遍。
|
|
23
23
|
|
|
24
|
-
##
|
|
24
|
+
## 目录规划
|
|
25
25
|
|
|
26
|
-
| 目录
|
|
27
|
-
|
|
28
|
-
| `doc/`
|
|
29
|
-
| `lessons/`
|
|
30
|
-
| `todo/`
|
|
31
|
-
| `index/entry.md` | 总入口与落位约定
|
|
26
|
+
| 目录 | 放什么 | 判据 |
|
|
27
|
+
| ---------------- | -------------------------------- | ------------------------------ |
|
|
28
|
+
| `doc/` | 主题文档:架构、模块、流程、配置 | 「是什么、怎么运转」 |
|
|
29
|
+
| `lessons/` | 踩坑记录 | 「症状 → 根因 → 对策」三行齐全 |
|
|
30
|
+
| `todo/` | 未完成事项、阻塞、交接 | 有明确的下一步动作 |
|
|
31
|
+
| `index/entry.md` | 总入口与落位约定 | 只放导航,不放正文 |
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
这张表是**推荐做法**,你可以自行根据项目实际情况进行规划。如项目没有待办就别建 `todo/`,需要 `adr/`、`api/` 等就自己加;节点 `type` 同样自由。
|
|
34
34
|
|
|
35
35
|
一个主题一个文件,**单文件取 < 4KB**;变大就按子主题拆成 `## 3.x` 段或独立文件。
|
|
36
36
|
|
|
37
|
-
##
|
|
37
|
+
## 两种引用方法
|
|
38
38
|
|
|
39
|
-
| 写法
|
|
40
|
-
|
|
41
|
-
| `[[node="doc:03-build:3.1"]]` | 一个链接,**不复制正文**
|
|
42
|
-
| `[[tscl="fact:abi"]]`
|
|
39
|
+
| 写法 | 渲染成 |
|
|
40
|
+
| ----------------------------- | ---------------------------------------------- |
|
|
41
|
+
| `[[node="doc:03-build:3.1"]]` | 一个链接,**不复制正文** |
|
|
42
|
+
| `[[tscl="fact:abi"]]` | 把 `<tscl id="fact:abi">` 的定义体**替换进来** |
|
|
43
43
|
|
|
44
44
|
```md
|
|
45
|
-
<tscl id="fact:
|
|
46
|
-
|
|
45
|
+
<tscl id="fact:sample">
|
|
46
|
+
tscl 机制支持段落标记与行内标记
|
|
47
47
|
</tscl>
|
|
48
|
+
|
|
49
|
+
<tscl id="fact:sample2">tscl 机制支持段落标记与行内标记</tscl>
|
|
48
50
|
```
|
|
49
51
|
|
|
50
52
|
规则:
|
|
51
53
|
|
|
52
|
-
- **同一个 id
|
|
53
|
-
- id 不能和节点 TID
|
|
54
|
-
-
|
|
54
|
+
- **同一个 id 全库只能定义一次**。
|
|
55
|
+
- id 不能和节点 TID 同名。惯例:值用 `fact:` 前缀,结构用目录前缀。
|
|
56
|
+
- 定义可以**就地写在正文句子里**:`当前版本号为:<tscl id="fact:ver">v1.23.45</tscl>,向下兼容 v1.22。`(渲染时标签被剥掉,别处写 `[[tscl="fact:ver"]]` 展开同一个值)。这种句内定义**必须单行闭合**。
|
|
57
|
+
- 宏体**跨行**时不要内联进表格单元格;定义本身也别写进表格行(`|` 会破格)。
|
|
58
|
+
- 要核一个值,`kb_read target=<定义 id>` 直接回定义体、定义处、被谁引用。
|
|
55
59
|
- 定义块内部的 `##` **不会**变成节点——它只是文本。
|
|
56
60
|
|
|
57
|
-
旧写法 `[[TID]]` 仍能解析(有定义时当宏、否则当链接),但 `kb_diag` 会 warning,2.0 移除。
|
|
58
|
-
接手老库时按 `kb_diag` 的「旧语法引用」清单逐处改成 `[[node=…]]` 或 `[[tscl=…]]`,改完再跑一次 `kb_diag`。
|
|
59
|
-
|
|
60
61
|
## TID 规则
|
|
61
62
|
|
|
62
63
|
- 文件节点:`<目录>:<文件名 slug>`,例如 `doc/03-工程经验与坑.md` → `doc:03-工程经验与坑`
|
|
@@ -72,12 +73,11 @@ kb_write target=<显式节点TID> mode=node content=... # 只换 <
|
|
|
72
73
|
kb_write target=doc/03-...md mode=append content=...
|
|
73
74
|
```
|
|
74
75
|
|
|
75
|
-
- 写完**看返回值里的校验结果**:悬空引用、重复定义、`<node>`
|
|
76
|
+
- 写完**看返回值里的校验结果**:悬空引用、重复定义、`<node>` 没独占一行,都会当场报回来。
|
|
76
77
|
- 改已有内容用 `append` / `node`,不要 `replace` 整篇(会丢掉别人刚写的段落)。
|
|
77
78
|
- 新建文件前先 `kb_read mode=toc` 确认没有同主题的旧文件。
|
|
78
79
|
|
|
79
|
-
##
|
|
80
|
+
## 注意事项
|
|
80
81
|
|
|
81
|
-
1.
|
|
82
|
-
2.
|
|
83
|
-
3. 只写「改了什么」不写「为什么」 —— 半年后没人知道能不能改回去。
|
|
82
|
+
1. 不要把整段对话贴进 `doc/` —— 记的是过程,不是结论。
|
|
83
|
+
2. 不要把同一件事在 `doc/` 和 `lessons/` 各写一份 —— 抽成 `<tscl>` 定义,两处都引用它。
|