@thincoder/core 0.10.12 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/advisor/messages.mjs +4 -2
  3. package/advisor/notice.mjs +1 -1
  4. package/advisor/project-context.mjs +121 -11
  5. package/agent/completion.mjs +13 -1
  6. package/agent/digest-account.mjs +73 -46
  7. package/agent/dispatch-run.mjs +1 -1
  8. package/agent/dispatch.mjs +29 -21
  9. package/agent/family-tools.mjs +9 -5
  10. package/agent/helpers.mjs +1 -94
  11. package/agent/offload.mjs +88 -0
  12. package/agent/record-results.mjs +1 -6
  13. package/agent/run-stages.mjs +74 -49
  14. package/agent/run-start.mjs +8 -8
  15. package/agent/setup.mjs +6 -11
  16. package/agent/suspension.mjs +1 -1
  17. package/agent/turn-loop.mjs +35 -3
  18. package/agent/write-face.mjs +99 -0
  19. package/agent/write-gate.mjs +354 -5
  20. package/agent-tools/advisor.mjs +127 -11
  21. package/agent-tools/async-settle.mjs +2 -2
  22. package/agent-tools/bash-async.mjs +8 -2
  23. package/agent-tools/batch-closeout.mjs +206 -0
  24. package/agent-tools/batch-gate.mjs +287 -73
  25. package/agent-tools/batch-ledger-bind.mjs +183 -0
  26. package/agent-tools/batch-ledger-link.mjs +73 -0
  27. package/agent-tools/batch-lifecycle.mjs +113 -354
  28. package/agent-tools/batch-paths.mjs +45 -40
  29. package/agent-tools/batch-read.mjs +1 -1
  30. package/agent-tools/batch-reopen.mjs +142 -0
  31. package/agent-tools/batch-skeleton.mjs +283 -80
  32. package/agent-tools/batch-unit-test-gate.mjs +107 -0
  33. package/agent-tools/batch.mjs +243 -50
  34. package/agent-tools/context.mjs +6 -3
  35. package/agent-tools/design-slots.mjs +24 -24
  36. package/agent-tools/design-token.mjs +96 -15
  37. package/agent-tools/doc-check.mjs +18 -2
  38. package/agent-tools/eng.mjs +1 -1
  39. package/agent-tools/escalate-async.mjs +3 -3
  40. package/agent-tools/manifest.mjs +8 -5
  41. package/agent-tools/read-history.mjs +5 -5
  42. package/agent-tools/review-facts.mjs +31 -13
  43. package/agent-tools/spawn-gates.mjs +10 -14
  44. package/agent-tools/ssh-session.mjs +120 -111
  45. package/agent-tools/ssh-transfer.mjs +210 -0
  46. package/agent-tools/ssh-transport.mjs +277 -46
  47. package/agent-tools/subagent-actions-query.mjs +1 -1
  48. package/agent-tools/subagent-actions.mjs +1 -1
  49. package/agent-tools/subagent-async.mjs +3 -2
  50. package/agent-tools/subagent-scheduler.mjs +43 -9
  51. package/agent-tools/subagent-spawn.mjs +68 -9
  52. package/agent-tools/subagent.mjs +16 -14
  53. package/agent.mjs +1 -0
  54. package/client-identity.mjs +70 -0
  55. package/config-presets.mjs +5 -3
  56. package/config.mjs +19 -13
  57. package/context.mjs +39 -8
  58. package/declaration.mjs +2 -2
  59. package/doc-check/anchors.mjs +128 -28
  60. package/doc-check/index.mjs +93 -27
  61. package/doc-check/line-refs.mjs +112 -0
  62. package/doc-check/paragraph.mjs +24 -8
  63. package/embedding.mjs +2 -0
  64. package/explore-distill.mjs +1 -1
  65. package/generate-title.mjs +3 -2
  66. package/i18n.mjs +1 -1
  67. package/index-discover.mjs +4 -1
  68. package/ledger-close-proof.mjs +62 -0
  69. package/ledger-cmd.mjs +79 -194
  70. package/ledger-db.mjs +29 -28
  71. package/ledger-migrate.mjs +4 -4
  72. package/ledger-read.mjs +10 -8
  73. package/ledger-surface.mjs +1 -1
  74. package/ledger-tools.mjs +10 -10
  75. package/ledger-variant-notice.mjs +1 -1
  76. package/ledger-write-ops.mjs +373 -0
  77. package/ledger.mjs +2 -2
  78. package/manifest-discovery.mjs +2 -2
  79. package/manifest-schema.mjs +4 -0
  80. package/memory/memory-tool.mjs +5 -2
  81. package/memory/schema.mjs +5 -26
  82. package/model-specs.mjs +6 -5
  83. package/package.json +1 -1
  84. package/path-resolve.mjs +217 -0
  85. package/permission.mjs +4 -0
  86. package/prompt-overlays.mjs +5 -0
  87. package/prompts/advisor-design.md +9 -6
  88. package/prompts/advisor-round1.md +4 -3
  89. package/prompts/advisor-round2.md +6 -5
  90. package/prompts/advisor-round3.md +6 -5
  91. package/prompts/common.md +23 -10
  92. package/prompts/consult-base.md +2 -1
  93. package/prompts/discipline-engineering.md +29 -48
  94. package/prompts/discipline-normal.md +10 -5
  95. package/prompts/persona-eng-coder.md +8 -0
  96. package/prompts/persona-eng-designer.md +22 -12
  97. package/prompts/persona-engineering.md +81 -66
  98. package/prompts/persona-plan.md +2 -1
  99. package/prompts/persona-tester.md +55 -0
  100. package/provider/anthropic.mjs +12 -7
  101. package/provider/core.mjs +21 -5
  102. package/provider/errors.mjs +41 -0
  103. package/provider/google.mjs +25 -9
  104. package/provider/list-models.mjs +4 -3
  105. package/provider/rate.mjs +11 -3
  106. package/provider/responses.mjs +17 -7
  107. package/provider/sse.mjs +15 -4
  108. package/session-identity.mjs +21 -0
  109. package/session-lifecycle.mjs +12 -4
  110. package/session-slots.mjs +9 -14
  111. package/session.mjs +5 -0
  112. package/skip-dirs.mjs +48 -0
  113. package/team-ledger.mjs +4 -4
  114. package/text-budget.mjs +4 -0
  115. package/token-window.mjs +107 -10
  116. package/tool-docs/advisor.md +2 -2
  117. package/tool-docs/apply_patch.md +2 -0
  118. package/tool-docs/bash.md +4 -0
  119. package/tool-docs/batch.md +85 -1
  120. package/tool-docs/browser.md +3 -1
  121. package/tool-docs/code_search.md +3 -1
  122. package/tool-docs/consult_start.md +6 -2
  123. package/tool-docs/consult_stop.md +3 -1
  124. package/tool-docs/delete.md +3 -0
  125. package/tool-docs/doc_check.md +43 -8
  126. package/tool-docs/doc_search.md +5 -1
  127. package/tool-docs/edit.md +22 -1
  128. package/tool-docs/execute.md +7 -1
  129. package/tool-docs/fetch.md +3 -1
  130. package/tool-docs/file_ops.md +3 -1
  131. package/tool-docs/git.md +9 -1
  132. package/tool-docs/goal.md +2 -0
  133. package/tool-docs/grep.md +1 -1
  134. package/tool-docs/hashline_edit.md +2 -0
  135. package/tool-docs/insert_after.md +3 -1
  136. package/tool-docs/ledger.md +5 -5
  137. package/tool-docs/lint.md +1 -0
  138. package/tool-docs/manifest-read.md +2 -1
  139. package/tool-docs/manifest.md +9 -2
  140. package/tool-docs/memory.md +11 -1
  141. package/tool-docs/notify_parent.md +1 -1
  142. package/tool-docs/plan.md +2 -0
  143. package/tool-docs/question.md +1 -1
  144. package/tool-docs/read_history.md +15 -3
  145. package/tool-docs/read_image.md +3 -1
  146. package/tool-docs/recent_changes.md +3 -1
  147. package/tool-docs/repo_outline.md +3 -1
  148. package/tool-docs/settings.md +10 -2
  149. package/tool-docs/skill.md +2 -0
  150. package/tool-docs/ssh.md +30 -17
  151. package/tool-docs/subagent.md +50 -13
  152. package/tool-docs/task.md +2 -0
  153. package/tool-docs/team_ledger.md +1 -1
  154. package/tool-docs/timer.md +7 -0
  155. package/tool-docs/verify.md +13 -1
  156. package/tool-docs/websearch.md +3 -1
  157. package/tool-docs/write.md +2 -0
  158. package/tools/bash.mjs +12 -2
  159. package/tools/edit-batch.mjs +40 -33
  160. package/tools/edit-diff.mjs +69 -1
  161. package/tools/execute.mjs +10 -4
  162. package/tools/file.mjs +17 -27
  163. package/tools/git.mjs +27 -1
  164. package/tools/ops.mjs +2 -1
  165. package/tools/patch.mjs +12 -3
  166. package/tools/process-doc-mark.mjs +139 -0
  167. package/tools/search.mjs +15 -6
  168. package/tools/shared.mjs +24 -1
  169. package/tools/ssh.mjs +48 -7
  170. package/tools/tree.mjs +4 -1
  171. package/tools/write-path.mjs +64 -3
  172. package/traces/trace-store.mjs +4 -3
  173. package/agent/light-round.mjs +0 -63
  174. package/agent/unbooked-pen.mjs +0 -62
@@ -0,0 +1,373 @@
1
+ /**
2
+ * ledger-write-ops.mjs — 台账逐行写核 + 两个批级写点的单源实现(2026-10-27 批拆出)。
3
+ *
4
+ * 为什么拆:`ledger-cmd.mjs` 收尾 455 行,本批要新增 `ledgerBindBatch`(建档 = 建台账行的单事务)与
5
+ * `ledgerReclaimBatch`(重开回收)。两枚都落在 `ledger-cmd` 会把它推过 500 顾问线,而设计档 §3 的
6
+ * 预算是「≈495 行」。本档承接**逐行写核**(迁移表判 → 核销机检 → executor / evidence 语义
7
+ * → 写门 → UPDATE 的那一条链)与两个批级写点;`ledger-cmd.mjs` 反向 import 取用并 re-export,
8
+ * 公共面(`ledger.mjs` 的导出面)逐字不变——先例 = `ledger-close-proof.mjs` / `ledger-tools.mjs`。
9
+ *
10
+ * 依赖单向:`ledger-db` → `ledger-close-proof` → 本档 → `ledger-cmd`(本档不 import 命令面,防环)。
11
+ *
12
+ * 三条硬口径(本档承载,来自设计档 `docs/core/design/BATCH-LEDGER-REVIEW-UNIFY.md`):
13
+ * ① **不直写 SQL 的落笔面**(§2.4):建档事务的行写入走 `applyRowUpdateTx` 同一函数体——迁移表 /
14
+ * 写门 / executor 语义一次都不重做;批级写点自己拼的 UPDATE 只覆盖「机器回收」那一类不查迁移表的写
15
+ * (同收口重锚口径)。
16
+ * ② **落档先于指针写入**(§2.4):`ledgerBindBatch` 在写指针之前回调 `writeRecord`——写门两形都判
17
+ * 「解析出的档在盘」,先写指针后落档必被自己的门当场拒。
18
+ * ③ **回收只回 `待核销`**(§2.13):`已核销` 是终态,重开不回收、回执点名;其余状态一律不动。
19
+ */
20
+ import { resolve } from "node:path"
21
+
22
+ import { ALLOWED_MIGRATIONS, nowIso, openLedger, PENDING_STATUSES, resolveLedgerRoot } from "./ledger-db.mjs"
23
+ import { resolveDeclaredRef } from "./declaration.mjs"
24
+ import { assertCloseOutProof, filePartOf, namesPointer } from "./ledger-close-proof.mjs"
25
+
26
+ /** 解析基准(设计档 §6.1 口径 2 同一子表达式——与台账库键同源):`resolveLedgerRoot(cwd, boundary)?.root
27
+ * ?? resolve(cwd ?? ".")`。`boundary` = 会话锚(与 `openLedger` 同一枚上界——同一笔动作的判据不能两界)。 */
28
+ export function baseOf(cwd, boundary) {
29
+ return resolveLedgerRoot(cwd, boundary)?.root ?? resolve(cwd ?? ".")
30
+ }
31
+
32
+ /** 路径同一性比较(win32 大小写不敏感——与 `batch-paths.mjs` / `design-slots.mjs` 同口径)。 */
33
+ const pathKey = (p) => {
34
+ const s = String(p ?? "").replace(/\\/g, "/")
35
+ return process.platform === "win32" ? s.toLowerCase() : s
36
+ }
37
+
38
+ // ─── 逐行写核(update / close / bind 共用;判序 = 设计档 §13.9 逐字未变)───────────────────────
39
+
40
+ /** executor 目标值计算(设计档 docs/core/design/LEDGER.md §3.1——判位在写核体内:迁移表判 → 本语义 → 写门 → UPDATE)。
41
+ * 优先级 = patch 显式 > 自动语义(进边 = executorSessionId / 出边 = NULL)> 行现值兜底 > NULL。
42
+ * 进边集:`待讨论 → 待设计`(批点火边)∥ `待设计 → 在途`(实施入边)∥ `待讨论 → 在途`(2026-10-27
43
+ * 建档边——「需求已确认、当场开工」同样是一次进边,AC-M2-7「进入在途的迁移带 executor」在机器径上
44
+ * 也得成立)。 */
45
+ export function resolveExecutorTarget(row, to, patch, executorSessionId) {
46
+ if ((row.status === "待讨论" && to === "待设计") || (row.status === "待设计" && to === "在途") || (row.status === "待讨论" && to === "在途")) {
47
+ return patch.executor ?? executorSessionId ?? row.executor ?? null
48
+ }
49
+ if (row.status === "在途" && (to === "待核销" || to === "已废弃")) {
50
+ return null // 出边自动语义压 patch——显式传 executor 也不复活
51
+ }
52
+ return patch.executor ?? row.executor ?? null
53
+ }
54
+
55
+ /** `evidence` 值计算(设计档 LEDGER.md §13.9——update / close 两函数同源一处落):追加缺省(旧值非空 ⇒
56
+ * `旧 + "|" + 新`——「|」两侧零空格直接相接;旧空(`null` ∥ 空串 ∥ 全空白)⇒ 直写,零分隔符残留);
57
+ * `evidenceReplace === true` 与值同传 ⇒ 整段替换(旧文零保留);`evidence` 缺 / `null` ≡ 略去(零变)。
58
+ * 拒面①(旗独传——无新文可算)判于本阶段;核内旗判只认 `=== true`(类型严判在工具层)。两值逐字落行
59
+ * (不裁 / 不判内容)。 */
60
+ export function evidenceValue(fnName, oldValue, evidence, evidenceReplace) {
61
+ if (evidenceReplace === true && evidence == null) throw new Error(`${fnName}:evidenceReplace 须与 evidence 同传(缺新文)`)
62
+ if (evidence == null) return oldValue
63
+ const old = oldValue == null || String(oldValue).trim() === "" ? "" : String(oldValue)
64
+ return evidenceReplace === true || old === "" ? evidence : `${old}|${evidence}`
65
+ }
66
+
67
+ /** 拒面②(新值全空白——追加 ∥ 覆盖两径同判)判点(§13.9 判序):update 与 close 两面皆随值计算同拍;
68
+ * `evidence` 未传(`null` / 略去)⇒ 不判。 */
69
+ export function assertEvidenceValue(fnName, evidence) {
70
+ if (evidence != null && String(evidence).trim() === "") throw new Error(`${fnName}:evidence 为空(非空字符串)`)
71
+ }
72
+
73
+ /** 写门·指针点名(设计档 §6.1 两臂 · AC-M2-10 ∥ AC-M2-29):判**本笔写入面**——两臂之外零判。
74
+ * 臂①(点名非空):结果行 `status ∈ {在途, 待核销}` ∨ 结果行 `trigger = 归批` ⇒ `task_book` 非空
75
+ * (缺 / 空串 / 全空白 ⇒ 拒「必填」);**不判在盘**(列值事实,不随盘面翻转——DDL 咬合 CHECK 为该半幅防线)。
76
+ * 臂②(本笔新指名):本笔新写 `task_book`(非空值)∥ 本笔新写 `trigger = 归批` ⇒ 绑定指针非空 ∧ 文件部分
77
+ * 可解析 ∧ 解析出的档**在盘**(判定单源 = `resolveDeclaredRef`,零二写)。
78
+ * **「档此刻在不在盘」不入臂①**:批档可随时整体清理 ⇒ 当结果行判据则合法账务被拒、而出路正是被拒字段
79
+ * (死锁);存量指针失效只体现为读数 `taskBookOnDisk = false`(§7.1)。 */
80
+ export function assertTaskBookGate(cwd, fnName, face, boundary) {
81
+ const { status, trigger, taskBook, wroteTaskBook, wroteBatchTrigger } = face
82
+ if ((status === "在途" || status === "待核销" || trigger === "归批") && !namesPointer(taskBook)) {
83
+ throw new Error(`${fnName}:task_book 必填(在途 / 待核销 / 归批 须携任务书指针)`)
84
+ }
85
+ if (!wroteTaskBook && !wroteBatchTrigger) return // 臂② 之外零判:旧指针所指档已清不阻任何此类写入
86
+ const part = filePartOf(taskBook)
87
+ if (!part) throw new Error(`${fnName}:task_book 不可解析(缺文件部分):${taskBook}`)
88
+ const base = baseOf(cwd, boundary) // §6.1 口径 2 同一子表达式(单源 = 本档 `baseOf`)
89
+ const { ok, abs } = resolveDeclaredRef(base, part)
90
+ if (!ok) throw new Error(`${fnName}:task_book 指向的档不存在:${taskBook}(解析 = ${abs})`)
91
+ }
92
+
93
+ /**
94
+ * 单行落笔·update(**单行形 ∥ 批量形 ∥ 建档形共函数**——设计档 §3.3「逐行语义单源」):迁移表判 →
95
+ * **核销机检**(目标 = `已核销` ⇒ `assertCloseOutProof`——2026-10-27 起 ③ = 档面 §3)→ executor / evidence 值计算
96
+ * (含两拒面)→ 写门 → UPDATE(判序 = §13.9 逐字未变)。`to` = `patch.status ?? 行现态`——批量形纯字段
97
+ * 补丁(不携 `status`)时 `to === row.status` ⇒ 迁移表不判、`status` 零写(§3.3)。
98
+ * `resume_when` = 普通可空字段补丁(`patch.resume_when ?? 行值`——§3.4)。
99
+ * **事务归属**:`tx = true` ⇒ 调用方已开事务(本函数不 BEGIN——嵌套 BEGIN 会当场抛);缺省 ⇒ 自开
100
+ * `BEGIN IMMEDIATE` 包一条 UPDATE(半途抛 ⇒ 行零改)。
101
+ */
102
+ export function applyRowUpdateTx(db, cwd, row, patch, executorSessionId, boundary, proof = {}) {
103
+ const to = patch.status ?? row.status
104
+ if (to !== row.status && !(ALLOWED_MIGRATIONS[row.status] ?? []).includes(to)) {
105
+ throw new Error(`ledgerUpdate:迁移 ${row.status} → ${to} 不在允许迁移表`)
106
+ }
107
+ // 核销机检(F-LX4):目标值 = `已核销` ⇒ 写库前三件校验(批档在盘 ∥ §1 已收口 ∥ §3 = 评审通过)
108
+ if (to === "已核销") assertCloseOutProof("ledgerUpdate", { base: proof.base ?? baseOf(cwd, boundary), row })
109
+ const nextExecutor = resolveExecutorTarget(row, to, patch, executorSessionId)
110
+ const nextEvidence = evidenceValue("ledgerUpdate", row.evidence, patch.evidence, patch.evidenceReplace)
111
+ assertEvidenceValue("ledgerUpdate", patch.evidence)
112
+ const nextTaskBook = patch.task_book ?? row.task_book
113
+ assertTaskBookGate(cwd, "ledgerUpdate", {
114
+ status: to,
115
+ trigger: patch.trigger ?? row.trigger,
116
+ taskBook: nextTaskBook,
117
+ wroteTaskBook: namesPointer(patch.task_book),
118
+ wroteBatchTrigger: patch.trigger === "归批",
119
+ }, boundary) // → 写门 → UPDATE
120
+ const now = nowIso()
121
+ db.prepare(`UPDATE items SET status = ?, title = ?, board = ?, req_doc = ?, task_book = ?, evidence = ?, trigger = ?, resume_when = ?, executor = ?, updated_at = ? WHERE id = ?`)
122
+ .run(to, patch.title ?? row.title, patch.board ?? row.board, patch.req_doc ?? row.req_doc, nextTaskBook, nextEvidence, patch.trigger ?? row.trigger, patch.resume_when ?? row.resume_when, nextExecutor, now, row.id)
123
+ return { id: Number(row.id), status: to }
124
+ }
125
+
126
+ /**
127
+ * 单行落笔·update(外层不持事务的调用面):`tx` 缺省 ⇒ 自开事务包裹 `applyRowUpdateTx`;
128
+ * `tx = true` ⇒ 直接透传(调用方自己的事务边界说了算——`ledgerUpdateBatch` / `ledgerBindBatch`)。
129
+ */
130
+ export function applyRowUpdate(db, cwd, row, patch, executorSessionId, boundary, proof = {}, tx = false) {
131
+ if (tx) return applyRowUpdateTx(db, cwd, row, patch, executorSessionId, boundary, proof)
132
+ db.exec("BEGIN IMMEDIATE")
133
+ try {
134
+ const out = applyRowUpdateTx(db, cwd, row, patch, executorSessionId, boundary, proof)
135
+ db.exec("COMMIT")
136
+ return out
137
+ } catch (e) {
138
+ try { db.exec("ROLLBACK") } catch { /* 事务未开 / 已结 */ }
139
+ throw e
140
+ }
141
+ }
142
+
143
+ /** 核销拒文的出路句(设计档 §3 三行表逐字):同一件事实(核销只自 `待核销` 出)、出路按现态分支——
144
+ * 对 `在途` 说「先 update 到 在途 认领」是让人重做一步已做过的事。归档态那支不是新分支:改前同一输入
145
+ * 也走首判并拒(旧判据本就不含归档态),行为零改,只把这句兜底话换准。 */
146
+ function closeExitSentence(status) {
147
+ if (status === "在途") return "先 update 到 待核销(做完才结账),再 close 勾销"
148
+ if (status === "已核销" || status === "已废弃") return `该行已归档(${status}),不在核销面`
149
+ return "先 update 到 在途 认领,再 update 到 待核销,最后 close 勾销"
150
+ }
151
+
152
+ /** 单行落笔·close(**单行形 ∥ 批量形共函数**——设计档 §3.3「逐行语义单源」):源态判(**核销只有一条边**——
153
+ * `待核销 → 已核销`;其余现态一律拒,拒文点名现态 + 该现态的出路句)→ **核销机检**(`已核销` ⇒ `assertCloseOutProof`)→ 值计算
154
+ * (拒面① 旗独传判于此)→ 拒面② → UPDATE。旧的 `待讨论 / 待设计 → 已核销` 捷径边已删(2026-11-02):
155
+ * 那条边跳过的不是权限,是四个状态各自代表的事实(没设计、没认领、没验证的行不得显示为已结账)。
156
+ * 撤回(任意态 → 已废弃——含在途直撤)同步 `executor = NULL`(LEDGER.md §3.1 ④);核销(`待核销 → 已核销`)
157
+ * `executor` 零触碰(非在途出边)。调用方恒已开事务(`ledgerClose` / `ledgerCloseBatch`)。 */
158
+ export function applyRowClose(db, row, status, evidence, evidenceReplace, proof = {}) {
159
+ if (status === "已核销" && row.status !== "待核销") throw new Error(`ledgerClose:核销仅限 待核销(现态 ${row.status}):${closeExitSentence(row.status)}`)
160
+ // 核销机检(F-LX4):判序 = 源态判之后、值计算之前
161
+ if (status === "已核销") assertCloseOutProof("ledgerClose", { base: proof.base, row })
162
+ const nextEvidence = evidenceValue("ledgerClose", row.evidence, evidence, evidenceReplace)
163
+ assertEvidenceValue("ledgerClose", evidence) // 判序:拒面②(新值空白)→ UPDATE
164
+ const now = nowIso()
165
+ db.prepare("UPDATE items SET status = ?, closed_at = ?, updated_at = ?, executor = ?, evidence = ? WHERE id = ?").run(status, now, now, status === "已废弃" ? null : row.executor, nextEvidence, row.id)
166
+ return { id: Number(row.id), status }
167
+ }
168
+
169
+ // ─── 批级写点(建档绑定 · 重开回收;与收口重锚同形对称)────────────────────────────────
170
+
171
+ /** `batch` 入参自身门(设计档 §3.3 · fail-closed):值自身须**可解析**(含非空文件部分)∧ 解析后批档
172
+ * **在盘** ⇒ 否则拒(零写)。判的是**本笔指名**的那一笔——与 §6.1 臂② 同一律、不同字段。
173
+ * @returns {string} 批档绝对路径(解析基准 = `base`) */
174
+ export function assertBatchArg(fnName, base, batch) {
175
+ const part = filePartOf(batch)
176
+ const hit = part === "" ? { ok: false, abs: "" } : resolveDeclaredRef(base, part)
177
+ if (!hit.ok) throw new Error(`${fnName}:batch 不可解析或不在盘(${batch})`)
178
+ return hit.abs
179
+ }
180
+
181
+ /** 批 → 行集(选行**单源** · 设计档 §3.3):`task_book` 文件部分经 `resolveDeclaredRef` 解析为与批档
182
+ * **同一绝对路径**的行(任意状态、含归档态)。判据 = 解析同址而非串等值(同批档三种指针写法并存——
183
+ * 无后缀 / `§2` / `§1.5(括注)`,串等值会漏行)。 */
184
+ export function resolveBatchRows(db, base, abs) {
185
+ return db.prepare("SELECT * FROM items ORDER BY id").all()
186
+ .filter((r) => { const part = filePartOf(r.task_book); return part !== "" && resolveDeclaredRef(base, part).abs === abs })
187
+ }
188
+
189
+ /** 建档绑定拒文(逐字 = 设计档 §2.4;三形各一条)。 */
190
+ const bindMissingError = (n) => `batch: ledger entry #${n} does not exist in this project's ledger — pass an existing number, or omit ledger and let create open a new row. Nothing was written.`
191
+ const bindArchivedError = (n, status) => `batch: ledger entry #${n} is ${status} — an archived row cannot carry a new batch record (open a new row instead). Nothing was written.`
192
+ const bindOwnedError = (n, path) => `batch: ledger entry #${n} is 在途 and its task_book points at ${path} — another batch owns it; a record may take over a row only when its task_book points at this record. Nothing was written.`
193
+ const bindSettledError = (n) => `batch: ledger entry #${n} is 待核销 — a row already settled to 待核销 cannot carry a new batch record (reopen the record that owns it with batch status, or open a new row instead). Nothing was written.`
194
+
195
+ // ─── 开批同面判(设计档 §6.3 · AC-M2-37;判点 = 写形开户之前)──────────────────────────
196
+
197
+ /** 新事理由的 `evidence` 前缀(单源 = 设计档 §6.3「参数面」;写入侧造前缀、收口侧按同一字面取文——
198
+ * 读面对 = `thincoder-core/agent-tools/batch-closeout.mjs` 的 `newDebtReasonOf`,两处同一字面、
199
+ * 不进公共导出面)。 */
200
+ const NEW_DEBT_EVIDENCE_PREFIX = "新事理由:"
201
+ /** 候选逐行上限(§6.3「上限」——超出改出计数句;一眼读完才叫「见该接哪行」)。 */
202
+ const SAME_FACE_LIST_LIMIT = 10
203
+
204
+ /** 面归一等(§6.3「面 = 本笔 `board`」):缺省 / 空串 / 全空白 ⇒ `null` = **无板面**(`board IS NULL`)。 */
205
+ const faceOf = (board) => {
206
+ const v = board == null ? "" : String(board).trim()
207
+ return v === "" ? null : v
208
+ }
209
+
210
+ /** 同行是否在本面(**等值口径同 §7.1 `board` 过滤——零新口径**):无板面 = `board IS NULL`(严格 NULL,
211
+ * 不是「空白串也算无板面」);有板面 = 与行值严格相等(SQL `board = ?` 的同一判等)。 */
212
+ const onSameFace = (rowBoard, face) => (face === null ? rowBoard == null : rowBoard === face)
213
+
214
+ /** 同携拒文(逐字 = 设计档 §6.3 第二段围栏;`newDebt` 与 `ledger` 语义互斥)。 */
215
+ const newDebtAndLedgerError = () => `batch: newDebt and ledger are mutually exclusive — take over a debt already in the pool with ledger:"#N"; declare a new debt with newDebt:"<reason>". Both together is ambiguous. Nothing was written.`
216
+
217
+ /**
218
+ * 同面拒文(逐字 = 设计档 §6.3 第一段围栏):首行点名面与未决数 + 逐行候选(`id` 升序、至多 10 行)+
219
+ * 截断时的计数句与自取命令 + 收尾 `Nothing was written.`。无板面 ⇒ 面名 `no board`,自取句不带
220
+ * `board` 参数(无板面没有板名可填;不带过滤的查询回全池,余数仍准)。
221
+ * @param {string|null} face — 面(`null` = 无板面)
222
+ * @param {{id:number, status:string, title:string}[]} candidates — 本面未决行(`id` 升序)
223
+ */
224
+ function sameFaceRefusal(face, candidates) {
225
+ const shown = candidates.slice(0, SAME_FACE_LIST_LIMIT)
226
+ const rest = candidates.length - shown.length
227
+ const lines = [
228
+ `batch: ${candidates.length} unsettled row(s) already exist on this face — create refuses to open a new ledger row silently (face = ${face ?? "no board"}). Either take one over with ledger:"#<id>", or declare a genuinely new debt with newDebt:"<one line: why it is not one of these>". Candidates:`,
229
+ ...shown.map((r) => ` #${r.id} ${r.status} ${r.title}`),
230
+ ]
231
+ if (rest > 0) {
232
+ lines.push(face === null
233
+ ? `… ${rest} more not listed — read the rest with the ledger tool (action query).`
234
+ : `… ${rest} more not listed — read the rest with the ledger tool (action query, board:"${face}").`)
235
+ }
236
+ lines.push("Nothing was written.")
237
+ return lines.join("\n")
238
+ }
239
+
240
+ /**
241
+ * 同面取集(设计档 §6.3「候选行集与次序」):`ledgerQuery` 取全行、核内按面与状态筛——零新 SQL、
242
+ * 零新导出。取集走**读形**开户(`ledgerQuery` 内部 `openLedger(readOnly)` ⇒ `create` 缺省假、库档不在
243
+ * 即返空集),故此步不建库档。`ledger-cmd` 反向 import 本档(防环)⇒ 此处动态 import。
244
+ * @returns {Promise<{n:number, m:number, candidates:object[]}>} 本面未决数 ∥ 他面未决数(读数)∥ 候选
245
+ */
246
+ async function sameFaceScan({ cwd, boundary, face }) {
247
+ const { ledgerQuery } = await import("./ledger-cmd.mjs")
248
+ const pending = ledgerQuery({ cwd, boundary }).filter((r) => PENDING_STATUSES.includes(r.status))
249
+ const mine = pending.filter((r) => onSameFace(r.board, face))
250
+ // 排序即判据(§6.3):`id` 升序 = 最老欠条优先(与 §3.7 出口面 `aged` 同轴)——候选表读作「从上往下先看」
251
+ mine.sort((a, b) => Number(a.id) - Number(b.id))
252
+ return { n: mine.length, m: pending.length - mine.length, candidates: mine }
253
+ }
254
+
255
+ /**
256
+ * 建档 = 建台账行(设计档 §2.4——单事务在台账侧,档写在事务窗口内,**落档先于指针写入**):
257
+ * `BEGIN IMMEDIATE` → (新建)INSERT 一行 `待讨论`(无指针;`trigger` 不填——`trigger=归批` 与
258
+ * `task_book` 同在下一步一笔写,否则臂① 会把无指针的行当场拒)→ `writeRecord(#N)` 写批档 →
259
+ * `applyRowUpdateTx` 迁 `在途` + 写 `task_book`(过写门)→ `COMMIT`。接管路径同一条道理:`#N` 已知 →
260
+ * `writeRecord` → `applyRowUpdateTx` → `COMMIT`。
261
+ *
262
+ * 崩在窗口内(行已 INSERT ∥ 已改、`COMMIT` 未执行、档已落盘):SQLite 回滚事务 ⇒ 行回原态;档留在盘上而
263
+ * 台账里没有它的行(写锁只问「这个项目里有没有 `在途` 行」,不读指针——设计档 §2.3)。清理只删**本笔所建**的档(`created` 由调用方按返回值判断——`wx` 撞已有
264
+ * 同名档时抛 EEXIST,无条件 unlink 会删掉别人的档)。
265
+ *
266
+ * **开批同面判**(设计档 §6.3 · AC-M2-37):省略 `ledger` ∧ 同面(本笔 `board`;未给 ⇒ `board IS NULL`)
267
+ * 存在未决四态行 ⇒ 必须显式声明 `newDebt:"<一句理由>"`,否则**在写形开户之前**拒(逐行列候选)。
268
+ * 因此「零写 = 二件」(无行写入 ∥ 无批档写入)是结构挣来的,不靠判后回滚;判前取集走读形开户,不建库档。
269
+ * 理由落本笔新开行的 `evidence`(前缀 `新事理由:`);`ledger:"#N"` 在场 ⇒ 本节零判(接手路径零回归)。
270
+ * @param {{cwd:string, ledgerNo:number|null, newDebt:string|null, targetAbs:string, taskBook:string, title:string,
271
+ * kind:string, board:string|null, boundary?:string, executorSessionId?:string|null, agent?:object}} o
272
+ * `newDebt` = 非空字符串(为什么这不是候选里那几条);空串 ∥ 全空白 ⇒ 按未给判;与 `ledger` 同携 ⇒ 拒。
273
+ * @param {(id:number)=>void} writeRecord — 写批档(`wx` 独占创建;真实行号已可得)
274
+ * @returns {Promise<{id:number, created:boolean, tookOver:"new"|"discussed"|"designed"|"live",
275
+ * sameFace:{face:string|null, n:number, m:number, declared:string|null}|null}>} `sameFace` = 同面判读数
276
+ * (`null` = 接手路径);`n` = 本面未决数 · `m` = 他面未决数(读数,不拦)· `declared` = 声明的新事理由
277
+ */
278
+ export async function ledgerBindBatch({ cwd, ledgerNo = null, newDebt = null, targetAbs, taskBook, title, kind = "tech_todo", board = null, boundary, executorSessionId = null, agent = {} }, writeRecord) {
279
+ const face = faceOf(board)
280
+ const newDebtReason = typeof newDebt === "string" && newDebt.trim() !== "" ? newDebt.trim() : null // 空串 ∥ 全空白 ⇒ 按未给判
281
+ if (ledgerNo != null && newDebtReason) throw new Error(newDebtAndLedgerError()) // 语义互斥:`ledger` 在场即接手,不是新事
282
+ // 开批同面判(§6.3):判点 = 写形开户之前;`ledger` 在场 ⇒ 本节零判(接手路径零回归)
283
+ const scan = ledgerNo == null ? await sameFaceScan({ cwd, boundary, face }) : null
284
+ if (scan && scan.n > 0 && !newDebtReason) throw new Error(sameFaceRefusal(face, scan.candidates))
285
+ const sameFace = scan ? { face, n: scan.n, m: scan.m, declared: newDebtReason } : null
286
+ const db = openLedger(cwd, { create: true, boundary })
287
+ try {
288
+ const base = baseOf(cwd, boundary)
289
+ db.exec("BEGIN IMMEDIATE")
290
+ try {
291
+ let row = null
292
+ let id = null
293
+ let created = false
294
+ if (ledgerNo == null) {
295
+ // 步 2:先落一行不带指针的 `待讨论`(`在途` 被 DDL 咬合 CHECK 强制非空指针)+ `trigger` 不填;
296
+ // 声明新事者同拍带理由入 `evidence`(步 4 的值计算在 `patch.evidence` 缺省时保留行现值)
297
+ const now = nowIso()
298
+ const r = db.prepare(`INSERT INTO items (kind, status, title, board, evidence, created_at, updated_at) VALUES (?, '待讨论', ?, ?, ?, ?, ?)`)
299
+ .run(kind, title, board, newDebtReason ? `${NEW_DEBT_EVIDENCE_PREFIX}${newDebtReason}` : null, now, now)
300
+ id = Number(r.lastInsertRowid)
301
+ created = true
302
+ row = db.prepare("SELECT * FROM items WHERE id = ?").get(id)
303
+ } else {
304
+ id = Number(ledgerNo)
305
+ row = db.prepare("SELECT * FROM items WHERE id = ?").get(id)
306
+ if (!row) throw new Error(bindMissingError(id))
307
+ if (row.status === "已核销" || row.status === "已废弃") throw new Error(bindArchivedError(id, row.status))
308
+ if (row.status === "待核销") throw new Error(bindSettledError(id))
309
+ if (row.status === "在途") {
310
+ const part = filePartOf(row.task_book)
311
+ const hit = part === "" ? { ok: false, abs: "" } : resolveDeclaredRef(base, part)
312
+ if (!hit.ok || pathKey(hit.abs) !== pathKey(targetAbs)) throw new Error(bindOwnedError(id, row.task_book))
313
+ // 接管(不动状态、零台账写):档可能已不在盘(残留态)——照写一次 `wx`,撞已有档时由调用方报
314
+ writeRecord(id)
315
+ db.exec("COMMIT")
316
+ return { id, created: false, tookOver: "live", sameFace }
317
+ }
318
+ }
319
+ // 步 3:落档——必须先于步 4(写门两形都判「解析出的档在盘」)
320
+ writeRecord(id)
321
+ // 步 4:行写入走同一函数体(迁移表 → 机检 → executor → 写门 → UPDATE),不直写 SQL
322
+ applyRowUpdateTx(db, cwd, row, { status: "在途", task_book: taskBook, trigger: "归批" }, executorSessionId, boundary, { base, agent })
323
+ db.exec("COMMIT")
324
+ return { id, created, tookOver: created ? "new" : row.status === "待设计" ? "designed" : "discussed", sameFace }
325
+ } catch (e) {
326
+ try { db.exec("ROLLBACK") } catch { /* 事务未开 / 已结 */ }
327
+ throw e
328
+ }
329
+ } finally { db.close() }
330
+ }
331
+
332
+ /** 重开回收回执的三种行(设计档 §2.13)。 */
333
+ const reclaimResult = (batch, { reclaimed = [], unreclaimable = [], failed = null } = {}) => ({ batch, reclaimed, unreclaimable, failed })
334
+
335
+ /**
336
+ * 重开回收(设计档 §2.13——收口的反面):批档重开(`§1` 从 `已收口` 回 `进行中`)时,本批 `待核销` 行
337
+ * 回到 `在途`(行回工作位,活继续在这一行上——`§2.2` 三合取当场成立);`已核销` 不动(终态,行上挂着
338
+ * 结账证据),入 `unreclaimable` 由回执点名;其余状态一律不动、不点名。
339
+ *
340
+ * 与 `ledgerReanchorBatch` 同形对称:同一行集 `resolveBatchRows`(行集判据单源)、`BEGIN IMMEDIATE`
341
+ * 单事务、失败 `ROLLBACK` 零行改、**不抛**(返回形 `failed`)。
342
+ * `trigger` / `resume_when` / `evidence` / `updated_at` **零触**(机器回收不是人工推进,行龄读数照旧);
343
+ * `executor` = 本会话 `sessionId`(行回工作位、干活的就是重开会话——AC-M2-7 在机器径上也成立)。
344
+ * @returns {{batch:string, reclaimed:{id:number,status:string,title:string}[], unreclaimable:{id:number,status:string,title:string}[], failed:string|null}}
345
+ */
346
+ export function ledgerReclaimBatch({ cwd, batch, boundary, executorSessionId = null } = {}) {
347
+ const derived = cwd == null ? null : resolveLedgerRoot(cwd, boundary)
348
+ if (!derived) return reclaimResult(batch, { failed: "批档所属项目根不可推导" })
349
+ const base = derived.root
350
+ let db
351
+ try { db = openLedger(base, { boundary }) } catch (e) { return reclaimResult(batch, { failed: String(e?.message ?? e) }) } // 库不可开 ⇒ 零写报因
352
+ if (!db) return reclaimResult(batch) // 库不在 ⇒ 零行可回收
353
+ try {
354
+ let abs
355
+ try { abs = assertBatchArg("ledgerReclaimBatch", base, batch) } catch (e) { return reclaimResult(batch, { failed: String(e?.message ?? e) }) }
356
+ try {
357
+ db.exec("BEGIN IMMEDIATE")
358
+ const reclaimed = [], unreclaimable = []
359
+ for (const row of resolveBatchRows(db, base, abs)) {
360
+ const face = { id: Number(row.id), status: row.status, title: row.title }
361
+ if (row.status === "已核销") { unreclaimable.push(face); continue }
362
+ if (row.status !== "待核销") continue // 其余(条件 / 待讨论 / 待设计 / 已废弃 / 在途)不动、不点名
363
+ db.prepare("UPDATE items SET status = ?, executor = ? WHERE id = ?").run("在途", executorSessionId, row.id)
364
+ reclaimed.push(face)
365
+ }
366
+ db.exec("COMMIT")
367
+ return reclaimResult(batch, { reclaimed, unreclaimable })
368
+ } catch (e) {
369
+ try { db.exec("ROLLBACK") } catch { /* 事务未开 / 已结 */ }
370
+ return reclaimResult(batch, { failed: String(e?.message ?? e) })
371
+ }
372
+ } finally { db.close() }
373
+ }
package/ledger.mjs CHANGED
@@ -153,8 +153,8 @@ export function detailScans(scans, current) {
153
153
  // ── re-export(拆分件接口——命令面接线 = 动态 import 本档,KD-M2-3;`AGING_DAYS` 常量单源 = `ledger-db.mjs`
154
154
  // ——2026-10-07 批 ledger-tool 自本档迁入,公共面零变;`ledgerChildren` / `MAX_SIBLING_SCAN` / `resolveLedgerRoot`
155
155
  // ——2026-10-16 批 ledger-root-derivation 随迁 / 新增,公共面形零改) ──
156
- 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"
157
- export { ledgerAdd, ledgerBatchRows, ledgerClose, ledgerCloseBatch, ledgerCount, ledgerEvidenceOversize, ledgerQuery, ledgerReanchorBatch, ledgerUpdate, ledgerUpdateBatch, overdueOf } from "./ledger-cmd.mjs"
156
+ export { AGING_DAYS, ALLOWED_MIGRATIONS, EVIDENCE_TOO_LONG_MAX, MAX_SIBLING_SCAN, ledgerChildren, ledgerDbPath, ledgerKey, ledgerDirPath, openLedger, PENDING_STATUSES, resolveLedgerRoot, _setLedgerDirForTest, _resetLedgerDirForTest, ensureColumn, ensureExecutorColumn } from "./ledger-db.mjs"
157
+ export { ledgerAdd, ledgerBatchRows, ledgerBindBatch, ledgerClose, ledgerCloseBatch, ledgerCount, ledgerEvidenceTooLong, ledgerQuery, ledgerReanchorBatch, ledgerReclaimBatch, ledgerUpdate, ledgerUpdateBatch, overdueOf } from "./ledger-cmd.mjs"
158
158
  export { ledgerTool, ledgerReadTool, ledgerAddTool, ledgerCloseTool, ledgerCountTool, ledgerQueryTool, ledgerUpdateTool } from "./ledger-tools.mjs"
159
159
  export { runLedgerAudit, runLedgerMigrate } from "./ledger-migrate.mjs"
160
160
  export { resolveExecutorStates, executorTail, _setExecutorProbeTtlForTest } from "./ledger-executors.mjs"
@@ -101,13 +101,13 @@ export function discoverRepos(cwd) {
101
101
  * 尾分隔符,`edge + sep` 直比会假阴)。
102
102
  * **覆盖位**(`_setProjectRootForTest` 在场)⇒ **头部短路**:直接返回覆盖值(不查档存在性)。
103
103
  * @param {string} [target] 目标路径(目录 / 文件)
104
- * @param {string} [boundary] 上溯上界(缺省 → 进程 cwd)
104
+ * @param {string} boundary 上溯上界(**必传**——取根面传目标所在盘根,台账面传会话锚)
105
105
  * @returns {string|null} 带档目录绝对路径 / null
106
106
  */
107
107
  export function owningProject(target, boundary) {
108
108
  if (_projectRootOverride) return resolve(_projectRootOverride)
109
109
  let dir = resolve(target ?? ".")
110
- const edge = resolve(boundary ?? process.cwd())
110
+ const edge = resolve(boundary)
111
111
  // 界:target 在界外(含「target 是界的祖先」)⇒ 无归属——界由调用方定,越界不猜。
112
112
  if (!isInsideOrEqual(dir, edge)) return null
113
113
  const st = statSync(dir, { throwIfNoEntry: false })
@@ -36,6 +36,10 @@ export const DEFAULT_MANIFEST = Object.freeze({
36
36
  design: "docs/design",
37
37
  modules: "docs/design/modules",
38
38
  batches: "docs/batches",
39
+ // 第六子键(MANIFEST.md §2.12 · KD-M1-55/57):**标准档本身**的文件路径(单份 ∥ 多份数组),
40
+ // 不是目录——目录会逼着在目录里猜文件名,正是本批禁的「猜固定名」。默认 `null`(未声明)是
41
+ // 三态的地基:给一条默认路径,「未声明」与「声明了但读不到」会读出同一路径、塌成一态。
42
+ standards: null,
39
43
  }),
40
44
  // 项目声明三族(2026-09-27 声明载体唯一化批并入——键名 / 形态逐字承前;来源档已退役)。
41
45
  codePaths: Object.freeze(["src"]),
@@ -10,7 +10,7 @@ import { put, search, putMarkdown, clearPersonal } from "./core.mjs"
10
10
  import { deleteByUid, matchMemoryRows, deleteWhere } from "./delete.mjs"
11
11
  import { commitAndPush } from "../git/gitmem.mjs"
12
12
  import { formatMaintainReport, maintainMemory } from "./maintain.mjs" // F-S13:同一单点(自愈轮 §6.14 面⑤)
13
- import { DESC } from "../tools/shared.mjs" // #15 描述外置:文本单点 = tool-docs/memory.md
13
+ import { DESC, LEDGER_PARAM_DESC_MEMORY, PROJECT_PARAM_DESC } from "../tools/shared.mjs" // #15 描述外置:文本单点 = tool-docs/memory.md
14
14
 
15
15
  // ---------------------------------------------------------------- agent tools
16
16
 
@@ -49,7 +49,7 @@ const listRowLine = (r) => `[${r.layer}] ${r.id} [${r.type}] ${r.title}(${fmtD
49
49
  * opts: { cwd, projectDir, author, team: { dir, name } | null }
50
50
  */
51
51
  export function memoryTools(memory, opts = {}) {
52
- const projectDir = opts.projectDir ? (isAbsolute(opts.projectDir) ? opts.projectDir : join(opts.cwd ?? process.cwd(), opts.projectDir)) : null
52
+ const projectDir = opts.projectDir ? (isAbsolute(opts.projectDir) ? opts.projectDir : join(opts.cwd, opts.projectDir)) : null
53
53
  // §6.11:dirs 保持原样(目录 I/O 基准)——归一落在各公共入口内(写缝 syncDir / indexMarkdownFile、
54
54
  // 删缝 deleteByUid / matchMemoryRows、读缝 search / fetchEntry)⇒ 逐入口一行,非工具层预归一。
55
55
  const dirs = { project: projectDir, team: opts.team?.dir ?? null }
@@ -71,6 +71,9 @@ export function memoryTools(memory, opts = {}) {
71
71
  id: { type: "string", description: "delete single: the entry id from put/search/list output" },
72
72
  limit: { type: "number", description: "Max rows: list 50 by default, search 5 by default" },
73
73
  confirm: { type: "boolean", description: "delete batch/clear: must be true — without it the tool refuses; maintain: true executes the maintenance run (omitted = dry-run)" },
74
+ ledger: { type: "string", description: LEDGER_PARAM_DESC_MEMORY },
75
+ // 项目声明面(决策 40):本工具无定位参数 ⇒ 落 project 层的写型 action 只能靠声明。
76
+ project: { type: "string", description: PROJECT_PARAM_DESC },
74
77
  },
75
78
  required: ["action"],
76
79
  },
package/memory/schema.mjs CHANGED
@@ -41,32 +41,11 @@ export const CODE_EXTS = new Set([
41
41
  // Doc index: markdown / plain text (separate index makes it easier for LLM to distinguish "design specs" from "existing code")
42
42
  // Same declaration rule as CODE_EXTS (index.docExtensions).
43
43
  export const DOC_EXTS = new Set([".md", ".mdc", ".mdx", ".txt", ".rst", ".adoc", ".org", ".wiki", ".tex"])
44
- // Directory names skipped during code/doc indexing — family split (§6.14 面④ · 台账 #1081): junk/product
45
- // family (`SKIP_DIRS_FOLD`) folds on win32 (that filesystem is case-insensitive — `Node_Modules` must
46
- // still prune); position family (user/system dirs) stays exact on every platform; POSIX is exact
47
- // throughout. `.git` is covered by the dot-segment rule (file-walk.mjs) — it belongs to no family.
48
- export const SKIP_DIRS = new Set([
49
- "node_modules", ".git", "dist", "build", ".turbo", "coverage",
50
- "__pycache__", ".venv", "venv", "target", ".next", ".nuxt", ".svelte-kit",
51
- // Windows user profile directories (never contain project code)
52
- "AppData", "Application Data", "Desktop", "Documents", "Downloads",
53
- "Music", "Pictures", "Videos", "OneDrive", "Contacts", "Favorites",
54
- "Links", "Saved Games", "Searches",
55
- // User-home common items (Library = macOS user dir; go = Go workspace) — basename match at any depth (false-prune face accepted)
56
- "Library", "go",
57
- // Other common non-code directories
58
- "Program Files", "Program Files (x86)", "Windows", "$Recycle.Bin",
59
- // Generic ecosystem names (§6.14 面④ — exact basename, any depth; `bin` deliberately NOT included)
60
- "vendor", "Pods", "bower_components", "third_party", "obj", "out",
61
- ])
62
- /** Junk/product family full list (the win32 fold subset — matched only via `isSkippedRelPath`). */
63
- export const SKIP_DIRS_FOLD = new Set([
64
- "node_modules", "dist", "build", ".turbo", "coverage",
65
- "__pycache__", ".venv", "venv", "target", ".next", ".nuxt", ".svelte-kit",
66
- "vendor", "Pods", "bower_components", "third_party", "obj", "out",
67
- ])
68
- /** Directory-name prefixes (Bazel convenience dirs `bazel-<workspace>` — the workspace name rules out enumeration). */
69
- export const SKIP_DIR_PREFIXES = ["bazel-"]
44
+ // 目录剪枝名单**单源外提**(2026-10-27 四份名单合一 · 零依赖叶档 `../skip-dirs.mjs`):本档不再持
45
+ // 副本,同名 re-export 保 `memory.mjs:7` 的导出面逐字零变。名单 = 42 项精确集 + win32 折叠族(18)
46
+ // + 前缀族(`bazel-`);判定形状(折叠 / 前缀 / 点段)仍在本包 `file-walk.mjs`(只合名单,不合判定)。
47
+ import { SKIP_DIRS, SKIP_DIRS_FOLD, SKIP_DIR_PREFIXES } from "../skip-dirs.mjs"
48
+ export { SKIP_DIRS, SKIP_DIRS_FOLD, SKIP_DIR_PREFIXES } from "../skip-dirs.mjs"
70
49
  // Files larger than these limits are skipped during bulk indexing
71
50
  // (minified bundles, test fixtures, generated code, etc.)
72
51
  export const MAX_CODE_FILE_BYTES = 1024 * 1024 // 1 MB
package/model-specs.mjs CHANGED
@@ -235,11 +235,12 @@ const MODEL_SPECS = [
235
235
  ["doubao-seed-2-0-lite-260428", { context: 256_000, maxOutput: 131_072, thinking: true, thinkApi: "effort", reasoningEffortEnum: ["none", "minimal", "low", "medium", "high", "xhigh", "max"], multimodal: true }],
236
236
  // doubao-seed-2-1 三档(火山方舟 · 2026-09-24 bench 名单批 · 三行同形):thinking / thinkApi / 枚举 = **实测**
237
237
  // (裸请求 rc 在场;`reasoning_effort:"none"` → rc=0;七值全 200、乱值 400 泛化 ⇒ **受理级**——等级低于 2.0 行的
238
- // 「真校验」,同值集不混级);maxOutput 524_288 = **受理级**(262 144 / 524 288 均受理;**上限未证**);
239
- // context = **族沿用 2.0 行**(官方口径未逐名取证);**视觉面未探**(不声明——如实标级,不冒充实测)。
240
- ["doubao-seed-2-1-pro-260915", { context: 256_000, maxOutput: 524_288, thinking: true, thinkApi: "effort", reasoningEffortEnum: ["none", "minimal", "low", "medium", "high", "xhigh", "max"] }],
241
- ["doubao-seed-2-1-turbo-260628", { context: 256_000, maxOutput: 524_288, thinking: true, thinkApi: "effort", reasoningEffortEnum: ["none", "minimal", "low", "medium", "high", "xhigh", "max"] }],
242
- ["doubao-seed-2-1-lite-260915", { context: 256_000, maxOutput: 524_288, thinking: true, thinkApi: "effort", reasoningEffortEnum: ["none", "minimal", "low", "medium", "high", "xhigh", "max"] }],
238
+ // 「真校验」,同值集不混级);context / maxOutput = **官方模型列表口径**(2026-10-26 实查:pro / lite 上下文 1024k、
239
+ // turbo 256k;三行最大回答 256k);旧值 context 256_000 / maxOutput 524_288 在档内无任何受理读数取证面,已退场。
240
+ // **视觉面未探**(不声明——如实标级,不冒充实测)。
241
+ ["doubao-seed-2-1-pro-260915", { context: 1_024_000, maxOutput: 256_000, thinking: true, thinkApi: "effort", reasoningEffortEnum: ["none", "minimal", "low", "medium", "high", "xhigh", "max"] }],
242
+ ["doubao-seed-2-1-turbo-260628", { context: 256_000, maxOutput: 256_000, thinking: true, thinkApi: "effort", reasoningEffortEnum: ["none", "minimal", "low", "medium", "high", "xhigh", "max"] }],
243
+ ["doubao-seed-2-1-lite-260915", { context: 1_024_000, maxOutput: 256_000, thinking: true, thinkApi: "effort", reasoningEffortEnum: ["none", "minimal", "low", "medium", "high", "xhigh", "max"] }],
243
244
  // step-3.7-flash(StepFun · 2026-09-25 批 §14.2 #2-6 · §1.7.1 父侧交付):**尺寸行**——context 262_144(多源 256K =
244
245
  // 网络转述级;渠道未激活——400 "product is not activated");maxOutput 未取到 ⇒ 零口径——取兜底值 32_000(与建行前
245
246
  // 生效值同值);行键 = 裸名(渠道名录形 stepfun/step-3.7-flash——命名空间剥离同命中)。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thincoder/core",
3
- "version": "0.10.12",
3
+ "version": "0.11.1",
4
4
  "description": "ThinCoder shared core — shared mechanism modules + shared prompt content (CLI ↔ VS Code extension).",
5
5
  "keywords": [
6
6
  "ai",