@thincoder/core 0.10.8 → 0.10.10

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.
Files changed (85) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/advisor/messages.mjs +4 -5
  3. package/advisor/project-context.mjs +69 -44
  4. package/agent/family-tools.mjs +11 -9
  5. package/agent/helpers.mjs +12 -6
  6. package/agent/post-turn.mjs +34 -0
  7. package/agent/run-stages.mjs +43 -9
  8. package/agent/run-start.mjs +1 -0
  9. package/agent/setup.mjs +10 -2
  10. package/agent/turn-loop.mjs +23 -13
  11. package/agent-tools/audit-block.mjs +4 -2
  12. package/agent-tools/batch-gate.mjs +197 -0
  13. package/agent-tools/batch-lifecycle.mjs +216 -36
  14. package/agent-tools/batch-skeleton.mjs +87 -0
  15. package/agent-tools/batch.mjs +11 -3
  16. package/agent-tools/checkpoint.mjs +11 -6
  17. package/agent-tools/context.mjs +28 -5
  18. package/agent-tools/design-slots.mjs +7 -33
  19. package/agent-tools/doc-check.mjs +50 -7
  20. package/agent-tools/manifest.mjs +44 -12
  21. package/agent-tools/spawn-gates.mjs +100 -41
  22. package/agent-tools/ssh-session.mjs +108 -61
  23. package/agent-tools/ssh-transport.mjs +17 -4
  24. package/agent-tools/ssh-vault.mjs +291 -0
  25. package/agent-tools/subagent-spawn.mjs +52 -30
  26. package/agent-tools/subagent.mjs +14 -8
  27. package/agent-tools/timer.mjs +2 -2
  28. package/agent-tools.mjs +1 -1
  29. package/config-defaults.mjs +92 -0
  30. package/config-normalize.mjs +109 -0
  31. package/config.mjs +88 -197
  32. package/declaration.mjs +5 -12
  33. package/doc-check/anchors.mjs +68 -34
  34. package/doc-check/index.mjs +143 -32
  35. package/doc-check/version.mjs +84 -0
  36. package/doc-check/width.mjs +10 -54
  37. package/i18n.mjs +7 -0
  38. package/ledger-cmd.mjs +314 -61
  39. package/ledger-db.mjs +22 -10
  40. package/ledger-migrate.mjs +15 -9
  41. package/ledger-tools.mjs +115 -69
  42. package/ledger.mjs +13 -10
  43. package/log.mjs +2 -2
  44. package/manifest-schema.mjs +7 -21
  45. package/manifest.mjs +4 -4
  46. package/memory/code-search.mjs +5 -14
  47. package/memory/docs.mjs +5 -14
  48. package/memory/read-gate.mjs +26 -0
  49. package/package.json +1 -1
  50. package/permission.mjs +7 -1
  51. package/prompts/advisor-design.md +95 -44
  52. package/prompts/advisor-round1.md +75 -27
  53. package/prompts/advisor-round2.md +68 -31
  54. package/prompts/advisor-round3.md +77 -33
  55. package/prompts/common.md +283 -113
  56. package/prompts/consult-base.md +16 -9
  57. package/prompts/discipline-engineering.md +213 -83
  58. package/prompts/discipline-normal.md +137 -85
  59. package/prompts/persona-coder.md +8 -6
  60. package/prompts/persona-eng-coder.md +42 -21
  61. package/prompts/persona-eng-designer.md +101 -43
  62. package/prompts/persona-engineering.md +376 -127
  63. package/prompts/persona-explore.md +3 -3
  64. package/prompts/persona-normal.md +29 -21
  65. package/prompts/persona-plan.md +7 -7
  66. package/prompts/subagent-base.md +4 -4
  67. package/provider/anthropic.mjs +4 -2
  68. package/provider/core.mjs +4 -2
  69. package/provider/google.mjs +5 -1
  70. package/provider/responses-request.mjs +3 -2
  71. package/session-lifecycle.mjs +6 -6
  72. package/team.mjs +2 -1
  73. package/token-window.mjs +1 -1
  74. package/tool-docs/batch.md +1 -1
  75. package/tool-docs/doc_check.md +47 -12
  76. package/tool-docs/fetch.md +3 -2
  77. package/tool-docs/ledger.md +7 -5
  78. package/tool-docs/manifest-read.md +8 -0
  79. package/tool-docs/manifest.md +9 -8
  80. package/tool-docs/ssh.md +23 -6
  81. package/tool-docs/subagent.md +7 -12
  82. package/tool-docs/websearch.md +3 -1
  83. package/tools/ssh.mjs +24 -11
  84. package/tools/web-deepseek.mjs +172 -0
  85. package/tools/web.mjs +29 -4
package/ledger-tools.mjs CHANGED
@@ -10,7 +10,7 @@
10
10
  * 接线:族出口 = `ledger.mjs`(re-export——消费面 agent/family-tools.mjs;旧读二装配面已清零);
11
11
  * 消费侧一律动态 import(`ledger-db.mjs` 静态 import node:sqlite ⇒ W8 契约②)。
12
12
  */
13
- import { ledgerAdd, ledgerClose, ledgerCount, ledgerQuery, ledgerUpdate } from "./ledger-cmd.mjs"
13
+ import { ledgerAdd, ledgerClose, ledgerCloseBatch, ledgerCount, ledgerQuery, ledgerUpdate, ledgerUpdateBatch } from "./ledger-cmd.mjs"
14
14
  import { resolveLedgerRoot } from "./ledger-db.mjs"
15
15
  import { DESC } from "./tools/shared.mjs" // #15 描述外置:文本单点 = tool-docs/ledger.md(DESC 单解析面)
16
16
 
@@ -37,37 +37,47 @@ const argPreview = (v) => {
37
37
  /** 枚举取值域文本(P3 / P5 文案同源)。 */
38
38
  const enumText = (values) => `{${values.join(", ")}}`
39
39
 
40
- /** P3 后缀(声明派生):枚举字段带取值域 / 非枚举必填串带「非空字符串」/ 数字字段仅「必填」。 */
41
- const requiredSuffix = (spec) => (spec.enum ? `;取值 ∈ ${enumText(spec.enum)}` : spec.type === "string" ? ";非空字符串" : "")
40
+ /** P3 后缀(声明派生):枚举字段带取值域 / 非枚举必填串带「非空字符串」/ 数字字段仅「必填」
41
+ * (`domain` = 按形条件判据给出的取值域——`whenPresent.enum` 优先于基本形)。 */
42
+ const requiredSuffix = (spec, domain) => (domain ? `;取值 ∈ ${enumText(domain)}` : spec.type === "string" ? ";非空字符串" : "")
43
+
44
+ /** 值非空字段(P6 字段级:空串 / 全空白 ⇒ 拒——声明面不表达值级判据,与 `title` 同源一处)。 */
45
+ const NON_EMPTY_FIELDS = ["title", "resume_when"]
42
46
 
43
47
  /**
44
- * 工具层参数守卫(设计档 §3.2 · Gitee #IKIQGK / 台账 #472):判序 P1–P6,文案逐字 = §3.2 模板;
45
- * 消费工具自身 `parameters` 声明派生(`required` / `type` / `enum`——枚举 / 必填 / 类型不在守卫内复写);
46
- * 只判不改(零缺省填充 / 零类型转换 / 零文本改写),返回入参对象(唯一归一 = 判序① 的缺省 / `null`
47
- * ⇒ `{}`)。非法 ⇒ throw——判于核函数之前(零写、零库动作)。
48
+ * 工具层参数守卫(设计档 §3.2 · Gitee #IKIQGK / 台账 #472):判序 P1–P7,文案逐字 = §3.2 模板;
49
+ * 消费工具自身声明派生(`required` / `type` / `enum` / `oneOfRequired` / `whenPresent`——枚举 / 必填 /
50
+ * 类型 / 组判据不在守卫内复写);只判不改(零缺省填充 / 零类型转换 / 零文本改写),返回入参对象
51
+ * (唯一归一 = 判序① 的缺省 / `null` ⇒ `{}`)。非法 ⇒ throw——判于核函数之前(零写、零库动作)。
52
+ * 按形条件判据(`whenPresent`):`when` 字段在本笔入参**在场**(非 `null`)⇒ 该条生效(`required` 按
53
+ * P3 判、`enum` 按 P5 判并取代基本形取值域);不在场 ⇒ 本条不生效、按基本形判。
48
54
  */
49
55
  function assertToolArgs(tool, args) {
50
56
  const name = tool.name
51
57
  const decl = tool.parameters ?? {}
52
58
  const props = decl.properties ?? {}
53
59
  const fields = Object.keys(props)
60
+ // 组判据两键居声明条目顶层(不入 `parameters`——自定义键不随声明交模型 / provider)
61
+ const conditional = tool.whenPresent ?? []
54
62
  // P1:入参缺省 / `null` ⇒ 视作 `{}`;非对象(数组 / 标量)⇒ 拒
55
63
  if (args == null) args = {}
56
64
  else if (typeof args !== "object" || Array.isArray(args)) throw new Error(`${name}:参数须为对象(收到 ${argPreview(args)})`)
57
65
  // P2:未知键 ⇒ 拒(判序先于缺参——typo 键先给可用参数集)
58
66
  const unknown = Object.keys(args).filter((k) => !Object.hasOwn(props, k))
59
67
  if (unknown.length > 0) throw new Error(`${name}:未知参数:${unknown.join(" / ")}(可用参数 = ${fields.join(" / ")})`)
60
- // P3–P6:逐字段(声明序)——缺失(必填;显式 `null` 同判)→ 类型 / 枚举 → `title` 空串 / 全空白
68
+ // P3–P6:逐字段(声明序)——缺失(必填;显式 `null` 同判)→ 类型 / 枚举 → 值非空
61
69
  for (const field of fields) {
62
70
  const spec = props[field]
63
71
  const value = args[field]
64
- const required = (decl.required ?? []).includes(field)
72
+ const rule = conditional.find((c) => c.field === field && args[c.when] != null)
73
+ const required = rule?.required ?? (decl.required ?? []).includes(field)
74
+ const domain = rule?.enum ?? spec.enum
65
75
  if (value === undefined || value === null) {
66
- if (required) throw new Error(`${name}:${field} 缺失(必填${requiredSuffix(spec)})`)
76
+ if (required) throw new Error(`${name}:${field} 缺失(必填${requiredSuffix(spec, domain)})`)
67
77
  continue // 可空字段 `null` ⇒ 放行(≡ 略去——与 `patch.x ?? 行值` 既有语义同源)
68
78
  }
69
- if (spec.enum) {
70
- if (!spec.enum.includes(value)) throw new Error(`${name}:${field} 非法:${argPreview(value)}(取值 ∈ ${enumText(spec.enum)})`)
79
+ if (domain) {
80
+ if (!domain.includes(value)) throw new Error(`${name}:${field} 非法:${argPreview(value)}(取值 ∈ ${enumText(domain)})`)
71
81
  } else if (spec.type === "string" && typeof value !== "string") {
72
82
  throw new Error(`${name}:${field} 非法:${argPreview(value)}(应为字符串)`)
73
83
  } else if (spec.type === "number" && typeof value !== "number") {
@@ -75,10 +85,16 @@ function assertToolArgs(tool, args) {
75
85
  } else if (spec.type === "boolean" && typeof value !== "boolean") {
76
86
  throw new Error(`${name}:${field} 非法:${argPreview(value)}(应为布尔)`)
77
87
  }
78
- if (field === "title" && typeof value === "string" && value.trim() === "") {
88
+ if (NON_EMPTY_FIELDS.includes(field) && typeof value === "string" && value.trim() === "") {
79
89
  throw new Error(`${name}:${field} 为空(${required ? "必填;" : ""}非空字符串)`)
80
90
  }
81
91
  }
92
+ // P7:组判据(声明派生 `oneOfRequired`)——组内恰携其一 ⇒ 过;同携 / 双缺 ⇒ 拒
93
+ for (const group of tool.oneOfRequired ?? []) {
94
+ if (group.filter((f) => args[f] != null).length === 1) continue
95
+ const received = Object.fromEntries(group.map((f) => [f, args[f] ?? null]))
96
+ throw new Error(`${name}:${group.join(" ∥ ")} 须恰携其一(收到 ${argPreview(received)})`)
97
+ }
82
98
  return args
83
99
  }
84
100
 
@@ -91,68 +107,90 @@ const READ_ACTIONS = ["query", "count"]
91
107
  /** cwd 参数描述(五前身声明同文——文本单点)。 */
92
108
  const CWD_DESC = "本次动作的目标路径——项目根由它推导:沿祖先链最近的项目目录(带 PROJECT-MANIFEST.json 或已有台账库);容器目录下收敛到子项目。台账按项目根关联存储于用户数据目录(工作树外、不进 git)。相对路径按当前工作目录解析;查/写别的项目请给绝对路径。缺省 = 当前会话锚"
93
109
 
94
- /** 各 action 声明条目(= 前身旧工具 `parameters` 逐字;伪工具对象守卫 + 弃用壳共用单源)。 */
110
+ /** 各 action 声明条目(= 前身旧工具 `parameters` 逐字;伪工具对象守卫 + 弃用壳共用单源)。
111
+ * `parameters` 内的键 = 交模型 / provider 的那一份声明;**组判据两键(`oneOfRequired` / `whenPresent`)
112
+ * 居本条条目顶层、不入 `parameters`**——自定义键不随声明外泄(provider 面零风险),守卫仍只读声明。 */
95
113
  const ACTION_SPECS = {
96
114
  add: {
97
- type: "object",
98
- required: ["kind", "title"],
99
- properties: {
100
- cwd: { type: "string", description: CWD_DESC },
101
- kind: { type: "string", enum: ["requirement", "tech_todo"], description: "类别" },
102
- title: { type: "string", description: "条目标题" },
103
- board: { type: "string", description: "归属板块(需求档文档名,可空)" },
104
- req_doc: { type: "string", description: "需求档指针(可空)" },
105
- task_book: { type: "string", description: "任务书指针(可空;在途 / 待核销必填——咬合 CHECK)" },
106
- evidence: { type: "string", description: "最小证据行(可空)" },
107
- trigger: { type: "string", enum: ["归批", "条件", "认账不排期"], description: "技术待办触发(可空)" },
115
+ parameters: {
116
+ type: "object",
117
+ required: ["kind", "title"],
118
+ properties: {
119
+ cwd: { type: "string", description: CWD_DESC },
120
+ kind: { type: "string", enum: ["requirement", "tech_todo"], description: "类别" },
121
+ title: { type: "string", description: "条目标题" },
122
+ board: { type: "string", description: "归属板块(需求档文档名,可空)" },
123
+ req_doc: { type: "string", description: "需求档指针(可空)" },
124
+ task_book: { type: "string", description: "任务书指针(可空;在途 / 待核销必填——咬合 CHECK)" },
125
+ evidence: { type: "string", description: "最小证据行(可空)" },
126
+ trigger: { type: "string", enum: ["归批", "条件", "认账不排期"], description: "技术待办触发(可空)" },
127
+ resume_when: { type: "string", description: "复启条件句(可空;`trigger=条件` 行的一句话条件 + 可选锚 `@YYYY-MM-DD` ∥ `@事件件名`)——值非空:空串 / 全空白 ⇒ 拒" },
128
+ },
129
+ additionalProperties: false,
108
130
  },
109
- additionalProperties: false,
110
131
  },
111
132
  update: {
112
- type: "object",
113
- required: ["id"],
114
- properties: {
115
- cwd: { type: "string", description: CWD_DESC },
116
- id: { type: "number", description: "条目 id" },
117
- status: { type: "string", enum: ["待讨论", "待设计", "在途", "待核销", "已核销", "已废弃"], description: "目标状态(六态之一,缺省 = 不变)" },
118
- title: { type: "string", description: "标题(缺省 = 不变)" },
119
- board: { type: "string", description: "归属板块(缺省 = 不变)" },
120
- req_doc: { type: "string", description: "需求档指针(缺省 = 不变)" },
121
- task_book: { type: "string", description: "任务书指针(缺省 = 不变)" },
122
- evidence: { type: "string", description: "证据(缺省 = 不变;传入 = 追加——旧值非空 ⇒ 拼 `旧|新`(「|」零空格)、旧空直写;新值全空白 ⇒ 拒;覆盖 = 与 `evidenceReplace: true` 同传)" },
123
- evidenceReplace: { type: "boolean", description: "覆盖旗(布尔):与 evidence 同传 = 整段替换(旧文零保留);缺省 / false = 追加;旗独传(无新文)⇒ 拒" },
124
- trigger: { type: "string", enum: ["归批", "条件", "认账不排期"], description: "触发(缺省 = 不变)" },
125
- executor: { type: "string", description: "执行者 sessionId(可选——接手改写归属用;缺省 = 按状态迁移语义:进入待设计 / 在途自动写本会话;出在途 / 撤回清空)" },
133
+ parameters: {
134
+ type: "object",
135
+ properties: {
136
+ cwd: { type: "string", description: CWD_DESC },
137
+ id: { type: "number", description: "条目 id(与 batch 恰携其一)" },
138
+ batch: { type: "string", description: "批档指针(与 id 恰携其一)——批级批量:一次结算该批的行;携 status ⇒ 逐行按迁移表重判,不携 status ⇒ 纯字段补丁(status 零写)" },
139
+ status: { type: "string", enum: ["待讨论", "待设计", "在途", "待核销", "已核销", "已废弃"], description: "目标状态(六态之一,缺省 = 不变)" },
140
+ title: { type: "string", description: "标题(缺省 = 不变)" },
141
+ board: { type: "string", description: "归属板块(缺省 = 不变)" },
142
+ req_doc: { type: "string", description: "需求档指针(缺省 = 不变)" },
143
+ task_book: { type: "string", description: "任务书指针(缺省 = 不变)" },
144
+ evidence: { type: "string", description: "证据(缺省 = 不变;传入 = 追加——旧值非空 ⇒ 拼 `旧|新`(「|」零空格)、旧空直写;新值全空白 ⇒ 拒;覆盖 = 与 `evidenceReplace: true` 同传)" },
145
+ evidenceReplace: { type: "boolean", description: "覆盖旗(布尔):与 evidence 同传 = 整段替换(旧文零保留);缺省 / false = 追加;旗独传(无新文)⇒ 拒" },
146
+ trigger: { type: "string", enum: ["归批", "条件", "认账不排期"], description: "触发(缺省 = 不变)" },
147
+ resume_when: { type: "string", description: "复启条件句(缺省 = 不变;值非空——空串 / 全空白 ⇒ 拒)" },
148
+ executor: { type: "string", description: "执行者 sessionId(可选——接手改写归属用;缺省 = 按状态迁移语义:进入待设计 / 在途自动写本会话;出在途 / 撤回清空)" },
149
+ },
150
+ additionalProperties: false,
126
151
  },
127
- additionalProperties: false,
152
+ oneOfRequired: [["id", "batch"]],
128
153
  },
129
154
  close: {
130
- type: "object",
131
- required: ["id", "status"],
132
- properties: {
133
- cwd: { type: "string", description: CWD_DESC },
134
- id: { type: "number", description: "条目 id" },
135
- status: { type: "string", enum: ["已核销", "已废弃"], description: "目标态(勾销 / 追认核销 / 撤回)" },
136
- evidence: { type: "string", description: "最小证据行(可选;传入 = 追加缺省——旧值非空 ⇒ 拼 `旧|新`(「|」零空格)、旧空直写;新值全空白 ⇒ 拒;覆盖 = 与 `evidenceReplace: true` 同传。追认核销面判结果值须非空:缺 / 全空白 ⇒ 拒)" },
137
- evidenceReplace: { type: "boolean", description: "覆盖旗(布尔):与 evidence 同传 = 整段替换(旧文零保留);缺省 / false = 追加;旗独传(无新文)⇒ 拒" },
155
+ parameters: {
156
+ type: "object",
157
+ required: ["status"],
158
+ properties: {
159
+ cwd: { type: "string", description: CWD_DESC },
160
+ id: { type: "number", description: "条目 id(与 batch 恰携其一)" },
161
+ batch: { type: "string", description: "批档指针(与 id 恰携其一)——批级批量结算:只销已到 待核销 的行;待讨论 / 待设计 点名跳过(追认核销不入批量面)" },
162
+ status: { type: "string", enum: ["已核销", "已废弃"], description: "目标态(勾销 / 追认核销 / 撤回)——携 batch 时可不给(缺省 = 勾销单值),给了只能是 已核销" },
163
+ evidence: { type: "string", description: "最小证据行(可选;传入 = 追加缺省——旧值非空 ⇒ 拼 `旧|新`(「|」零空格)、旧空直写;新值全空白 ⇒ 拒;覆盖 = 与 `evidenceReplace: true` 同传。追认核销面判结果值须非空:缺 / 全空白 ⇒ 拒)" },
164
+ evidenceReplace: { type: "boolean", description: "覆盖旗(布尔):与 evidence 同传 = 整段替换(旧文零保留);缺省 / false = 追加;旗独传(无新文)⇒ 拒" },
165
+ },
166
+ additionalProperties: false,
138
167
  },
139
- additionalProperties: false,
168
+ oneOfRequired: [["id", "batch"]],
169
+ whenPresent: [{ when: "batch", field: "status", required: false, enum: ["已核销"] }],
140
170
  },
141
171
  query: {
142
- type: "object",
143
- properties: {
144
- cwd: { type: "string", description: CWD_DESC },
145
- status: { type: "string", enum: ["待讨论", "待设计", "在途", "待核销", "已核销", "已废弃"], description: "按状态过滤(六态之一,缺省 = 不过滤)" },
146
- kind: { type: "string", enum: ["requirement", "tech_todo"], description: "按类别过滤" },
147
- board: { type: "string", description: "按归属板块过滤(需求档文档名)" },
148
- trigger: { type: "string", enum: ["归批", "条件", "认账不排期"], description: "按技术待办触发过滤(三枚举,缺省 = 不过滤——无触发(NULL)不过滤)" },
172
+ parameters: {
173
+ type: "object",
174
+ properties: {
175
+ cwd: { type: "string", description: CWD_DESC },
176
+ status: { type: "string", enum: ["待讨论", "待设计", "在途", "待核销", "已核销", "已废弃"], description: "按状态过滤(六态之一,缺省 = 不过滤)" },
177
+ kind: { type: "string", enum: ["requirement", "tech_todo"], description: "按类别过滤" },
178
+ board: { type: "string", description: "按归属板块过滤(需求档文档名)" },
179
+ trigger: { type: "string", enum: ["归批", "条件", "认账不排期"], description: "按技术待办触发过滤(三枚举,缺省 = 不过滤——无触发(NULL)不过滤)" },
180
+ task_book: { type: "string", description: "按任务书指针过滤(原值等值——批→行反查;缺省 = 不过滤)" },
181
+ },
182
+ additionalProperties: false,
149
183
  },
150
- additionalProperties: false,
151
184
  },
152
185
  count: {
153
- type: "object",
154
- properties: { cwd: { type: "string", description: CWD_DESC } },
155
- additionalProperties: false,
186
+ parameters: {
187
+ type: "object",
188
+ properties: {
189
+ cwd: { type: "string", description: CWD_DESC },
190
+ task_book: { type: "string", description: "按任务书指针过滤(原值等值)" },
191
+ },
192
+ additionalProperties: false,
193
+ },
156
194
  },
157
195
  }
158
196
 
@@ -171,8 +209,10 @@ const UNION_PARAMETERS = {
171
209
  evidence: { type: "string", description: "最小证据行(add 可空;update / close 传入 = 追加缺省——旧空直写、旧非空拼 `旧|新`(「|」零空格)、新值全空白 ⇒ 拒;覆盖 = 与 `evidenceReplace: true` 同传;update 缺省 = 不变 / close 缺省 = 判行值;追认核销须非空)" },
172
210
  evidenceReplace: { type: "boolean", description: "覆盖旗(布尔):与 evidence 同传 = 整段替换;缺省 / false = 追加;旗独传(无新文)⇒ 拒(update / close)" },
173
211
  trigger: { type: "string", enum: ["归批", "条件", "认账不排期"], description: "技术待办触发(add / update 可空 / query 过滤)" },
174
- id: { type: "number", description: "条目 id(update / close 必填)" },
175
- status: { type: "string", enum: ["待讨论", "待设计", "在途", "待核销", "已核销", "已废弃"], description: "状态(close 必填 ∈ {已核销, 已废弃};update 迁移 / query 过滤——六态之一)" },
212
+ resume_when: { type: "string", description: "复启条件句(add / update 可空;值非空——空串 / 全空白 ⇒ 拒;`trigger=条件` 行的一句话条件 + 可选锚 `@YYYY-MM-DD` ∥ `@事件件名`)" },
213
+ id: { type: "number", description: "条目 id(update / close:与 batch 恰携其一)" },
214
+ batch: { type: "string", description: "批档指针(update / close:与 id 恰携其一)——批级批量:一次结算该批的行(update 携 status ⇒ 逐行迁移表重判;不携 status ⇒ 纯字段补丁;close 只销 待核销 行、status 可空)" },
215
+ status: { type: "string", enum: ["待讨论", "待设计", "在途", "待核销", "已核销", "已废弃"], description: "状态(单行 close 必填 ∈ {已核销, 已废弃};close 携 batch 时可空、给了只能是 已核销;update 迁移 / query 过滤——六态之一)" },
176
216
  executor: { type: "string", description: "执行者 sessionId(update 可选——接手改写归属;缺省 = 按状态迁移语义:进入待设计 / 在途自动写本会话;出在途 / 撤回清空)" },
177
217
  },
178
218
  additionalProperties: false,
@@ -210,8 +250,10 @@ function makeLedgerTool(actions, { readonly = false, writeFace = false } = {}) {
210
250
  // ② 入口级 action 判(P3 缺失 / P5 非法——消费 action;取值域 = 本装配面枚举)
211
251
  const { action, ...rest0 } = args
212
252
  assertToolArgs(entryTool, { action })
213
- // ③ 余键喂该 action 伪工具对象(声明源 = 前身工具 parameters 逐字——`action` 键不入 P2 域)
214
- const rest = assertToolArgs({ name: `ledger(${action})`, parameters: ACTION_SPECS[action] }, rest0)
253
+ // ③ 余键喂该 action 伪工具对象(声明源 = 前身工具 parameters 逐字——`action` 键不入 P2 域;
254
+ // 组判据两键在条目顶层,与 `parameters` 同送守卫)
255
+ const spec = ACTION_SPECS[action]
256
+ const rest = assertToolArgs({ name: `ledger(${action})`, ...spec }, rest0)
215
257
  // ④ 路由既有核函数(包装细节携自原工具 execute 逐字保留:cwd 缺省 / JSON 缩进 / getSessionId 动态 import)
216
258
  switch (action) {
217
259
  case "add": {
@@ -221,7 +263,7 @@ function makeLedgerTool(actions, { readonly = false, writeFace = false } = {}) {
221
263
  return JSON.stringify({ id, status: "待讨论", ...reportedRoot(target) })
222
264
  }
223
265
  case "update": {
224
- const { cwd, id, ...patch } = rest
266
+ const { cwd, id, batch, ...patch } = rest
225
267
  const target = cwd ?? cwdOf(ctx)
226
268
  let executorSessionId
227
269
  if (patch.executor == null) {
@@ -229,17 +271,21 @@ function makeLedgerTool(actions, { readonly = false, writeFace = false } = {}) {
229
271
  const { getSessionId } = await import("./session-slots.mjs")
230
272
  executorSessionId = getSessionId()
231
273
  }
274
+ // 批级批量(§3.3)——`batch` 与 `id` 恰携其一(守卫 P7 已先裁)
275
+ if (batch != null) return JSON.stringify({ ...ledgerUpdateBatch({ cwd: target, batch, patch, executorSessionId }), ...reportedRoot(target) })
232
276
  return JSON.stringify({ ...ledgerUpdate({ cwd: target, id, patch, executorSessionId }), ...reportedRoot(target) })
233
277
  }
234
278
  case "close": {
235
279
  const target = rest.cwd ?? cwdOf(ctx)
280
+ // 批级批量结算(§3.3——勾销径)
281
+ if (rest.batch != null) return JSON.stringify({ ...ledgerCloseBatch({ cwd: target, batch: rest.batch, evidence: rest.evidence, evidenceReplace: rest.evidenceReplace }), ...reportedRoot(target) })
236
282
  return JSON.stringify({ ...ledgerClose({ cwd: target, id: rest.id, status: rest.status, evidence: rest.evidence, evidenceReplace: rest.evidenceReplace }), ...reportedRoot(target) })
237
283
  }
238
284
  case "query":
239
- return JSON.stringify(ledgerQuery({ cwd: rest.cwd ?? cwdOf(ctx), status: rest.status, kind: rest.kind, board: rest.board, trigger: rest.trigger }), null, 2)
285
+ return JSON.stringify(ledgerQuery({ cwd: rest.cwd ?? cwdOf(ctx), status: rest.status, kind: rest.kind, board: rest.board, trigger: rest.trigger, task_book: rest.task_book }), null, 2)
240
286
  case "count": {
241
287
  const target = rest.cwd ?? cwdOf(ctx)
242
- return JSON.stringify({ count: ledgerCount({ cwd: target }), ...reportedRoot(target) })
288
+ return JSON.stringify({ ...ledgerCount({ cwd: target, task_book: rest.task_book }), ...reportedRoot(target) })
243
289
  }
244
290
  default:
245
291
  // 不可达(② 枚举守门已先拒)——fail-loud 兜底(防未来变体增量 drift)
@@ -262,7 +308,7 @@ const deprecatedLine = (old, action) => `${old} 已退役(本名不再装配
262
308
  const deprecatedShell = (old, action, readonly) => ({
263
309
  name: old,
264
310
  description: deprecatedLine(old, action),
265
- parameters: ACTION_SPECS[action],
311
+ parameters: ACTION_SPECS[action].parameters,
266
312
  readonly,
267
313
  async execute() { throw new Error(deprecatedLine(old, action)) },
268
314
  })
package/ledger.mjs CHANGED
@@ -65,9 +65,11 @@ export function discoverFamily(anchor) {
65
65
  }
66
66
 
67
67
  /** scan 组装(SQLite 行集 + 计数单源 → scan 对象)——形状契约(设计档 §2.2):pool/tech/aged/
68
- * agedKeys/agedTitles/thresholdReached/boards/actionable/root/name/ledger。行龄 = 时间戳
68
+ * agedKeys/agedTitles/overdue/thresholdReached/boards/actionable/root/name/ledger。行龄 = 时间戳
69
69
  * (updated_at ?? created_at 距今 > days;未知不计——零假阳降级,同 v1 行龄未知口径)。
70
70
  * `now`/`days` = 注入面(确定性用例——替代 v1 ageOf git 注入面)。
71
+ * **`days` 只作用于展示面 `aged`**(本函数局部阈值);`overdue`(出口态)恒按核常量 `AGING_DAYS`
72
+ * (`overdueOf` 单源)——故 `days` 调小时 `aged` 会先于 `overdue` 涨(两数口径不同,非矛盾)。
71
73
  * **根推导**(设计档 §3.8):`root` / `name` / `ledger` = `resolveLedgerRoot(anchor)` 推导所得项目根(非锚
72
74
  * ——容器 ∥ 工作区锚取收敛序之根)——与行集读取同一单源;无根 ⇒ `ledger` = `null`(不落兜底根——
73
75
  * 该态下 `root` / `name` 回落锚、行集为空,且该 scan 不进族枚举(`discoverFamily` 容器支只列已注册子目录))。
@@ -77,7 +79,7 @@ export function buildScan({ cwd, days = AGING_DAYS, now = Date.now() } = {}) {
77
79
  const anchor = resolve(cwd)
78
80
  const derived = resolveLedgerRoot(anchor)
79
81
  const root = derived ? derived.root : anchor
80
- const rows = ledgerQuery({ cwd: anchor })
82
+ const rows = ledgerQuery({ cwd: anchor, now }) // `now` 透传(两读数同时钟——`overdue` 的行键由核同一 `now` 算)
81
83
  const pending = rows.filter((r) => PENDING_STATUSES.includes(r.status))
82
84
  const poolEntries = pending.filter((r) => r.kind === "requirement")
83
85
  const techEntries = pending.filter((r) => r.kind === "tech_todo")
@@ -98,6 +100,7 @@ export function buildScan({ cwd, days = AGING_DAYS, now = Date.now() } = {}) {
98
100
  return {
99
101
  name: basename(root), root, ledger: derived ? ledgerDbPath(root) : null,
100
102
  pool: poolEntries.length, tech: techEntries.length, aged: aged.length,
103
+ overdue: pending.filter((r) => r.overdue != null).length, // 待处置(§3.7——机判单源 = 行集 `overdue` 键,行值与 `now` 均同源)
101
104
  agedKeys: aged.map((r) => normalizeEntry(r.title)), agedTitles: aged.map((r) => entryTitle(r.title)),
102
105
  thresholdReached, boards, actionable: aged.length > 0 || thresholdReached,
103
106
  inflightExecutors,
@@ -113,8 +116,8 @@ export function formatMarker(scan) {
113
116
  * `root` = `family.current.root` 的 scan(命中但不在 `scans`——不可读 ⇒ 空范围,不掺兄弟);
114
117
  * `current` 缺席(容器根锚)⇒ 范围 = `scans` 全体(族内已读项目——构建期不可读已跳过)。
115
118
  * 空范围 ⇒ `{ marker: null, warn: false }`(不落 `0·0`);范围非空 ⇒ `marker` = `formatMarker`
116
- * 逐字(两池分列求和——F1);`warn` = 范围内任一项 `aged>0 ∨ deadExecutors>0`(端零重算)。
117
- * 消费面 = 核拍面(`ledger-surface.mjs`)∥ VSC item——端零自算单源。 */
119
+ * 逐字(两池分列求和——F1);`warn` = 范围内任一项 `overdue>0 ∨ deadExecutors>0`(§3.7——`aged ⊂
120
+ * overdue` ⇒ 警示色不缩;端零重算)。消费面 = 核拍面(`ledger-surface.mjs`)∥ VSC item。 */
118
121
  export function scopeMarkerOf(scans, family) {
119
122
  const list = Array.isArray(scans) ? scans : []
120
123
  const currentRoot = family?.current?.root ?? null
@@ -126,15 +129,15 @@ export function scopeMarkerOf(scans, family) {
126
129
  for (const s of range) {
127
130
  pool += s.pool
128
131
  tech += s.tech
129
- if (s.aged > 0 || s.deadExecutors > 0) warn = true
132
+ if (s.overdue > 0 || s.deadExecutors > 0) warn = true
130
133
  }
131
134
  return { marker: formatMarker({ pool, tech }), warn }
132
135
  }
133
136
 
134
- /** L2 明细行(逐字——`(老化 <n>)` 恒显;阈值达成加 ` — 可开批`;判活尾段 = executorTail 三态——
135
- * 在途 executor 缺席时尾段空串,既有逐字断言零破)。 */
137
+ /** L2 明细行(逐字——`(老化 <n> · 待处置 <m>)` 两数恒显(含 0——§3.7 出口面恒显位扩);
138
+ * 阈值达成加 ` — 可开批`;判活尾段 = executorTail 三态——在途 executor 缺席时尾段空串)。 */
136
139
  export function formatDetailLine(scan) {
137
- return `台账 ${scan.name}:需求池 ${scan.pool} · 技术待办 ${scan.tech}(老化 ${scan.aged})${scan.thresholdReached ? " — 可开批" : ""}${executorTail(scan)}`
140
+ return `台账 ${scan.name}:需求池 ${scan.pool} · 技术待办 ${scan.tech}(老化 ${scan.aged} · 待处置 ${scan.overdue})${scan.thresholdReached ? " — 可开批" : ""}${executorTail(scan)}`
138
141
  }
139
142
 
140
143
  /** 明细行集 = {current} ∪ {可动作项目}(发现序——current 在前,其余按目录名升序;同项去重)。 */
@@ -148,8 +151,8 @@ export function detailScans(scans, current) {
148
151
  // ── re-export(拆分件接口——命令面接线 = 动态 import 本档,KD-M2-3;`AGING_DAYS` 常量单源 = `ledger-db.mjs`
149
152
  // ——2026-10-07 批 ledger-tool 自本档迁入,公共面零变;`ledgerChildren` / `MAX_SIBLING_SCAN` / `resolveLedgerRoot`
150
153
  // ——2026-10-16 批 ledger-root-derivation 随迁 / 新增,公共面形零改) ──
151
- export { AGING_DAYS, ALLOWED_MIGRATIONS, MAX_SIBLING_SCAN, ledgerChildren, ledgerDbPath, ledgerKey, ledgerDirPath, openLedger, PENDING_STATUSES, resolveLedgerRoot, _setLedgerDirForTest, _resetLedgerDirForTest, ensureExecutorColumn } from "./ledger-db.mjs"
152
- export { ledgerAdd, ledgerClose, ledgerCount, ledgerQuery, ledgerUpdate } from "./ledger-cmd.mjs"
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"
155
+ export { ledgerAdd, ledgerBatchRows, ledgerClose, ledgerCloseBatch, ledgerCount, ledgerEvidenceOversize, ledgerQuery, ledgerReanchorBatch, ledgerUpdate, ledgerUpdateBatch, overdueOf } from "./ledger-cmd.mjs"
153
156
  export { ledgerTool, ledgerReadTool, ledgerAddTool, ledgerCloseTool, ledgerCountTool, ledgerQueryTool, ledgerUpdateTool } from "./ledger-tools.mjs"
154
157
  export { runLedgerAudit, runLedgerMigrate } from "./ledger-migrate.mjs"
155
158
  export { resolveExecutorStates, executorTail, _setExecutorProbeTtlForTest } from "./ledger-executors.mjs"
package/log.mjs CHANGED
@@ -18,8 +18,8 @@
18
18
  * → 整个字段丢弃;
19
19
  * ② 内容只扫密钥形态(sk-xxx / Bearer xxx / key=… / token 值形等)→ 截断到形态之前
20
20
  * ——宁可丢信息不漏密钥。
21
- * token 值形(`uuid:epoch`——2026-10-14 design-token-recovery 批并入):design-slots
22
- * reveal 结果经 `tool:done` head 落盘时不落值(与 batch.mjs 凭据第三形同判据)。
21
+ * token 值形(`uuid:epoch`——2026-10-14 批并入):工具结果经 `tool:done` head 落盘
22
+ * 时在形态前截断、不落值(与 batch.mjs 凭据第三形同判据)。
23
23
  * 工具事件不记 args;URL 不入事件(llm/tool 事件从不携带 URL;err 文本经形态扫描)。
24
24
  * - 摘要截断(B 方案,2026-09-03 用户裁定):截断处带 "…" 标记(截后仍 ≤上限)。
25
25
  * - 测试隔离:node --test 进程(NODE_TEST_CONTEXT)默认不写盘——防测试事件污染真实
@@ -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`(默认八键)· `MANIFEST_SCHEMA`(判据单源)· 元素层谓词一件 · `validateManifest` ·
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,7 +25,8 @@ export function isValidDocRootValue(value) {
25
25
  return value.every((p) => typeof p === "string" && p.trim() !== "")
26
26
  }
27
27
 
28
- /** 默认八键(架构 §2.3 E1 五键 + 三族声明键——KD-M1-31)——整档初始化的写源与缺键 fallback 的补源。 */
28
+ /** 默认五键——整档初始化的写源与缺键 fallback 的补源;每一键描述的都是这个用户项目自身
29
+ * (阶段 / 文档层 / 代码段 / 索引),产品内部布局与工具参数不住这里。 */
29
30
  export const DEFAULT_MANIFEST = Object.freeze({
30
31
  version: 1,
31
32
  phase: "initial-dev",
@@ -36,18 +37,9 @@ export const DEFAULT_MANIFEST = Object.freeze({
36
37
  modules: "docs/design/modules",
37
38
  batches: "docs/batches",
38
39
  }),
39
- promptsLanding: "thincoder-core/prompts",
40
- checkConfig: Object.freeze({
41
- scanDirs: Object.freeze(["docs"]),
42
- lineWidth: 300,
43
- widthExemptZones: Object.freeze([]),
44
- anchors: Object.freeze({ domain: "docs", exclude: Object.freeze(["_archive", "batches"]) }),
45
- exemptions: Object.freeze([]),
46
- }),
47
40
  // 项目声明三族(2026-09-27 声明载体唯一化批并入——键名 / 形态逐字承前;来源档已退役)。
48
41
  codePaths: Object.freeze(["src"]),
49
42
  index: Object.freeze({ codeExtensions: Object.freeze([]), docExtensions: Object.freeze([]), excludePaths: Object.freeze([]), publicRepos: Object.freeze([]) }),
50
- advisor: Object.freeze({ docMap: "", standardsDoc: "" }),
51
43
  })
52
44
 
53
45
  /** 校验判据(模块设计 §2.2)——枚举 / 键存在(fallback 用);判据单源(KD-M1-4)。 */
@@ -59,9 +51,7 @@ export const MANIFEST_SCHEMA = Object.freeze({
59
51
  keys: Object.freeze(Object.keys(DEFAULT_MANIFEST)),
60
52
  nestedKeys: Object.freeze({
61
53
  docRoot: Object.freeze(Object.keys(DEFAULT_MANIFEST.docRoot)),
62
- checkConfig: Object.freeze(Object.keys(DEFAULT_MANIFEST.checkConfig)),
63
54
  index: Object.freeze(Object.keys(DEFAULT_MANIFEST.index)),
64
- advisor: Object.freeze(Object.keys(DEFAULT_MANIFEST.advisor)),
65
55
  }),
66
56
  })
67
57
 
@@ -75,7 +65,7 @@ function isNonEmptyStringArray(v) {
75
65
  * 形状校验(模块设计 §2.2)——枚举 / version 数值恒做;**纯函数、零 fs**(原 `{ cwd }`
76
66
  * 指针分支已随字段整链裁撤——KD-M1-5 墓志);不落盘。键存在性入 missingKeys(非拒):
77
67
  * 缺键 = 便利 fallback(补默认值),不是错(与整档缺失两分——KD-M1-2)。
78
- * 未知键(#802 / KD-M1-36):顶层 + 四嵌套层产 `unknownKeys`(非拒——ok 定性零变;产线恰 =
68
+ * 未知键(#802 / KD-M1-36):顶层 + 两嵌套层产 `unknownKeys`(非拒——ok 定性零变;产线恰 =
79
69
  * fillDefaults 丢弃面;嵌套键用点形路径——同 missingKeys 口径)。
80
70
  * @param {object} obj 待校验 manifest 对象
81
71
  * @returns {{ok:boolean, errors:string[], missingKeys:string[], unknownKeys:string[]}}
@@ -86,7 +76,7 @@ export function validateManifest(obj) {
86
76
  if (obj === null || typeof obj !== "object" || Array.isArray(obj)) {
87
77
  return { ok: false, errors: ["manifest 顶层必须是 JSON 对象"], missingKeys, unknownKeys: [] }
88
78
  }
89
- // #802(KD-M1-36——未知键可见化):顶层 + 四嵌套层逐键对照(非拒——ok 定性零变;嵌套键点形)。
79
+ // #802(KD-M1-36——未知键可见化):顶层 + 两嵌套层逐键对照(非拒——ok 定性零变;嵌套键点形)。
90
80
  const unknownKeys = []
91
81
  for (const key of Object.keys(obj)) if (!MANIFEST_SCHEMA.keys.includes(key)) unknownKeys.push(key)
92
82
  for (const [nested, subkeys] of Object.entries(MANIFEST_SCHEMA.nestedKeys)) {
@@ -115,7 +105,6 @@ export function validateManifest(obj) {
115
105
  // `index.excludePaths`(§6.14 面①):**数组层** fail-closed(非数组 ⇒ 拒);元素层宽容——空 ∕ 非串
116
106
  // 元素由归一剔除(L-①-1 夹具 `["./openclaw/","refs",""]` ⇒ `["openclaw","refs"]`);`index.publicRepos`(§6.15)同款。
117
107
  const strArrErr = (key, v) => { if (!isNonEmptyStringArray(v)) errors.push(`${key} 值形态非法:${JSON.stringify(v)}(须为非空字符串数组——KD-M1-32)`) }
118
- const strErr = (key, v) => { if (typeof v !== "string") errors.push(`${key} 值形态非法:${JSON.stringify(v)}(须为字符串——KD-M1-32)`) }
119
108
  const objErr = (key, v) => (v === null || typeof v !== "object" || Array.isArray(v)
120
109
  ? `${key} 值形态非法:${JSON.stringify(v)}(须为对象——KD-M1-32)` : null)
121
110
  if (obj.codePaths !== undefined) strArrErr("codePaths", obj.codePaths)
@@ -130,9 +119,6 @@ export function validateManifest(obj) {
130
119
  errors.push(`index.publicRepos 值形态非法:${JSON.stringify(obj.index.publicRepos)}(须为数组——非数组 fail-closed;空 ∕ 非串元素由归一剔除)`)
131
120
  }
132
121
  }
133
- const advBad = obj.advisor === undefined ? null : objErr("advisor", obj.advisor)
134
- if (advBad) errors.push(advBad)
135
- else if (obj.advisor !== undefined) for (const k of ["docMap", "standardsDoc"]) if (obj.advisor[k] !== undefined) strErr(`advisor.${k}`, obj.advisor[k])
136
122
  if (obj.phase !== undefined && !MANIFEST_SCHEMA.enum.phase.includes(obj.phase)) {
137
123
  errors.push(`phase 取值非法:"${obj.phase}"(允许:${MANIFEST_SCHEMA.enum.phase.join(" | ")})`)
138
124
  }
@@ -142,7 +128,7 @@ export function validateManifest(obj) {
142
128
  return { ok: errors.length === 0, errors, missingKeys, unknownKeys }
143
129
  }
144
130
 
145
- /** 缺键补默认值(module 设计 §2.2 管线):顶层缺键补默认、嵌套键(docRoot/checkConfig/index/advisor)
131
+ /** 缺键补默认值(module 设计 §2.2 管线):顶层缺键补默认、嵌套键(docRoot/index)
146
132
  * 子键补默认(与整键缺同语义);不覆写既有值。深拷贝默认源(structuredClone)——冻结常量永不外泄引用。
147
133
  * 三族键的非对象值**原样透传**(不洗白——再校验拒,KD-M1-32)。 */
148
134
  export function fillDefaults(obj) {
@@ -153,7 +139,7 @@ export function fillDefaults(obj) {
153
139
  if (def !== null && typeof def === "object" && !Array.isArray(def)) {
154
140
  const src = obj[key]
155
141
  if (src === null || typeof src !== "object" || Array.isArray(src)) {
156
- if (key === "index" || key === "advisor") out[key] = src // 形态错 → 透传(再校验拒);docRoot/checkConfig 保持默认
142
+ if (key === "index") out[key] = src // 形态错 → 透传(再校验拒);docRoot 保持默认
157
143
  continue
158
144
  }
159
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
- * - 三族声明键(`codePaths` / `index.{codeExtensions,docExtensions,publicRepos}` / `advisor.{docMap,standardsDoc}`——
15
- * KD-M1-31 / M1-32):缺键补默认;形态错 = 档非法(fail-closed,与 docRoot 子键同款);
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 / checkConfig 由被开发项目声明)。
38
+ * N3 可迁移:本模块不硬编码任何本仓路径(docRoot 由被开发项目声明;机检参数不在 manifest 面——住引擎内置常量)。
39
39
  */
40
40
  import { readFileSync, writeFileSync } from "node:fs"
41
41
  import { join, resolve } from "node:path"
@@ -277,7 +277,7 @@ export function writeManifest(cwd, manifest, { writer = "subagent" } = {}) {
277
277
  }
278
278
 
279
279
  /**
280
- * 初始化 = 写默认八键档(模块设计 §2.2):只提供机制,不包交互问答(问答归壳面)。
280
+ * 初始化 = 写默认五键档(模块设计 §2.2):只提供机制,不包交互问答(问答归壳面)。
281
281
  * 落盘走同一写门(内部 writeManifest(cwd, DEFAULT_MANIFEST, { writer }))——与 AC-M1-5
282
282
  * 同一闸,无第二条写路径(KD-M1-4 / 评审 #12)。
283
283
  * @returns {object} 写入的默认 manifest
@@ -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, loadProjectDeclaration, isExcludedRelPath, declaredIndexScope, isInDeclaredScope } from "../conventions.mjs"
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
- // 行 origin 为 `base`)∪ 声明范围门(面⑦——`isInDeclaredScope(path, declaredIndexScope(origin), origin)`,
40
- // 逐 origin 预载一次)⇒「批次档 ∥ 范围外路径从未入索引」等价检索;存量行不删(收尾保护位);窗口欠额接受。
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, isExcludedRelPath, declaredIndexScope, isInDeclaredScope } from "../conventions.mjs" // 面① 谓词经 `decl` 传 `sweepStaleRows`;面⑥∪面⑦ 读面准入本档直调
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
- // 行 origin 为 `base`)∪ 声明范围门(面⑦——`isInDeclaredScope(path, declaredIndexScope(origin), origin)`,
122
- // 逐 origin 预载一次)⇒「批次档 ∥ 范围外路径从未入索引」等价检索;存量行不删(收尾保护位);窗口欠额接受。
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(`