@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
package/ledger-db.mjs CHANGED
@@ -22,7 +22,9 @@ import { join, resolve } from "node:path"
22
22
  import { DatabaseSync } from "node:sqlite"
23
23
 
24
24
  import { configDir } from "./config-io.mjs"
25
- import { MANIFEST_REL, owningProject, projectRootView } from "./manifest.mjs"
25
+ import { MANIFEST_REL, owningProject } from "./manifest.mjs"
26
+ // 无根报明面的候选来源 = 统一解析层(`docs/core/design/PATH-RESOLUTION.md` §2.3——本档不自持候选枚举)。
27
+ import { resolvePathTarget } from "./path-resolve.mjs"
26
28
  // 归一步单源(KD-LN1——直引,不抽新 util:先例 = peer-domains.mjs / traces/trace-store.mjs)。
27
29
  import { normalizeCwd } from "./session-slots.mjs"
28
30
 
@@ -58,11 +60,11 @@ function isRegistered(dir) {
58
60
  }
59
61
 
60
62
  /** 同级枚举上限(成本有界:父目录超此规模 = 缓存 / 临时等非仓族形态 → 退化空集)。
61
- * 2026-10-16 批 ledger-root-derivation 自 `ledger.mjs` 随迁(同级枚举:族发现 ∥ 轻通道闸候选枚举同源)。 */
63
+ * 2026-10-16 批 ledger-root-derivation 自 `ledger.mjs` 随迁(同级枚举:族发现单一消费面)。 */
62
64
  export const MAX_SIBLING_SCAN = 100
63
65
 
64
66
  /** 目录下**已注册**(档在盘——§2.1 单谓词)的直接子目录(目录名升序;不可读 ∥ 超限 ⇒ 空集;
65
- * **惰性枚举 + 上限**——大目录零全量扫描)。消费面 = 族发现 ∥ 轻通道闸候选枚举。
67
+ * **惰性枚举 + 上限**——大目录零全量扫描)。消费面 = 族发现(`thincoder-core/ledger.mjs`)。
66
68
  * `ledger` = 该子目录根的键控库路径(推算值 ⇒ 可能尚未在盘)。2026-10-16 批自 `ledger.mjs` 随迁。 */
67
69
  export function ledgerChildren(dir) {
68
70
  const out = []
@@ -91,11 +93,11 @@ export function ledgerChildren(dir) {
91
93
  * `ancestor`(最近带档祖先)。「项目根报明 / 建档」由用点决定。
92
94
  * **无自指**:本档不判库在盘(台账无第二定义——库在盘不算项目)。
93
95
  * @param {string} [target] 动作目标路径(目录 | 文件则取其所在目录)
94
- * @param {string} [boundary] 上溯上界(会话锚;缺省 = `process.cwd()`)
96
+ * @param {string} boundary 上溯上界(会话锚——**必传**;缺则该值缺失即失败,不落 `process.cwd()`)
95
97
  * @returns {{root: string, basis: 'self'|'ancestor'}|null} 项目根 + 判据来源 / 无根(`null`) */
96
98
  export function resolveLedgerRoot(target, boundary) {
97
99
  const start = resolve(target ?? ".")
98
- const edge = resolve(boundary ?? process.cwd())
100
+ const edge = resolve(boundary)
99
101
  const root = owningProject(start, edge)
100
102
  return root ? { root, basis: root === start ? "self" : "ancestor" } : null
101
103
  }
@@ -103,26 +105,26 @@ export function resolveLedgerRoot(target, boundary) {
103
105
  /** 无根文案(单源——`openLedger` 写面 ∥ `ledgerDbPath` ∥ 工具面读面(`ledger-tools.mjs`)共用同一句):
104
106
  * **只陈述事实**——起点 + 上溯上界(界内无项目根),不写「祖先链无已注册项目」这类越界断言:
105
107
  * 归属根可能就在界**之上**(界外)——那一格该句为假,读它会教人误判层级。
106
- * 锚态 = 工作区时追报下辖候选(报明面读数 = `projectRootView` 的 `workspace` 态——
107
- * **只供报明,不参与取根**:取根仍以会话锚为界)。
108
+ * 锚态 = 工作区时追报下辖候选(报明面读数 = 统一解析层的 `ambiguous.candidates`——
109
+ * **只供报明,不参与取根**:取根仍以会话锚为界,`resolveLedgerRoot` 零改)。
108
110
  * @param {string} start 起点(动作目标路径)
109
- * @param {string} [boundary] 上溯上界(会话锚;缺省 = `process.cwd()`)——与 `resolveLedgerRoot` 同一缺省 */
111
+ * @param {string} boundary 上溯上界(会话锚——**必传**)——与 `resolveLedgerRoot` 同一口径 */
110
112
  export const noRootMessage = (start, boundary) => {
111
- const view = projectRootView(start)
112
- const edge = resolve(boundary ?? process.cwd())
113
- const tail = view.state === "workspace" ? `;下辖候选项目:${view.candidates.join(" ∥ ")}` : ""
113
+ const picked = resolvePathTarget(start, undefined, { kind: "dir", order: "root-first" })
114
+ const edge = resolve(boundary)
115
+ const tail = picked.state === "ambiguous" ? `;下辖候选项目:${picked.candidates.join(" ∥ ")}` : ""
114
116
  return `台账:自 ${start} 未找到项目根(向上找到 ${edge} 为止)${tail}`
115
117
  }
116
118
 
117
119
  /** 项目根 → 库路径(稳定键 = `ledgerKey` 前 16 位——路径唯一标识,不含分隔符)。
118
120
  * **归单源**(2026-10-16 批 ledger-root-derivation):根 = `resolveLedgerRoot(cwd, boundary)`——与读面 / 写面
119
121
  * 同一推导;无根 ⇒ 抛**同一**无根文案(键需根,不产第二答案)。
120
- * `boundary` = 上溯上界(**会话锚**——设计档 §3.8「界(单源)」):调用方知道锚就传;
121
- * 缺省 = `process.cwd()`(CLI / 进程锚 = 会话锚的调用面不必传)。
122
+ * `boundary` = 上溯上界(**会话锚**——设计档 §3.8「界(单源)」):由调用方指名(**必传**);
123
+ * 进程工作目录不是界(CLI 的锚只是端 adapter 在装配期写入 `agent.cwd` 的取值)。
122
124
  * v2 键归一(2026-09-25 批):盘符本大写(CLI)⇒ 键恒等零变;小写盘符(VSC `uri.fsPath`)
123
125
  * 并入同键 ⇒ 同项目两端同库(收正前 = 同项目两键两库)。
124
126
  * @param {string} [cwd] 动作目标路径
125
- * @param {string} [boundary] 上溯上界(会话锚;缺省 = `process.cwd()`) */
127
+ * @param {string} boundary 上溯上界(会话锚——**必传**) */
126
128
  export function ledgerDbPath(cwd, boundary) {
127
129
  const start = resolve(cwd ?? ".")
128
130
  const derived = resolveLedgerRoot(start, boundary)
@@ -133,33 +135,32 @@ export function ledgerDbPath(cwd, boundary) {
133
135
  /** 未决四态(「活条目」口径 = 计数单源 WHERE 集;已核销 / 已废弃 = 归档态)。 */
134
136
  export const PENDING_STATUSES = ["待讨论", "待设计", "在途", "待核销"]
135
137
 
136
- /** 轻通道轮次行标记(单源——LIGHT-CHANNEL.md §2.3 开轮挂账 ∥ §2.8 闸凭据 ∥ §2.5 核销标记门
137
- * 判据三面同一字面;消费面 = `agent/light-round.mjs`(经 `ledger.mjs` re-export 动态解构——
138
- * W8 契约②零新静态边)∥ `ledger-cmd.mjs` 核销标记门)。 */
139
- export const ROUND_MARKER = "收尾链待跑"
140
-
141
138
  /** 老化阈值(天——口径 = 需求档;消费面 = `buildScan`(展示,`days` 可覆盖)+ `ledgerQuery` 逐行
142
139
  * `aged`(查询面,`now` 可注入))。2026-10-07 批 ledger-tool:自 `ledger.mjs` 迁入(枚举常量档单源
143
140
  * ——`ledgerQuery` 计算面直引本档,消 `ledger-cmd ↔ ledger.mjs` 新环;`ledger.mjs` 同名 re-export
144
141
  * 保公共面零变——LEDGER.md §13.4)。 */
145
142
  export const AGING_DAYS = 30
146
143
 
147
- /** `evidence` 软上限(字——设计档 `LEDGER.md` §5 P4 读数面单源):超限**只出读数,不拒写**
148
- * (软上限非闸门——用户裁定「无限机检反感」;读数面 = `query` 逐行 `evidenceOversize` ∥
149
- * `count` 回报)。 */
150
- export const EVIDENCE_SOFT_LIMIT = 300
144
+ /** `evidence` 判为太长的上限(字——设计档 `LEDGER.md` §5 P4 读数面单源):过了这条线即判为太长,
145
+ * 但**只出读数,不拒写**(读数非闸门——用户裁定「无限机检反感」;读数面 = `query` 逐行
146
+ * `evidenceTooLong` ∥ `count` 回报)。 */
147
+ export const EVIDENCE_TOO_LONG_MAX = 300
151
148
 
152
149
  /** 台账库结构版本(N2 标记**单源**——`PRAGMA user_version` 落标:写面开库 `v < 本值 ⇒ 置`;
153
150
  * 读面如实回读(旧库未标 = 0);结构变更(新列 / 新表)同步升号——2026-10-03 read-data-interface 批。
154
151
  * 2 = `resume_when` 可空列(设计档 §3.4 P5)。 */
155
152
  export const LEDGER_SCHEMA_VERSION = 2
156
153
 
157
- /** 六态允许迁移表(ledgerUpdate 迁移前判,不在表内 → 拒——规格 ②.2;设计档 §2.1)。 */
154
+ /** 六态允许迁移表(ledgerUpdate 迁移前判,不在表内 → 拒——规格 ②.2;设计档 §2.1)。
155
+ * 2026-10-27 批(三链统一)补两条边:
156
+ * - `待讨论 → 在途`:建档事务的必经一步(新建行只能先 INSERT `待讨论`——`在途` 被 DDL 咬合 CHECK 强制
157
+ * 非空指针;落档后才迁 `在途` 并写指针。没有这条边,`batch create` 写不出来);
158
+ * - `待核销 → 在途`:重开回收(批档重开 ⇒ 行回工作位)。六态唯一的回退边,人面出路与机器径同一条。 */
158
159
  export const ALLOWED_MIGRATIONS = {
159
- "待讨论": ["待设计", "已废弃"],
160
+ "待讨论": ["待设计", "在途", "已废弃"],
160
161
  "待设计": ["在途", "已废弃"],
161
162
  "在途": ["待核销", "已废弃"],
162
- "待核销": ["已核销", "已废弃"],
163
+ "待核销": ["已核销", "在途", "已废弃"],
163
164
  "已核销": ["已废弃"],
164
165
  "已废弃": [],
165
166
  }
@@ -238,8 +239,8 @@ function markSchemaVersion(db) {
238
239
  * **只读态**(readOnly=true——读面专用):只读句柄 + 零 DDL / ALTER + items 探针(见 openReadOnly);
239
240
  * 如实副作用:旧库(缺 executor 列)**读**不再自动补列——补列归写面开库;读出行缺列按 null 归一。
240
241
  * 「目录不存在」的真错照抛(fail-loud——先于推导,文案逐字零改)。
241
- * `boundary` = 上溯上界(**会话锚**——设计档 §3.8「界(单源)」):工具面传 `ctx.agent.cwd`;
242
- * 缺省 = `process.cwd()`(CLI / 进程锚 = 会话锚的调用面不必传)。 */
242
+ * `boundary` = 上溯上界(**会话锚**——设计档 §3.8「界(单源)」):工具面传 `ctx.agent.cwd`(**必传**;
243
+ * 缺则该值缺失即失败,不落 `process.cwd()`——进程工作目录不是界)。 */
243
244
  export function openLedger(cwd, { create = false, readOnly = false, boundary } = {}) {
244
245
  const start = resolve(cwd ?? ".")
245
246
  if (!existsSync(start)) {
@@ -98,9 +98,9 @@ function fromKeyRefusal(fromKeys, dir) {
98
98
  return null
99
99
  }
100
100
 
101
- /** 迁移计划(dry-run / confirm 共用)。cwd 口径 = 写门口径 2 同一子表达式(根推导单源 `resolveLedgerRoot(cwd)?.root` ∨ 兜底)——与实开库(`openLedger`)同源。 */
101
+ /** 迁移计划(dry-run / confirm 共用)。cwd 口径 = 写门口径 2 同一子表达式(根推导单源 `resolveLedgerRoot(cwd, cwd)?.root` ∨ 兜底)——与实开库(`openLedger`)同源。界 = 目标自身(不再向上找——同 `findProject` 形)。 */
102
102
  export function planLedgerMigration(cwd, { fromKeys = [] } = {}) {
103
- const projectRoot = resolveLedgerRoot(cwd)?.root ?? resolve(cwd ?? ".")
103
+ const projectRoot = resolveLedgerRoot(cwd, cwd)?.root ?? resolve(cwd ?? ".")
104
104
  const dir = ledgerDirPath()
105
105
  const targetKey = ledgerKey(projectRoot)
106
106
  const targetPath = join(dir, `${targetKey}.db`)
@@ -194,7 +194,7 @@ export function runLedgerMigrate(args, { cwd = process.cwd(), out = console.log,
194
194
  // 步 3 单事务迁入:BEGIN → 逐行 INSERT(重发新 id)→ 事务内读回核验 → COMMIT;失败 ⇒ ROLLBACK。
195
195
  const report = { version: 1, batch, at: now.toISOString(), projectRoot: plan.projectRoot, targetKey: plan.targetKey, targetPath: plan.targetPath, targetCreated: !targetExists, sources: [], skippedRows: [], flags: plan.flags, recycled: [], kept: [] }
196
196
  let db
197
- try { db = openLedger(plan.projectRoot, { create: true }) } catch (e) { err(`Refused: 目标库不可开(${e.message})。`); return 1 }
197
+ try { db = openLedger(plan.projectRoot, { create: true, boundary: plan.projectRoot }) } catch (e) { err(`Refused: 目标库不可开(${e.message})。`); return 1 }
198
198
  try {
199
199
  db.exec(`PRAGMA busy_timeout = ${Math.max(0, Math.floor(busyMs))}`)
200
200
  const existing = readRows(db)
@@ -255,7 +255,7 @@ export function runLedgerMigrate(args, { cwd = process.cwd(), out = console.log,
255
255
  }
256
256
 
257
257
  /** ③ 审计(只读——零删改):逐 `*.db` 出 `{键, 文件, 大小, mtime, 行数, 状态分布, 定性}`;归因 = **候选根集合**(锚项目 + `--root`)的变体键匹配(sha1 不可逆——只做候选生成)。 */
258
- export function auditLedgerDir({ cwd = process.cwd(), roots = [], dir = ledgerDirPath() } = {}) {
258
+ export function auditLedgerDir({ cwd, roots = [], dir = ledgerDirPath() } = {}) {
259
259
  let names = []
260
260
  try { names = readdirSync(dir).filter((n) => n.endsWith(".db")).sort() } catch { /* 不可读 ⇒ 空报告 */ }
261
261
  const anchorKeys = new Set()
package/ledger-read.mjs CHANGED
@@ -34,28 +34,30 @@ function exportRow(r, full) {
34
34
  }
35
35
 
36
36
  /** 单项目读(**两开均只读态**——版本探针开 + 行集开,§2.3):库不在 / 非台账库 ⇒ null(项目不出项)。
37
- * 不可读(坏档)⇒ 抛(单范围 = 读错转 exit 1;族范围 = 调用侧捕获跳过)。 */
38
- function readProject(root, full) {
39
- const probe = openLedger(root, { readOnly: true })
37
+ * 不可读(坏档)⇒ 抛(单范围 = 读错转 exit 1;族范围 = 调用侧捕获跳过)。
38
+ * `boundary` = 上溯上界(会话锚)——**必传**:缺值即当场失败(不再落 `process.cwd()`——那正是台账 #1834
39
+ * 的静默空账:进程 cwd 与目标项目不同时 `openLedger` 判越界 ⇒ 返回 null「该项目没有台账」,与真无库不可区分)。 */
40
+ function readProject(root, full, boundary) {
41
+ const probe = openLedger(root, { readOnly: true, boundary })
40
42
  if (!probe) return null
41
43
  let schemaVersion
42
44
  try { schemaVersion = Number(probe.prepare("PRAGMA user_version").get().user_version ?? 0) } finally { probe.close() }
43
- const rows = ledgerQuery({ cwd: root })
45
+ const rows = ledgerQuery({ cwd: root, boundary })
44
46
  return { root, name: basename(root), schemaVersion, rows: rows.map((r) => exportRow(r, full)) }
45
47
  }
46
48
 
47
49
  /** 统一序列化单源(N1——两出口共用;载荷 = §3.2 冻结形)。
48
50
  * 单范围 = 当前项目一项;`family=true` = 族内发现序逐项(不可读跳过——余者照常)。 */
49
51
  export function ledgerExport({ cwd, family = false, full = false } = {}) {
50
- const anchor = cwd ?? process.cwd()
52
+ const anchor = cwd
51
53
  if (!family) {
52
- const derived = resolveLedgerRoot(anchor)
53
- const item = derived ? readProject(derived.root, full) : null
54
+ const derived = resolveLedgerRoot(anchor, anchor)
55
+ const item = derived ? readProject(derived.root, full, anchor) : null
54
56
  return { projects: item ? [item] : [] }
55
57
  }
56
58
  const projects = []
57
59
  for (const p of discoverFamily(anchor).projects) {
58
- try { const item = readProject(p.root, full); if (item) projects.push(item) } catch { /* 不可读跳过 */ }
60
+ try { const item = readProject(p.root, full, anchor); if (item) projects.push(item) } catch { /* 不可读跳过 */ }
59
61
  }
60
62
  return { projects }
61
63
  }
@@ -14,7 +14,7 @@ import { ledgerDirPath } from "./ledger-db.mjs"
14
14
  /** 单次扫描(直驱面——timer 包装见 `startLedgerSurface`)。**async**(F-LX1:await 判活解析——
15
15
  * LEDGER.md §7.3.1 ④ 读面不阻塞事件循环)。 */
16
16
  export async function runLedgerScan({ state, agent = null, anchor = null, render = () => {} } = {}) {
17
- const base = anchor ?? agent?.cwd ?? process.cwd()
17
+ const base = anchor ?? agent?.cwd
18
18
  const family = discoverFamily(base)
19
19
  const scans = []
20
20
  for (const p of family.projects) {
package/ledger-tools.mjs CHANGED
@@ -24,10 +24,10 @@ const ptrPath = (v) => {
24
24
  }
25
25
 
26
26
  /** 会话锚供值面(**单源**——`cwdOf` 的缺省起点与上溯上界(`boundary`)同一表达式):
27
- * `ctx.agent.cwd` > `ctx.cwd` > 进程 cwd。界 = 会话锚(设计档 §3.8「界(单源)· 两面两界」)——
27
+ * `ctx.agent.cwd` > `ctx.cwd`(零进程缺省:工具面拿不到给值时当场失败,不换界)。界 = 会话锚(设计档 §3.8「界(单源)· 两面两界」)——
28
28
  * 工具面传给 `resolveLedgerRoot` / `openLedger` 的上界恒取本值,不取 `process.cwd()`(desktop / VSC
29
29
  * 宿主下两者不同:会话锚才是「不能跑到工作目录以外去」的那道界)。 */
30
- const anchorOf = (ctx) => ctx?.agent?.cwd ?? ctx?.cwd ?? process.cwd()
30
+ const anchorOf = (ctx) => ctx?.agent?.cwd ?? ctx?.cwd
31
31
 
32
32
  /** 工具 cwd 供值面:**起点 = 动作目标路径**(`docs/core/requirements/ENGINEERING-MODE-V2-SPEC-MANIFEST.md:72`
33
33
  * 「参数按用点解析——动作作用于哪个项目的路径,就取那个项目的档」)。
@@ -189,9 +189,9 @@ const ACTION_SPECS = {
189
189
  properties: {
190
190
  cwd: { type: "string", description: CWD_DESC },
191
191
  id: { type: "number", description: "条目 id(与 batch 恰携其一)" },
192
- batch: { type: "string", description: "批档指针(与 id 恰携其一)——批级批量结算:只销已到 待核销 的行;待讨论 / 待设计 点名跳过(追认核销不入批量面)" },
193
- status: { type: "string", enum: ["已核销", "已废弃"], description: "目标态(勾销 / 追认核销 / 撤回)——携 batch 时可不给(缺省 = 勾销单值),给了只能是 已核销" },
194
- evidence: { type: "string", description: "最小证据行(可选;传入 = 追加缺省——旧值非空 ⇒ 拼 `旧|新`(「|」零空格)、旧空直写;新值全空白 ⇒ 拒;覆盖 = 与 `evidenceReplace: true` 同传。追认核销面判结果值须非空:缺 / 全空白 ⇒ 拒)" },
192
+ batch: { type: "string", description: "批档指针(与 id 恰携其一)——批级批量结算:只销已到 待核销 的行;待讨论 / 待设计 点名跳过(须先 update 到 在途 认领);在途 点名跳过(不可跳)" },
193
+ status: { type: "string", enum: ["已核销", "已废弃"], description: "目标态(勾销 / 撤回)——携 batch 时可不给(缺省 = 勾销单值),给了只能是 已核销" },
194
+ evidence: { type: "string", description: "最小证据行(可选;传入 = 追加缺省——旧值非空 ⇒ 拼 `旧|新`(「|」零空格)、旧空直写;新值全空白 ⇒ 拒;覆盖 = 与 `evidenceReplace: true` 同传)" },
195
195
  evidenceReplace: { type: "boolean", description: "覆盖旗(布尔):与 evidence 同传 = 整段替换(旧文零保留);缺省 / false = 追加;旗独传(无新文)⇒ 拒" },
196
196
  },
197
197
  additionalProperties: false,
@@ -237,7 +237,7 @@ const UNION_PARAMETERS = {
237
237
  board: { type: "string", description: "归属板块(需求档文档名;add 可空 / update 缺省 = 不变 / query 过滤)" },
238
238
  req_doc: { type: "string", description: "需求档指针(add 可空 / update 缺省 = 不变)" },
239
239
  task_book: { type: "string", description: "任务书指针(add 可空 / update 缺省 = 不变;在途 / 待核销必填——咬合 CHECK)" },
240
- evidence: { type: "string", description: "最小证据行(add 可空;update / close 传入 = 追加缺省——旧空直写、旧非空拼 `旧|新`(「|」零空格)、新值全空白 ⇒ 拒;覆盖 = 与 `evidenceReplace: true` 同传;update 缺省 = 不变 / close 缺省 = 判行值;追认核销须非空)" },
240
+ evidence: { type: "string", description: "最小证据行(add 可空;update / close 传入 = 追加缺省——旧空直写、旧非空拼 `旧|新`(「|」零空格)、新值全空白 ⇒ 拒;覆盖 = 与 `evidenceReplace: true` 同传;update 缺省 = 不变 / close 缺省 = 判行值)" },
241
241
  evidenceReplace: { type: "boolean", description: "覆盖旗(布尔):与 evidence 同传 = 整段替换;缺省 / false = 追加;旗独传(无新文)⇒ 拒(update / close)" },
242
242
  trigger: { type: "string", enum: ["归批", "条件", "认账不排期"], description: "技术待办触发(add / update 可空 / query 过滤)" },
243
243
  resume_when: { type: "string", description: "复启条件句(add / update 可空;值非空——空串 / 全空白 ⇒ 拒;`trigger=条件` 行的一句话条件 + 可选锚 `@YYYY-MM-DD` ∥ `@事件件名`)" },
@@ -305,14 +305,14 @@ function makeLedgerTool(actions, { readonly = false, writeFace = false } = {}) {
305
305
  executorSessionId = getSessionId()
306
306
  }
307
307
  // 批级批量(§3.3)——`batch` 与 `id` 恰携其一(守卫 P7 已先裁)
308
- if (batch != null) return JSON.stringify({ ...ledgerUpdateBatch({ cwd: target, batch, patch, executorSessionId, boundary: anchor }) })
309
- return JSON.stringify({ ...ledgerUpdate({ cwd: target, id, patch, executorSessionId, boundary: anchor }) })
308
+ if (batch != null) return JSON.stringify({ ...ledgerUpdateBatch({ cwd: target, batch, patch, executorSessionId, boundary: anchor, agent: ctx?.agent }) })
309
+ return JSON.stringify({ ...ledgerUpdate({ cwd: target, id, patch, executorSessionId, boundary: anchor, agent: ctx?.agent }) })
310
310
  }
311
311
  case "close": {
312
312
  const target = startOf(ctx, anchor, rest.cwd, rest.batch, rest.task_book)
313
313
  // 批级批量结算(§3.3——勾销径)
314
- if (rest.batch != null) return JSON.stringify({ ...ledgerCloseBatch({ cwd: target, batch: rest.batch, evidence: rest.evidence, evidenceReplace: rest.evidenceReplace, boundary: anchor }) })
315
- return JSON.stringify({ ...ledgerClose({ cwd: target, id: rest.id, status: rest.status, evidence: rest.evidence, evidenceReplace: rest.evidenceReplace, boundary: anchor }) })
314
+ if (rest.batch != null) return JSON.stringify({ ...ledgerCloseBatch({ cwd: target, batch: rest.batch, evidence: rest.evidence, evidenceReplace: rest.evidenceReplace, boundary: anchor, agent: ctx?.agent }) })
315
+ return JSON.stringify({ ...ledgerClose({ cwd: target, id: rest.id, status: rest.status, evidence: rest.evidence, evidenceReplace: rest.evidenceReplace, boundary: anchor, agent: ctx?.agent }) })
316
316
  }
317
317
  case "query": {
318
318
  const target = startOf(ctx, anchor, rest.cwd, rest.task_book)
@@ -35,7 +35,7 @@ export function _resetLedgerVariantNoticeForTest() { noticeChecked = false }
35
35
  * @param {{cwd?: string, dir?: string, locale?: string, exists?: (p: string) => boolean}} [opts]
36
36
  * @returns {string|null} 单行文案 / null
37
37
  */
38
- export function ledgerVariantNotice({ cwd = process.cwd(), dir = ledgerDirPath(), locale = "en", exists = defaultExists } = {}) {
38
+ export function ledgerVariantNotice({ cwd, dir = ledgerDirPath(), locale = "en", exists = defaultExists } = {}) {
39
39
  if (noticeChecked) return null
40
40
  noticeChecked = true
41
41
  try {