@thincoder/core 0.10.9 → 0.10.11
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/CHANGELOG.md +28 -2
- package/advisor/messages.mjs +4 -5
- package/advisor/project-context.mjs +69 -44
- package/agent/completion.mjs +4 -1
- package/agent/family-tools.mjs +12 -10
- package/agent/helpers.mjs +19 -11
- package/agent/light-round.mjs +1 -4
- package/agent/record-results.mjs +6 -1
- package/agent/run-start.mjs +1 -0
- package/agent/setup-reminders.mjs +1 -1
- package/agent/turn-loop.mjs +17 -13
- package/agent/unbooked-pen.mjs +62 -0
- package/agent-tools/advisor.mjs +15 -31
- package/agent-tools/audit-block.mjs +16 -62
- package/agent-tools/batch-gate.mjs +12 -21
- package/agent-tools/checkpoint.mjs +11 -6
- package/agent-tools/context.mjs +1 -1
- package/agent-tools/design-token.mjs +9 -2
- package/agent-tools/digest-budget.mjs +2 -0
- package/agent-tools/doc-check.mjs +17 -23
- package/agent-tools/manifest.mjs +45 -14
- package/agent-tools/review-facts.mjs +35 -81
- package/agent-tools/settings.mjs +65 -5
- package/agent-tools/spawn-gates.mjs +68 -31
- package/agent-tools/subagent-async.mjs +6 -3
- package/agent-tools/subagent-spawn.mjs +76 -80
- package/agent-tools/subagent.mjs +16 -16
- package/agent-tools.mjs +1 -1
- package/browser/launch.mjs +14 -3
- package/browser/queue.mjs +31 -3
- package/browser/session.mjs +4 -0
- package/config-defaults.mjs +8 -0
- package/config-presets.mjs +1 -1
- package/config.mjs +60 -11
- package/declaration.mjs +5 -12
- package/doc-check/anchors.mjs +53 -60
- package/doc-check/index.mjs +106 -140
- package/doc-check/paragraph.mjs +119 -0
- package/fts-text.mjs +4 -1
- package/i18n.mjs +5 -0
- package/ledger-cmd.mjs +17 -4
- package/ledger-db.mjs +14 -8
- package/ledger.mjs +1 -1
- package/manifest-discovery.mjs +19 -10
- package/manifest-schema.mjs +7 -35
- package/manifest.mjs +21 -9
- package/memory/code-search.mjs +5 -14
- package/memory/docs.mjs +5 -14
- package/memory/read-gate.mjs +26 -0
- package/model-context.mjs +98 -0
- package/model-specs.mjs +110 -37
- package/package.json +2 -2
- package/permission.mjs +3 -1
- package/prompts/advisor-design.md +104 -38
- package/prompts/advisor-round1.md +72 -24
- package/prompts/advisor-round2.md +62 -25
- package/prompts/advisor-round3.md +68 -24
- package/prompts/common.md +214 -58
- package/prompts/consult-base.md +10 -3
- package/prompts/discipline-engineering.md +202 -54
- package/prompts/discipline-normal.md +95 -44
- package/prompts/persona-coder.md +3 -1
- package/prompts/persona-eng-coder.md +29 -9
- package/prompts/persona-eng-designer.md +101 -35
- package/prompts/persona-engineering.md +320 -98
- package/prompts/persona-explore.md +1 -1
- package/prompts/persona-normal.md +16 -8
- package/provider/anthropic.mjs +127 -4
- package/provider/core.mjs +16 -6
- package/provider/google.mjs +8 -8
- package/provider/list-models.mjs +37 -5
- package/provider/normalize.mjs +2 -2
- package/provider/responses.mjs +7 -7
- package/provider/sse.mjs +6 -5
- package/proxy-transport.mjs +5 -3
- package/proxy.mjs +2 -1
- package/session-guard.mjs +7 -1
- package/session-lifecycle.mjs +23 -8
- package/session-segments.mjs +1 -0
- package/session-slot-claims.mjs +4 -0
- package/session-slot-write.mjs +6 -0
- package/session-slots-manifest.mjs +4 -0
- package/token-ttl.mjs +10 -0
- package/tool-docs/advisor.md +24 -5
- package/tool-docs/doc_check.md +44 -25
- package/tool-docs/ledger.md +2 -2
- package/tool-docs/manifest-read.md +8 -0
- package/tool-docs/manifest.md +9 -8
- package/tool-docs/subagent.md +41 -16
- package/tools/web-deepseek.mjs +26 -11
- package/doc-check/version.mjs +0 -84
- package/doc-check/width.mjs +0 -132
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* doc-check/paragraph.mjs — 文档纪律机检的产品档位(段判据谓词族 + 段判据——实现单源)。
|
|
3
|
+
*
|
|
4
|
+
* 口径单源 = `docs/core/design/DOC-DISCIPLINE.md` §3.18 条目 Q(单位是段 / 面谓词 / 阈值 / 不分域);
|
|
5
|
+
* 产品内建裁定 = 同档 §3.20 条目 S——谓词单源随迁:本仓 `scripts/doc-check-paragraph.mjs` 改述为
|
|
6
|
+
* **再出口缝**(薄档 · 零逻辑 · 逐名再出口),盘上不留第二份谓词实现。
|
|
7
|
+
* 两面切块:文档面量段(相邻非空正文行合并成一段,量段长)· 提示词面(`isPromptFace`)量行
|
|
8
|
+
* (逐行裸判,无结构豁免)。度量 = `[...line].length`(CJK 计 1——不引显示宽度 / 字体);
|
|
9
|
+
* 阈值 = 调用方的内置常量(`DOC_CHECK_BUILTINS.paragraphMax` / `lineMax`)——不读声明面,也不扫目录:
|
|
10
|
+
* **判定集 = 调用方传入的文件清单**。
|
|
11
|
+
* 不计入段的行类(同时把段断开)= 空行 ∥ 表格行 ∥ 标题 ∥ 列表项 ∥ 引用行 ∥ 围栏(标记行与块内整行)
|
|
12
|
+
* ∥ 区带内:只认结构谓词,无行级白名单(按行号 / 注释声明的豁免不入判);可执行行在段判据里**不豁免**
|
|
13
|
+
* (段 = 读者所见,码段渲染后仍在段内——§3.18 关键决策 ①)。
|
|
14
|
+
* 边界:本档零门禁 / 零退出码 / 零阻断——报告态由 `agent-tools/doc-check.mjs` 呈现。
|
|
15
|
+
*/
|
|
16
|
+
import { readFileSync } from "node:fs"
|
|
17
|
+
|
|
18
|
+
/** 表格行谓词(markdown 表格行结构性不可折行——不计入段,与锚检查同源)。 */
|
|
19
|
+
export const isTableRow = (l) => /^\s*\|.*\|\s*$/.test(String(l))
|
|
20
|
+
|
|
21
|
+
/** 围栏标记(行首可选空白 + 三反引号 ∥ 三波浪号 ⇒ 标记字符;非围栏行 ⇒ null)——**单源**:
|
|
22
|
+
* `isFenceLine` 与两处块扫描(本节段判据 ∥ `./anchors.mjs` 锚抽取)同取此式,使「同形围栏行止」只此一解
|
|
23
|
+
* (同一模块内同符两义 = 单源可漂的结构口子)。 */
|
|
24
|
+
export function fenceMark(l) {
|
|
25
|
+
const m = /^\s*(`{3,}|~{3,})/.exec(String(l))
|
|
26
|
+
return m ? m[1][0] : null
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** 围栏行谓词(单源 = `fenceMark`)——围栏行自身与块内整行均不计入段。 */
|
|
30
|
+
export const isFenceLine = (l) => fenceMark(l) !== null
|
|
31
|
+
|
|
32
|
+
/** 引文块行谓词(行首可选空白 + `>`)——连续 `>` 行整块不计入段(引证须逐字连续、不可折,同表行理据)。 */
|
|
33
|
+
export const isQuoteLine = (l) => /^\s*>/.test(String(l))
|
|
34
|
+
|
|
35
|
+
/** 列表项谓词(无序 `-` ∥ `*` ∥ `+` · 有序 `数字 + . ∥ ) ∥ 、` + 空白)——列表项不计入段且断段。 */
|
|
36
|
+
export const isListItem = (l) => /^\s*(?:[-*+]|\d+[.)、])\s/.test(String(l))
|
|
37
|
+
|
|
38
|
+
/** 提示词面谓词(`thincoder-core/prompts/**`——形态对齐 `./anchors.mjs` 的 `OPERAND_FACE`,单源住本档):
|
|
39
|
+
* 模型读原始文本,一行就是一个块(FR46 同源理由)⇒ 该面逐行裸判,无结构豁免。 */
|
|
40
|
+
export const isPromptFace = (abs) => /(?:^|\/)thincoder-core\/prompts\//i.test(String(abs).replace(/\\/g, "/"))
|
|
41
|
+
|
|
42
|
+
/** 区带头谓词(§3.15 谓词面):标题文本去首部编号(`^\d+(?:\.\d+)*[.、]?\s*`)后——等于区带名 ∨ 以
|
|
43
|
+
* 「区带名 + (∕(」起(容括注后缀,如 `变更记录(续)`);闭集外一律不纳(fail-closed)。 */
|
|
44
|
+
export function isZoneHead(headingText, zones = []) {
|
|
45
|
+
const text = String(headingText).replace(/^\d+(?:\.\d+)*[.、]?\s*/, "")
|
|
46
|
+
return zones.some((z) => typeof z === "string" && z !== "" && (text === z || text.startsWith(z + "(") || text.startsWith(z + "(")))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** 行内可执行坐标(命令 / 搜索模式串码段)——锚判据整行跳过用(证据与命令保字面);段判据不豁免(段 = 读者所见)。 */
|
|
50
|
+
export function isExecutableLine(line) {
|
|
51
|
+
const l = String(line)
|
|
52
|
+
if (/`[^`\n]*&&[^`\n]*`/.test(l)) return true // 命令链(cd … && …)
|
|
53
|
+
if (/`[^`\n]*\b(?:cd|node|npm|npx|grep|rg|git)\s[^`\n]*`/.test(l)) return true // 码段内命令词
|
|
54
|
+
if (/`[^`\n]*(?:\[\^|\\\.|\(\?<|\(\?:|\{\d)/.test(l)) return true // 码段内搜索模式串片段
|
|
55
|
+
return false
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** 行内位置是否在反引号码段内(引述面不判)。 */
|
|
59
|
+
export function inCodeSpan(line, idx) {
|
|
60
|
+
return (String(line).slice(0, idx).match(/`/g) ?? []).length % 2 === 1
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** 段判据(阈值 = 调用方内置常量 `paragraphMax` ∥ `lineMax`;口径逐字 = §3.18 条目 Q):
|
|
64
|
+
* · 文档面:相邻非空正文行合并成一段,段长 = 组成行 `[...line].length` 之和 > `paragraphMax` ⇔ 命中
|
|
65
|
+
* (`clazz: "段太长"`,定位 = 段首行号)。正文行 = 非空 ∧ 非表格行 ∧ 非标题 ∧ 非列表项 ∧ 非引用行
|
|
66
|
+
* ∧ 非围栏(标记行与块内整行)∧ 非区带内;其余行类不计入、且把段断开。
|
|
67
|
+
* · 提示词面(`isPromptFace`):逐行裸判——`[...line].length` > `lineMax` ⇔ 命中(`clazz: "行太长"`,
|
|
68
|
+
* 无结构豁免)。
|
|
69
|
+
* 单遍扫描携标题栈(`^(#{1,6})\s` 行入栈——同级 ∥ 更高级出栈):行所属标题链任一为区带头 ⇒ 区带内
|
|
70
|
+
* (不计入段且断段)。围栏未闭合 ⇒ 至档尾整块免(fail-open 方向——§3.18 残余登记 ①);
|
|
71
|
+
* `exemptZones` 缺省 ∥ 空集 ⇒ 零豁免。`files`(必填)= **判定集**(调用方传入的档清单——本档不遍历
|
|
72
|
+
* 任何目录;空集 ⇒ 零命中)。 */
|
|
73
|
+
export function checkParagraphs({ paragraphMax, lineMax, exemptZones = [], files = [] } = {}) {
|
|
74
|
+
const zones = (Array.isArray(exemptZones) ? exemptZones : []).filter((z) => typeof z === "string" && z !== "")
|
|
75
|
+
const hits = []
|
|
76
|
+
for (const f of files) {
|
|
77
|
+
let text = ""
|
|
78
|
+
try { text = readFileSync(f, "utf8") } catch { continue }
|
|
79
|
+
if (isPromptFace(f)) {
|
|
80
|
+
text.split("\n").forEach((l, i) => {
|
|
81
|
+
const len = [...l].length
|
|
82
|
+
if (len > lineMax) hits.push({ file: f, line: i + 1, len, clazz: "行太长" })
|
|
83
|
+
})
|
|
84
|
+
continue
|
|
85
|
+
}
|
|
86
|
+
const stack = [] // 标题栈 —— { level, exempt };exempt = 自身 ∥ 祖先命中区带
|
|
87
|
+
let fence = null // 围栏标记(` ∥ ~);null = 不在块内
|
|
88
|
+
let para = null // 在写的段 —— { start, len };非正文行 ⇒ 断开
|
|
89
|
+
const lines = text.split("\n")
|
|
90
|
+
const flush = () => {
|
|
91
|
+
if (para !== null && para.len > paragraphMax) hits.push({ file: f, line: para.start, len: para.len, clazz: "段太长" })
|
|
92
|
+
para = null
|
|
93
|
+
}
|
|
94
|
+
lines.forEach((l, i) => {
|
|
95
|
+
const mark = fenceMark(l)
|
|
96
|
+
if (mark !== null) {
|
|
97
|
+
if (fence === null) fence = mark
|
|
98
|
+
else if (mark === fence) fence = null
|
|
99
|
+
flush()
|
|
100
|
+
return // 围栏标记行自身断段
|
|
101
|
+
}
|
|
102
|
+
if (fence !== null) { flush(); return } // 块内整行断段(未闭合 ⇒ 至档尾免)
|
|
103
|
+
const hm = /^(#{1,6})\s+(.*)$/.exec(l)
|
|
104
|
+
if (hm) {
|
|
105
|
+
while (stack.length && stack[stack.length - 1].level >= hm[1].length) stack.pop()
|
|
106
|
+
const self = isZoneHead(hm[2].trim(), zones)
|
|
107
|
+
stack.push({ level: hm[1].length, exempt: self || (stack[stack.length - 1]?.exempt ?? false) })
|
|
108
|
+
flush()
|
|
109
|
+
return // 标题断段
|
|
110
|
+
}
|
|
111
|
+
const inZone = stack[stack.length - 1]?.exempt === true
|
|
112
|
+
if (inZone || l.trim() === "" || isTableRow(l) || isListItem(l) || isQuoteLine(l)) { flush(); return }
|
|
113
|
+
if (para === null) para = { start: i + 1, len: 0 }
|
|
114
|
+
para.len += [...l].length
|
|
115
|
+
})
|
|
116
|
+
flush()
|
|
117
|
+
}
|
|
118
|
+
return hits
|
|
119
|
+
}
|
package/fts-text.mjs
CHANGED
|
@@ -30,7 +30,10 @@ export function segmentCJK(text) {
|
|
|
30
30
|
* 无 token ⇒ 返回空串(消费侧据此退化——SESSION.md §6.19 边界情形表)。
|
|
31
31
|
*/
|
|
32
32
|
export function buildFtsQuery(query) {
|
|
33
|
-
|
|
33
|
+
// 模型偶尔把 query 传成数组 / 对象(schema 声明是 string):先按文本归一。
|
|
34
|
+
// 不归一的话 query.split 直接抛 TypeError,整个回合崩掉(GitHub #30)。
|
|
35
|
+
const text = Array.isArray(query) ? query.join(" ") : query == null ? "" : String(query)
|
|
36
|
+
const terms = text
|
|
34
37
|
.split(/[\s,,。、;;!!??()()"`]+/)
|
|
35
38
|
.map((t) => t.trim())
|
|
36
39
|
.filter(Boolean)
|
package/i18n.mjs
CHANGED
|
@@ -60,6 +60,11 @@ export const CORE_MESSAGES = Object.freeze({
|
|
|
60
60
|
"sub.cancelled": { en: "cancelled", zh: "已取消" },
|
|
61
61
|
"sub.awaitingDigest": { en: "done · awaiting digestion", zh: "已完成 · 等待消化" },
|
|
62
62
|
"sub.queueSlot": { en: "queued · position ${n} (slot full)", zh: "排队中 · 位置 ${n}(槽满等位)" },
|
|
63
|
+
// #1507 补四键(核字典缺键——值形与宿主同形:desktop `renderer/i18n.mjs:197-200` ∥ VSC `locales/{en,zh}.json`)
|
|
64
|
+
"sub.async": { en: "async", zh: "异步" },
|
|
65
|
+
"sub.sync": { en: "sync", zh: "同步" },
|
|
66
|
+
"sub.waiting": { en: "waiting", zh: "等待中" },
|
|
67
|
+
"sub.awaitingApproval": { en: "Awaiting approval: ${tool}", zh: "等待审批: ${tool}" },
|
|
63
68
|
// ── 会诊状态词 ──
|
|
64
69
|
"consult.answered": { en: "answered", zh: "已回复" },
|
|
65
70
|
"consult.terminated": { en: "terminated", zh: "已终止" },
|
package/ledger-cmd.mjs
CHANGED
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
* 「档此刻在不在盘」降为读数——读面导出 `ledgerBatchRows`、收口重锚单源 `ledgerReanchorBatch`。
|
|
13
13
|
* 批量面(§3.3 P1):选行单源 `resolveBatchRows` + 逐行落笔 `applyRowUpdate` / `applyRowClose`
|
|
14
14
|
* (与单行形共函数——迁移表判 / 写门 / evidence 门逐行照判)。
|
|
15
|
+
* 核销标记门(LIGHT-CHANNEL.md §2.5 · 台账 #1780):写入 `已核销` 的三径(close 单行 ∥ close 批量 ∥
|
|
16
|
+
* update 迁移——均经两逐行共函数)判**结果行标题**携 `ROUND_MARKER` ⇒ 拒;判据只认标题标记
|
|
17
|
+
* (不查批档 ∥ 不查评审实跑),撤回(→ 已废弃)不拦。
|
|
15
18
|
*
|
|
16
19
|
* 接线(设计档 §2.2 命令面接线):族出口 = `ledger.mjs`(re-export 两源——KD-M2-3);查询命令 ←
|
|
17
20
|
* tools/index.mjs(assembleBuiltinTools 全角色面);写命令 ← agent/family-tools.mjs(assembleFamilyTools
|
|
@@ -19,7 +22,7 @@
|
|
|
19
22
|
* 消费侧静态 import 会破 W8 契约②)。
|
|
20
23
|
*/
|
|
21
24
|
import { resolve } from "node:path"
|
|
22
|
-
import { AGING_DAYS, ALLOWED_MIGRATIONS, EVIDENCE_SOFT_LIMIT, nowIso, openLedger, PENDING_STATUSES, resolveLedgerRoot } from "./ledger-db.mjs"
|
|
25
|
+
import { AGING_DAYS, ALLOWED_MIGRATIONS, EVIDENCE_SOFT_LIMIT, nowIso, openLedger, PENDING_STATUSES, resolveLedgerRoot, ROUND_MARKER } from "./ledger-db.mjs"
|
|
23
26
|
import { resolveDeclaredRef } from "./declaration.mjs"
|
|
24
27
|
|
|
25
28
|
/** 逐行老化判真(LEDGER.md §13.4 · 2026-10-07 批 ledger-tool):未决四态(`PENDING_STATUSES`)∧
|
|
@@ -210,8 +213,9 @@ function assertEvidenceValue(fnName, evidence) {
|
|
|
210
213
|
if (evidence != null && String(evidence).trim() === "") throw new Error(`${fnName}:evidence 为空(非空字符串)`)
|
|
211
214
|
}
|
|
212
215
|
|
|
213
|
-
/** 单行落笔·update(**单行形 ∥ 批量形共函数**——设计档 §3.3「逐行语义单源」):迁移表判 →
|
|
214
|
-
*
|
|
216
|
+
/** 单行落笔·update(**单行形 ∥ 批量形共函数**——设计档 §3.3「逐行语义单源」):迁移表判 → 核销标记门
|
|
217
|
+
* (目标 = `已核销` ∧ 结果标题携标记 ⇒ 拒——判序 = LIGHT-CHANNEL.md §2.5)→ executor / evidence 值计算
|
|
218
|
+
* (含两拒面)→ 写门 → UPDATE(判序 = §13.9 逐字未变)。`to` = `patch.status ?? 行现态`
|
|
215
219
|
* ——批量形纯字段补丁(不携 `status`)时 `to === row.status` ⇒ 迁移表不判、`status` 零写(§3.3)。
|
|
216
220
|
* `resume_when` = 普通可空字段补丁(`patch.resume_when ?? 行值`——§3.4)。 */
|
|
217
221
|
function applyRowUpdate(db, cwd, row, patch, executorSessionId) {
|
|
@@ -219,6 +223,10 @@ function applyRowUpdate(db, cwd, row, patch, executorSessionId) {
|
|
|
219
223
|
if (to !== row.status && !(ALLOWED_MIGRATIONS[row.status] ?? []).includes(to)) {
|
|
220
224
|
throw new Error(`ledgerUpdate:迁移 ${row.status} → ${to} 不在允许迁移表`)
|
|
221
225
|
}
|
|
226
|
+
// 核销标记门(LIGHT-CHANNEL.md §2.5):判**结果标题**——一笔同携清标记 title + status=已核销 ⇒ 放行
|
|
227
|
+
if (to === "已核销" && String(patch.title ?? row.title ?? "").includes(ROUND_MARKER)) {
|
|
228
|
+
throw new Error(`ledgerUpdate:行标题仍携「${ROUND_MARKER}」——收尾链未跑完,不许核销(先跑收尾全链,再由父侧清标记)`)
|
|
229
|
+
}
|
|
222
230
|
const nextExecutor = resolveExecutorTarget(row, to, patch, executorSessionId)
|
|
223
231
|
const nextEvidence = evidenceValue("ledgerUpdate", row.evidence, patch.evidence, patch.evidenceReplace)
|
|
224
232
|
assertEvidenceValue("ledgerUpdate", patch.evidence)
|
|
@@ -252,11 +260,16 @@ export function ledgerUpdate({ cwd, id, patch, executorSessionId }) {
|
|
|
252
260
|
}
|
|
253
261
|
|
|
254
262
|
/** 单行落笔·close(**单行形 ∥ 批量形共函数**——设计档 §3.3「逐行语义单源」):源态判(核销仅三源——
|
|
255
|
-
* 在途不可跳)→
|
|
263
|
+
* 在途不可跳)→ 核销标记门(LIGHT-CHANNEL.md §2.5——`已核销` ∧ 标题携标记 ⇒ 拒;撤回不拦)→
|
|
264
|
+
* 值计算(拒面① 旗独传判于此)→ 追认口 `evidence` 门 → 拒面② → UPDATE。
|
|
256
265
|
* 撤回(任意态 → 已废弃——含在途直撤)同步 `executor = NULL`(LEDGER.md §3.1 ④);核销两源(勾销 /
|
|
257
266
|
* 追认)`executor` 零触碰(非在途出边)。 */
|
|
258
267
|
function applyRowClose(db, row, status, evidence, evidenceReplace) {
|
|
259
268
|
if (status === "已核销" && !["待讨论", "待设计", "待核销"].includes(row.status)) throw new Error(`ledgerClose:核销仅限 待讨论 / 待设计 / 待核销(现态 ${row.status})`)
|
|
269
|
+
// 核销标记门(LIGHT-CHANNEL.md §2.5):勾销 ∥ 追认同判;撤回(→ 已废弃)不入本门
|
|
270
|
+
if (status === "已核销" && String(row.title ?? "").includes(ROUND_MARKER)) {
|
|
271
|
+
throw new Error(`ledgerClose:行标题仍携「${ROUND_MARKER}」——收尾链未跑完,不许核销(先跑收尾全链,再由父侧清标记)`)
|
|
272
|
+
}
|
|
260
273
|
const nextEvidence = evidenceValue("ledgerClose", row.evidence, evidence, evidenceReplace)
|
|
261
274
|
if (status === "已核销" && ["待讨论", "待设计"].includes(row.status) && (nextEvidence == null || String(nextEvidence).trim() === "")) throw new Error(`ledgerClose:追认核销须带 evidence(现态 ${row.status})`)
|
|
262
275
|
assertEvidenceValue("ledgerClose", evidence) // 判序:拒面②(新值空白)落追认门之后 → UPDATE
|
package/ledger-db.mjs
CHANGED
|
@@ -87,23 +87,24 @@ export function ledgerChildren(dir) {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
/** 项目根推导**单源**(设计档 §2.1「根解析面」/ §3.8——读写同一判据、同一实现):
|
|
90
|
-
* 自 `target
|
|
91
|
-
*
|
|
92
|
-
*
|
|
90
|
+
* 自 `target` 沿**祖先链**上溯取最近「已注册」目录——**以会话锚(`process.cwd()`)为界,越界即停**:
|
|
91
|
+
* 界内上溯 = 子目录归到自己的项目根(应该);越过锚 = 挂到无关外层项目上(禁止)。
|
|
92
|
+
* 命中 ⇒ `{ root, basis: 'self' | 'ancestor' }`(自身 vs 界内祖先);
|
|
93
|
+
* 界内无命中 ∧ `descend: true` ⇒ **向下收敛**锚的直接子目录(`ledgerChildren` 同款惰性枚举 +
|
|
93
94
|
* 上限、名序升序):恰一 ⇒ 取之;≥2 ⇒ ① 先取**有键控库**者(账已在盘 = 事实)② 该集名序首者 ⇒
|
|
94
95
|
* `{ root, basis: 'descendant' }`(向下发现——**须报明**);零已注册子目录 ⇒ `null`(无根)。
|
|
95
|
-
* `descend: false` ⇒
|
|
96
|
+
* `descend: false` ⇒ 只问界内上溯支(族发现 ∥ 标记面——容器 ∥ 工作区锚 ⇒ `null` ⇒ 族求和)。
|
|
96
97
|
* **无自指**:内部「库在盘」判据只用 `ledgerKey` + `keyedDbFile`(不经 `ledgerDbPath`)。
|
|
97
98
|
* @param {string} [target] 动作目标路径(目录)
|
|
98
99
|
* @param {{descend?: boolean}} [opts] `descend` 缺省 `true`(动作面——`openLedger` / `ledgerDbPath` / 单范围读)
|
|
99
|
-
* @returns {{root: string, basis: 'ancestor'|'descendant'}|null} 项目根 + 判据来源 / 无根(`null`) */
|
|
100
|
+
* @returns {{root: string, basis: 'self'|'ancestor'|'descendant'}|null} 项目根 + 判据来源 / 无根(`null`) */
|
|
100
101
|
export function resolveLedgerRoot(target, { descend = true } = {}) {
|
|
101
102
|
const start = resolve(target ?? ".")
|
|
102
103
|
let dir = start
|
|
103
104
|
for (;;) {
|
|
104
|
-
if (isRegistered(dir)) return { root: dir, basis: "ancestor" }
|
|
105
|
+
if (isRegistered(dir)) return { root: dir, basis: dir === start ? "self" : "ancestor" }
|
|
105
106
|
const parent = dirname(dir)
|
|
106
|
-
if (parent === dir) break
|
|
107
|
+
if (parent === dir) break
|
|
107
108
|
dir = parent
|
|
108
109
|
}
|
|
109
110
|
if (!descend) return null
|
|
@@ -116,7 +117,7 @@ export function resolveLedgerRoot(target, { descend = true } = {}) {
|
|
|
116
117
|
|
|
117
118
|
/** 无根文案(单源——设计档 §3.8「无根契约」逐字;`openLedger` 写面 ∥ `ledgerDbPath` 共用同一句)。 */
|
|
118
119
|
const noRootMessage = (start) =>
|
|
119
|
-
`台账:自 ${start}
|
|
120
|
+
`台账:自 ${start} 未能推导项目根——该目录与本级子目录均无已注册项目(带 PROJECT-MANIFEST.json 或已有键控台账库)`
|
|
120
121
|
|
|
121
122
|
/** 项目根 → 库路径(稳定键 = `ledgerKey` 前 16 位——路径唯一标识,不含分隔符)。
|
|
122
123
|
* **归单源**(2026-10-16 批 ledger-root-derivation):根 = `resolveLedgerRoot(cwd)`——与读面 / 写面
|
|
@@ -133,6 +134,11 @@ export function ledgerDbPath(cwd) {
|
|
|
133
134
|
/** 未决四态(「活条目」口径 = 计数单源 WHERE 集;已核销 / 已废弃 = 归档态)。 */
|
|
134
135
|
export const PENDING_STATUSES = ["待讨论", "待设计", "在途", "待核销"]
|
|
135
136
|
|
|
137
|
+
/** 轻通道轮次行标记(单源——LIGHT-CHANNEL.md §2.3 开轮挂账 ∥ §2.8 闸凭据 ∥ §2.5 核销标记门
|
|
138
|
+
* 判据三面同一字面;消费面 = `agent/light-round.mjs`(经 `ledger.mjs` re-export 动态解构——
|
|
139
|
+
* W8 契约②零新静态边)∥ `ledger-cmd.mjs` 核销标记门)。 */
|
|
140
|
+
export const ROUND_MARKER = "收尾链待跑"
|
|
141
|
+
|
|
136
142
|
/** 老化阈值(天——口径 = 需求档;消费面 = `buildScan`(展示,`days` 可覆盖)+ `ledgerQuery` 逐行
|
|
137
143
|
* `aged`(查询面,`now` 可注入))。2026-10-07 批 ledger-tool:自 `ledger.mjs` 迁入(枚举常量档单源
|
|
138
144
|
* ——`ledgerQuery` 计算面直引本档,消 `ledger-cmd ↔ ledger.mjs` 新环;`ledger.mjs` 同名 re-export
|
package/ledger.mjs
CHANGED
|
@@ -151,7 +151,7 @@ export function detailScans(scans, current) {
|
|
|
151
151
|
// ── re-export(拆分件接口——命令面接线 = 动态 import 本档,KD-M2-3;`AGING_DAYS` 常量单源 = `ledger-db.mjs`
|
|
152
152
|
// ——2026-10-07 批 ledger-tool 自本档迁入,公共面零变;`ledgerChildren` / `MAX_SIBLING_SCAN` / `resolveLedgerRoot`
|
|
153
153
|
// ——2026-10-16 批 ledger-root-derivation 随迁 / 新增,公共面形零改) ──
|
|
154
|
-
export { AGING_DAYS, ALLOWED_MIGRATIONS, EVIDENCE_SOFT_LIMIT, MAX_SIBLING_SCAN, ledgerChildren, ledgerDbPath, ledgerKey, ledgerDirPath, openLedger, PENDING_STATUSES, resolveLedgerRoot, _setLedgerDirForTest, _resetLedgerDirForTest, ensureColumn, ensureExecutorColumn } from "./ledger-db.mjs"
|
|
154
|
+
export { AGING_DAYS, ALLOWED_MIGRATIONS, EVIDENCE_SOFT_LIMIT, MAX_SIBLING_SCAN, ledgerChildren, ledgerDbPath, ledgerKey, ledgerDirPath, openLedger, PENDING_STATUSES, resolveLedgerRoot, ROUND_MARKER, _setLedgerDirForTest, _resetLedgerDirForTest, ensureColumn, ensureExecutorColumn } from "./ledger-db.mjs"
|
|
155
155
|
export { ledgerAdd, ledgerBatchRows, ledgerClose, ledgerCloseBatch, ledgerCount, ledgerEvidenceOversize, ledgerQuery, ledgerReanchorBatch, ledgerUpdate, ledgerUpdateBatch, overdueOf } from "./ledger-cmd.mjs"
|
|
156
156
|
export { ledgerTool, ledgerReadTool, ledgerAddTool, ledgerCloseTool, ledgerCountTool, ledgerQueryTool, ledgerUpdateTool } from "./ledger-tools.mjs"
|
|
157
157
|
export { runLedgerAudit, runLedgerMigrate } from "./ledger-migrate.mjs"
|
package/manifest-discovery.mjs
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* (`MANIFEST_REL` ∥ `discoverProjects` ∥ `owningProject` ∥ `resolveProjectRoot`)随 import。
|
|
10
10
|
* 零环:本档不引宿主(方向单行);档外依赖 = `node:fs` ∥ `node:path`;机制详述 = `docs/core/design/MANIFEST.md` §2.2 / §2.9。
|
|
11
11
|
*/
|
|
12
|
-
import { existsSync, readdirSync } from "node:fs"
|
|
12
|
+
import { existsSync, readdirSync, statSync } from "node:fs"
|
|
13
13
|
import { dirname, join, resolve } from "node:path"
|
|
14
14
|
|
|
15
15
|
/** 数据档文件名(**每个项目一份**——项目根 = 带档目录;**git 非前提**。2026-09-17 / 2026-09-21 用户裁定)。 */
|
|
@@ -88,27 +88,36 @@ export function discoverRepos(cwd) {
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
/**
|
|
91
|
-
* **归属形单点**(KD-M1-30
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
* 基座三态 + 覆盖位 + 薄委托等价逐格)+ `docs/batches/2026-09-29-structure-split-2.test.mjs` 导出枚举探针(`Object.keys` 名面核))。
|
|
91
|
+
* **归属形单点**(KD-M1-30 · 2026-10-23 用户裁定):自 `target`(目录 = 自身;文件 = 其所在目录)
|
|
92
|
+
* 沿**祖先链**上溯,取最近带 `MANIFEST_REL` 的目录 ⇒ 该目录;无 ⇒ `null`(交发现兜底)。
|
|
93
|
+
* 会话落在项目内层子目录时,这一路上溯正是它归到自己项目根的通路(用户 2026-10-23 23:24
|
|
94
|
+
* 「本来就该爬到项目根啊」);**发现梯(`discoverProjects`)仍纯向下**——从工作区容器只找直接
|
|
95
|
+
* 子目录,不把容器挂到外层项目上。**纯 fs** · **不跨兄弟** · **无全局优先级**。
|
|
96
|
+
* **覆盖位**(`_setProjectRootForTest` 在场)⇒ **头部短路**:直接返回覆盖值(不查档存在性)。
|
|
98
97
|
* @param {string} [target] 目标路径(目录 / 文件)
|
|
99
|
-
* @returns {string|null}
|
|
98
|
+
* @returns {string|null} 带档目录绝对路径 / null
|
|
100
99
|
*/
|
|
101
100
|
export function owningProject(target) {
|
|
102
101
|
if (_projectRootOverride) return resolve(_projectRootOverride)
|
|
103
102
|
let dir = resolve(target ?? ".")
|
|
103
|
+
const st = statSync(dir, { throwIfNoEntry: false })
|
|
104
|
+
if (st && !st.isDirectory()) dir = dirname(dir)
|
|
104
105
|
for (;;) {
|
|
105
106
|
if (existsSync(join(dir, MANIFEST_REL))) return dir
|
|
106
107
|
const parent = dirname(dir)
|
|
107
|
-
if (parent === dir) return null
|
|
108
|
+
if (parent === dir) return null
|
|
108
109
|
dir = parent
|
|
109
110
|
}
|
|
110
111
|
}
|
|
111
112
|
|
|
113
|
+
/** `dir` 是否等于 `bound` 或在其子树内(分隔符归一比较)。 */
|
|
114
|
+
function isInsideOrEqual(dir, bound) {
|
|
115
|
+
const norm = (p) => p.replaceAll("\\", "/").replace(/\/+$/, "")
|
|
116
|
+
const d = norm(dir)
|
|
117
|
+
const b = norm(bound)
|
|
118
|
+
return d === b || d.startsWith(b + "/")
|
|
119
|
+
}
|
|
120
|
+
|
|
112
121
|
/** **项目根解析的直读形**(判定单点——设计 `MANIFEST.md` §2.2 `projectRootView` 契约行 / §2.5
|
|
113
122
|
* 「单项目解析律与四处会话级缺省收口」条):`ok`(唯一解析:归属命中 ∥ 发现 `self` / `unique`)/
|
|
114
123
|
* `workspace`(锚自身无档 ∧ 下辖 **≥2** 项目 ⇒ `root` null + 下辖项目全列**按名排序**——工作区
|
package/manifest-schema.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* manifest-schema.mjs — **校验 ∕ 默认值族**出档(2026-09-29 structure-split-2 · 台账 #620):自 `manifest.mjs`
|
|
3
3
|
* 全量迁出——`isValidDocRootValue`(docRoot 子键值形态判据)·
|
|
4
|
-
* `DEFAULT_MANIFEST
|
|
4
|
+
* `DEFAULT_MANIFEST`(默认五键)· `MANIFEST_SCHEMA`(判据单源)· 元素层谓词一件 · `validateManifest` ·
|
|
5
5
|
* `fillDefaults`;**结构拆分零语义**(面不变 ∕ 判据不变,只换宿主档;切点 ∕ 缝单源 =
|
|
6
6
|
* 批档 `docs/batches/2026-09-29-structure-split-2.md` §2.2-C)。
|
|
7
7
|
*
|
|
@@ -25,21 +25,8 @@ export function isValidDocRootValue(value) {
|
|
|
25
25
|
return value.every((p) => typeof p === "string" && p.trim() !== "")
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
/**
|
|
29
|
-
*
|
|
30
|
-
* 消费面两处:产品引擎 `doc-check/anchors.mjs`(报告态逐元素弃用)∥ `doc-check/index.mjs`
|
|
31
|
-
* (gate 面 `configError` 拒,fail-closed)。
|
|
32
|
-
* @param {unknown} value 解析根元素
|
|
33
|
-
* @returns {string|null}
|
|
34
|
-
*/
|
|
35
|
-
export function anchorRootError(value) {
|
|
36
|
-
if (typeof value !== "string" || value.trim() === "") return "元素须为非空字符串"
|
|
37
|
-
if (/^(?:[A-Za-z]:[\\/]|[\\/])/.test(value)) return "元素须为相对项目根的路径(不得为绝对路径)"
|
|
38
|
-
if (value.split(/[\\/]/).includes("..")) return "元素不得含 `..` 越界段"
|
|
39
|
-
return null
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** 默认八键(架构 §2.3 E1 五键 + 三族声明键——KD-M1-31)——整档初始化的写源与缺键 fallback 的补源。 */
|
|
28
|
+
/** 默认五键——整档初始化的写源与缺键 fallback 的补源;每一键描述的都是这个用户项目自身
|
|
29
|
+
* (阶段 / 文档层 / 代码段 / 索引),产品内部布局与工具参数不住这里。 */
|
|
43
30
|
export const DEFAULT_MANIFEST = Object.freeze({
|
|
44
31
|
version: 1,
|
|
45
32
|
phase: "initial-dev",
|
|
@@ -50,18 +37,9 @@ export const DEFAULT_MANIFEST = Object.freeze({
|
|
|
50
37
|
modules: "docs/design/modules",
|
|
51
38
|
batches: "docs/batches",
|
|
52
39
|
}),
|
|
53
|
-
promptsLanding: "thincoder-core/prompts",
|
|
54
|
-
checkConfig: Object.freeze({
|
|
55
|
-
scanDirs: Object.freeze(["docs"]),
|
|
56
|
-
lineWidth: 300,
|
|
57
|
-
widthExemptZones: Object.freeze([]),
|
|
58
|
-
anchors: Object.freeze({ domain: "docs", exclude: Object.freeze(["_archive", "batches"]), roots: Object.freeze([]) }),
|
|
59
|
-
exemptions: Object.freeze([]),
|
|
60
|
-
}),
|
|
61
40
|
// 项目声明三族(2026-09-27 声明载体唯一化批并入——键名 / 形态逐字承前;来源档已退役)。
|
|
62
41
|
codePaths: Object.freeze(["src"]),
|
|
63
42
|
index: Object.freeze({ codeExtensions: Object.freeze([]), docExtensions: Object.freeze([]), excludePaths: Object.freeze([]), publicRepos: Object.freeze([]) }),
|
|
64
|
-
advisor: Object.freeze({ docMap: "", standardsDoc: "" }),
|
|
65
43
|
})
|
|
66
44
|
|
|
67
45
|
/** 校验判据(模块设计 §2.2)——枚举 / 键存在(fallback 用);判据单源(KD-M1-4)。 */
|
|
@@ -73,9 +51,7 @@ export const MANIFEST_SCHEMA = Object.freeze({
|
|
|
73
51
|
keys: Object.freeze(Object.keys(DEFAULT_MANIFEST)),
|
|
74
52
|
nestedKeys: Object.freeze({
|
|
75
53
|
docRoot: Object.freeze(Object.keys(DEFAULT_MANIFEST.docRoot)),
|
|
76
|
-
checkConfig: Object.freeze(Object.keys(DEFAULT_MANIFEST.checkConfig)),
|
|
77
54
|
index: Object.freeze(Object.keys(DEFAULT_MANIFEST.index)),
|
|
78
|
-
advisor: Object.freeze(Object.keys(DEFAULT_MANIFEST.advisor)),
|
|
79
55
|
}),
|
|
80
56
|
})
|
|
81
57
|
|
|
@@ -89,7 +65,7 @@ function isNonEmptyStringArray(v) {
|
|
|
89
65
|
* 形状校验(模块设计 §2.2)——枚举 / version 数值恒做;**纯函数、零 fs**(原 `{ cwd }`
|
|
90
66
|
* 指针分支已随字段整链裁撤——KD-M1-5 墓志);不落盘。键存在性入 missingKeys(非拒):
|
|
91
67
|
* 缺键 = 便利 fallback(补默认值),不是错(与整档缺失两分——KD-M1-2)。
|
|
92
|
-
* 未知键(#802 / KD-M1-36):顶层 +
|
|
68
|
+
* 未知键(#802 / KD-M1-36):顶层 + 两嵌套层产 `unknownKeys`(非拒——ok 定性零变;产线恰 =
|
|
93
69
|
* fillDefaults 丢弃面;嵌套键用点形路径——同 missingKeys 口径)。
|
|
94
70
|
* @param {object} obj 待校验 manifest 对象
|
|
95
71
|
* @returns {{ok:boolean, errors:string[], missingKeys:string[], unknownKeys:string[]}}
|
|
@@ -100,7 +76,7 @@ export function validateManifest(obj) {
|
|
|
100
76
|
if (obj === null || typeof obj !== "object" || Array.isArray(obj)) {
|
|
101
77
|
return { ok: false, errors: ["manifest 顶层必须是 JSON 对象"], missingKeys, unknownKeys: [] }
|
|
102
78
|
}
|
|
103
|
-
// #802(KD-M1-36——未知键可见化):顶层 +
|
|
79
|
+
// #802(KD-M1-36——未知键可见化):顶层 + 两嵌套层逐键对照(非拒——ok 定性零变;嵌套键点形)。
|
|
104
80
|
const unknownKeys = []
|
|
105
81
|
for (const key of Object.keys(obj)) if (!MANIFEST_SCHEMA.keys.includes(key)) unknownKeys.push(key)
|
|
106
82
|
for (const [nested, subkeys] of Object.entries(MANIFEST_SCHEMA.nestedKeys)) {
|
|
@@ -129,7 +105,6 @@ export function validateManifest(obj) {
|
|
|
129
105
|
// `index.excludePaths`(§6.14 面①):**数组层** fail-closed(非数组 ⇒ 拒);元素层宽容——空 ∕ 非串
|
|
130
106
|
// 元素由归一剔除(L-①-1 夹具 `["./openclaw/","refs",""]` ⇒ `["openclaw","refs"]`);`index.publicRepos`(§6.15)同款。
|
|
131
107
|
const strArrErr = (key, v) => { if (!isNonEmptyStringArray(v)) errors.push(`${key} 值形态非法:${JSON.stringify(v)}(须为非空字符串数组——KD-M1-32)`) }
|
|
132
|
-
const strErr = (key, v) => { if (typeof v !== "string") errors.push(`${key} 值形态非法:${JSON.stringify(v)}(须为字符串——KD-M1-32)`) }
|
|
133
108
|
const objErr = (key, v) => (v === null || typeof v !== "object" || Array.isArray(v)
|
|
134
109
|
? `${key} 值形态非法:${JSON.stringify(v)}(须为对象——KD-M1-32)` : null)
|
|
135
110
|
if (obj.codePaths !== undefined) strArrErr("codePaths", obj.codePaths)
|
|
@@ -144,9 +119,6 @@ export function validateManifest(obj) {
|
|
|
144
119
|
errors.push(`index.publicRepos 值形态非法:${JSON.stringify(obj.index.publicRepos)}(须为数组——非数组 fail-closed;空 ∕ 非串元素由归一剔除)`)
|
|
145
120
|
}
|
|
146
121
|
}
|
|
147
|
-
const advBad = obj.advisor === undefined ? null : objErr("advisor", obj.advisor)
|
|
148
|
-
if (advBad) errors.push(advBad)
|
|
149
|
-
else if (obj.advisor !== undefined) for (const k of ["docMap", "standardsDoc"]) if (obj.advisor[k] !== undefined) strErr(`advisor.${k}`, obj.advisor[k])
|
|
150
122
|
if (obj.phase !== undefined && !MANIFEST_SCHEMA.enum.phase.includes(obj.phase)) {
|
|
151
123
|
errors.push(`phase 取值非法:"${obj.phase}"(允许:${MANIFEST_SCHEMA.enum.phase.join(" | ")})`)
|
|
152
124
|
}
|
|
@@ -156,7 +128,7 @@ export function validateManifest(obj) {
|
|
|
156
128
|
return { ok: errors.length === 0, errors, missingKeys, unknownKeys }
|
|
157
129
|
}
|
|
158
130
|
|
|
159
|
-
/** 缺键补默认值(module 设计 §2.2 管线):顶层缺键补默认、嵌套键(docRoot/
|
|
131
|
+
/** 缺键补默认值(module 设计 §2.2 管线):顶层缺键补默认、嵌套键(docRoot/index)
|
|
160
132
|
* 子键补默认(与整键缺同语义);不覆写既有值。深拷贝默认源(structuredClone)——冻结常量永不外泄引用。
|
|
161
133
|
* 三族键的非对象值**原样透传**(不洗白——再校验拒,KD-M1-32)。 */
|
|
162
134
|
export function fillDefaults(obj) {
|
|
@@ -167,7 +139,7 @@ export function fillDefaults(obj) {
|
|
|
167
139
|
if (def !== null && typeof def === "object" && !Array.isArray(def)) {
|
|
168
140
|
const src = obj[key]
|
|
169
141
|
if (src === null || typeof src !== "object" || Array.isArray(src)) {
|
|
170
|
-
if (key === "index"
|
|
142
|
+
if (key === "index") out[key] = src // 形态错 → 透传(再校验拒);docRoot 保持默认
|
|
171
143
|
continue
|
|
172
144
|
}
|
|
173
145
|
for (const sub of Object.keys(def)) {
|
package/manifest.mjs
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
* 同档同键集去重、读到零未知键清该档 memo)——非拒定性零变。
|
|
12
12
|
* - validateManifest(obj) → { ok, errors, missingKeys, unknownKeys }:枚举 / version 数值 /
|
|
13
13
|
* docRoot 子键值形态(串 | 数组 | null(本面无——KD-M1-37),F7)——**纯函数、零 fs**;不落盘。
|
|
14
|
-
* -
|
|
15
|
-
*
|
|
14
|
+
* - 两族声明键(`codePaths` / `index.{codeExtensions,docExtensions,publicRepos}`——KD-M1-31 / M1-32):
|
|
15
|
+
* 缺键补默认;形态错 = 档非法(fail-closed,与 docRoot 子键同款);
|
|
16
16
|
* 读向 = `declaration.mjs`(2026-10-01 自 `conventions.mjs` 迁出——经其转口可达)经 `readManifest` /
|
|
17
17
|
* `manifestFilePath` 投影(单向——KD-M1-33);`index.publicRepos` 绝对根集 = `declaredPublicRoots`(§6.15)。
|
|
18
18
|
* - isValidDocRootValue(value) / docRootPaths(value, cwd)(F7 判据单源 KD-M1-8):值形态谓词
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
* 落盘);仅 writer === 'main' 落盘(fail-closed 缺省拒)。
|
|
36
36
|
*
|
|
37
37
|
* N1 零依赖:仅 node:fs / node:path + 标准库 JSON.parse——无第三方解析器、无 node:sqlite。
|
|
38
|
-
* N3 可迁移:本模块不硬编码任何本仓路径(docRoot
|
|
38
|
+
* N3 可迁移:本模块不硬编码任何本仓路径(docRoot 由被开发项目声明;机检参数不在 manifest 面——住引擎内置常量)。
|
|
39
39
|
*/
|
|
40
40
|
import { readFileSync, writeFileSync } from "node:fs"
|
|
41
41
|
import { join, resolve } from "node:path"
|
|
@@ -135,13 +135,13 @@ export function manifestFilePath(cwd) {
|
|
|
135
135
|
/** #802(KD-M1-36)未知键告警去重 memo:档路径 → 上次观测键集(进程内);读到零未知键即清该条。 */
|
|
136
136
|
const unknownKeysSeen = new Map()
|
|
137
137
|
|
|
138
|
-
/**
|
|
138
|
+
/** 未知键诊断(#802 · 2026-10-23 改):**不上界面**——只写 logEvent('manifest:unknown-keys');同档同键集恰一次。 */
|
|
139
139
|
function warnUnknownKeys(file, unknownKeys) {
|
|
140
140
|
if (unknownKeys.length === 0) { unknownKeysSeen.delete(file); return }
|
|
141
141
|
const sig = JSON.stringify([...unknownKeys].sort())
|
|
142
142
|
if (unknownKeysSeen.get(file) === sig) return
|
|
143
143
|
unknownKeysSeen.set(file, sig)
|
|
144
|
-
|
|
144
|
+
// 不上界面(2026-10-23 用户裁定:换别的项目也不能弹,不许骚扰用户);只写诊断日志,`manifest read` 时才出现在结果里
|
|
145
145
|
logEvent("manifest:unknown-keys", { path: file, keys: unknownKeys.join(",") })
|
|
146
146
|
}
|
|
147
147
|
|
|
@@ -264,20 +264,32 @@ function workspaceProjectMessage(cwd, candidates) {
|
|
|
264
264
|
* 不替被开发项目创建目录(§1.4 边界)——cwd 不存在时写失败自然上抛。
|
|
265
265
|
* @returns {object} 传入的 manifest(校验通过后原样落盘)
|
|
266
266
|
*/
|
|
267
|
+
/** 已退役的顶层键(FR42 八键 → 五键):写入时静默丢弃——它们不是用户拼错,是产品自己退场的键,
|
|
268
|
+
* 留着只会让读面反复报「未知键」骚扰用户(2026-10-23 用户裁定「死键你删掉啊」)。 */
|
|
269
|
+
const RETIRED_KEYS = ["promptsLanding", "checkConfig", "advisor"]
|
|
270
|
+
|
|
271
|
+
/** 返回去掉退役键的新对象(不改入参)。 */
|
|
272
|
+
function stripRetiredKeys(obj) {
|
|
273
|
+
const out = { ...obj }
|
|
274
|
+
for (const k of RETIRED_KEYS) delete out[k]
|
|
275
|
+
return out
|
|
276
|
+
}
|
|
277
|
+
|
|
267
278
|
export function writeManifest(cwd, manifest, { writer = "subagent" } = {}) {
|
|
268
279
|
if (writer !== "main") {
|
|
269
280
|
throw new Error("writeManifest 拒绝:非主 agent 无写权(writer 须为 \"main\",fail-closed 缺省拒)")
|
|
270
281
|
}
|
|
271
|
-
const
|
|
282
|
+
const cleaned = stripRetiredKeys(manifest)
|
|
283
|
+
const validated = validateManifest(cleaned)
|
|
272
284
|
if (!validated.ok) {
|
|
273
285
|
throw new Error(`writeManifest 拒绝:manifest 校验不过——${validated.errors.join(";")}`)
|
|
274
286
|
}
|
|
275
|
-
writeFileSync(manifestFilePath(cwd), JSON.stringify(
|
|
276
|
-
return
|
|
287
|
+
writeFileSync(manifestFilePath(cwd), JSON.stringify(cleaned, null, 2) + "\n")
|
|
288
|
+
return cleaned
|
|
277
289
|
}
|
|
278
290
|
|
|
279
291
|
/**
|
|
280
|
-
* 初始化 =
|
|
292
|
+
* 初始化 = 写默认五键档(模块设计 §2.2):只提供机制,不包交互问答(问答归壳面)。
|
|
281
293
|
* 落盘走同一写门(内部 writeManifest(cwd, DEFAULT_MANIFEST, { writer }))——与 AC-M1-5
|
|
282
294
|
* 同一闸,无第二条写路径(KD-M1-4 / 评审 #12)。
|
|
283
295
|
* @returns {object} 写入的默认 manifest
|
package/memory/code-search.mjs
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
import { embed, embedTolerant, cosine, toBlob, fromBlob } from "../embedding.mjs"
|
|
9
9
|
import { scanVectors, createTopK } from "./scan.mjs"
|
|
10
10
|
import { normalizeOrigin } from "./origin.mjs"
|
|
11
|
-
import { declaredPublicRoots
|
|
11
|
+
import { declaredPublicRoots } from "../conventions.mjs"
|
|
12
|
+
import { createReadGate } from "./read-gate.mjs" // 面⑥∪面⑦ 读面准入单源(#1372 抽件——两档原各持一份逐字相同的 rowHidden)
|
|
12
13
|
import { buildFtsQuery, ensureEmbeddings, EMBED_TEXT_MAX_LEN } from "./core.mjs"
|
|
13
14
|
import { safeSliceUTF16 } from "../text-budget.mjs"
|
|
14
15
|
import { DESC } from "../tools/shared.mjs" // #15 描述外置:文本单点 = tool-docs/code_search.md
|
|
@@ -35,19 +36,9 @@ export async function codeSearch(memory, query, { limit = 5 } = {}) {
|
|
|
35
36
|
const origins = searchOrigins(memory)
|
|
36
37
|
const ftsOriginFilter = origins.length === 1 ? `AND c.origin = ?` : origins.length > 1 ? `AND c.origin IN (${origins.map(() => "?").join(", ")})` : ""
|
|
37
38
|
const originParams = origins
|
|
38
|
-
// §6.14 读面准入过滤(FTS 列表建成后 ∥ 向量 top-K push
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
const declCache = new Map()
|
|
42
|
-
const scopeCache = new Map()
|
|
43
|
-
const rowHidden = (origin, path) => {
|
|
44
|
-
const d = declCache.get(origin) ?? loadProjectDeclaration(origin)
|
|
45
|
-
declCache.set(origin, d)
|
|
46
|
-
if (isExcludedRelPath(path, d, origin)) return true
|
|
47
|
-
let scope = scopeCache.get(origin)
|
|
48
|
-
if (scope === undefined) { scope = declaredIndexScope(origin); scopeCache.set(origin, scope) }
|
|
49
|
-
return !isInDeclaredScope(path, scope, origin)
|
|
50
|
-
}
|
|
39
|
+
// §6.14 读面准入过滤(FTS 列表建成后 ∥ 向量 top-K push 前)——判据与逐 origin 预载单源 = `./read-gate.mjs`;
|
|
40
|
+
// 存量行不删(收尾保护位);窗口欠额接受。
|
|
41
|
+
const { rowHidden } = createReadGate()
|
|
51
42
|
const ftsNeedOrigin = origins.length !== 1 // 单 origin 形 SQL 零改;多 origin ∥ 无过滤形补 `origin` 列(行 origin 随行可得)
|
|
52
43
|
|
|
53
44
|
const ftsList = ftsQuery ? memory.db.prepare(`
|
package/memory/docs.mjs
CHANGED
|
@@ -12,7 +12,8 @@ import { _upsertDocFile, yieldTick } from "./code-index.mjs"
|
|
|
12
12
|
import { markIndexedCommit, listProjectFiles, indexExtensions, declaredRootEntries } from "./code-sync.mjs"
|
|
13
13
|
import { searchOrigins } from "./code-search.mjs"
|
|
14
14
|
import { createRowBudget, rowCountOfPath, sweepStaleRows } from "./sync-tail.mjs"
|
|
15
|
-
import { loadProjectDeclaration,
|
|
15
|
+
import { loadProjectDeclaration, declaredIndexScope } from "../conventions.mjs" // 面① 谓词经 `decl` 传 `sweepStaleRows`
|
|
16
|
+
import { createReadGate } from "./read-gate.mjs" // 面⑥∪面⑦ 读面准入单源(#1372 抽件——两档原各持一份逐字相同的 rowHidden)
|
|
16
17
|
import { logEvent } from "../log.mjs"
|
|
17
18
|
import { safeSliceUTF16 } from "../text-budget.mjs"
|
|
18
19
|
import { DESC } from "../tools/shared.mjs" // #15 描述外置:文本单点 = tool-docs/*.md(DESC 单解析面,缺档抛错语义不变)
|
|
@@ -117,19 +118,9 @@ export async function docSearch(memory, query, { limit = 5, origins: originsOver
|
|
|
117
118
|
const origins = originsOverride ?? searchOrigins(memory)
|
|
118
119
|
const ftsOriginFilter = origins.length === 1 ? `AND d.origin = ?` : origins.length > 1 ? `AND d.origin IN (${origins.map(() => "?").join(", ")})` : ""
|
|
119
120
|
const originParams = origins
|
|
120
|
-
// §6.14 读面准入过滤(FTS 列表建成后 ∥ 向量 top-K push
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
const declCache = new Map()
|
|
124
|
-
const scopeCache = new Map()
|
|
125
|
-
const rowHidden = (origin, path) => {
|
|
126
|
-
const d = declCache.get(origin) ?? loadProjectDeclaration(origin)
|
|
127
|
-
declCache.set(origin, d)
|
|
128
|
-
if (isExcludedRelPath(path, d, origin)) return true
|
|
129
|
-
let scope = scopeCache.get(origin)
|
|
130
|
-
if (scope === undefined) { scope = declaredIndexScope(origin); scopeCache.set(origin, scope) }
|
|
131
|
-
return !isInDeclaredScope(path, scope, origin)
|
|
132
|
-
}
|
|
121
|
+
// §6.14 读面准入过滤(FTS 列表建成后 ∥ 向量 top-K push 前)——判据与逐 origin 预载单源 = `./read-gate.mjs`;
|
|
122
|
+
// 存量行不删(收尾保护位);窗口欠额接受。
|
|
123
|
+
const { rowHidden } = createReadGate()
|
|
133
124
|
const ftsNeedOrigin = origins.length !== 1 // 单 origin 形 SQL 零改;多 origin ∥ 无过滤形补 `origin` 列(行 origin 随行可得)
|
|
134
125
|
|
|
135
126
|
const ftsList = ftsQuery ? memory.db.prepare(`
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* memory/read-gate.mjs — 读面准入闸(§6.14 面①∪面⑥∪面⑦;2026-10-19 批 K · 台账 #1372 抽件)。
|
|
3
|
+
*
|
|
4
|
+
* 判据单源:`memory/docs.mjs` ∥ `memory/code-search.mjs` 原先各持一份**逐字相同**的 `rowHidden`
|
|
5
|
+
* (排除谓词 ∪ 声明范围门)——两份判据迟早会不一致,而它们判的是同一件事:这行要不要藏。
|
|
6
|
+
*
|
|
7
|
+
* 判据序 = `isExcludedRelPath(path, decl, origin)` ∪ `!isInDeclaredScope(path, scope, origin)`。
|
|
8
|
+
* 两缓存(项目声明 ∥ 声明索引范围)为工厂闭包私有 Map,逐 origin 预载一次;
|
|
9
|
+
* 实例粒度 = 每次检索调用建一次 ⇒ 与抽出前的函数作用域缓存同寿命,行为零变。
|
|
10
|
+
*/
|
|
11
|
+
import { loadProjectDeclaration, isExcludedRelPath, declaredIndexScope, isInDeclaredScope } from "../conventions.mjs"
|
|
12
|
+
|
|
13
|
+
/** 建一个读面准入闸:返回 `{ rowHidden(origin, path) → boolean }`(true = 该行不进结果集)。 */
|
|
14
|
+
export function createReadGate() {
|
|
15
|
+
const declCache = new Map() // origin → 项目声明(`loadProjectDeclaration` 回执)
|
|
16
|
+
const scopeCache = new Map() // origin → 声明索引范围(`declaredIndexScope` 回执)
|
|
17
|
+
const rowHidden = (origin, path) => {
|
|
18
|
+
const d = declCache.get(origin) ?? loadProjectDeclaration(origin)
|
|
19
|
+
declCache.set(origin, d)
|
|
20
|
+
if (isExcludedRelPath(path, d, origin)) return true
|
|
21
|
+
let scope = scopeCache.get(origin)
|
|
22
|
+
if (scope === undefined) { scope = declaredIndexScope(origin); scopeCache.set(origin, scope) }
|
|
23
|
+
return !isInDeclaredScope(path, scope, origin)
|
|
24
|
+
}
|
|
25
|
+
return { rowHidden }
|
|
26
|
+
}
|