thincoder 0.12.62 → 0.12.64

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 (231) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +46 -49
  3. package/bin/thincoder.mjs +68 -34
  4. package/package.json +7 -6
  5. package/src/acp/bridge.mjs +35 -15
  6. package/src/acp/client-caps.mjs +86 -0
  7. package/src/acp/ext.mjs +86 -0
  8. package/src/acp/handlers-session.mjs +257 -0
  9. package/src/acp/handlers-slots.mjs +196 -0
  10. package/src/acp/login.mjs +48 -0
  11. package/src/acp/session.mjs +6 -4
  12. package/src/acp.mjs +67 -379
  13. package/src/cli/distill-command.mjs +3 -3
  14. package/src/cli/make-agent.mjs +60 -17
  15. package/src/cli/memory-command.mjs +3 -3
  16. package/src/cli/permission.mjs +4 -48
  17. package/src/cli/setup-wizard.mjs +1 -1
  18. package/src/completions.mjs +3 -1
  19. package/src/crash-reports.mjs +1 -1
  20. package/src/distill.mjs +4 -4
  21. package/src/heap-watch.mjs +1 -1
  22. package/src/prompt-injections.mjs +20 -0
  23. package/src/tui/agent-turn.mjs +40 -9
  24. package/src/tui/cmd-advisor.mjs +5 -5
  25. package/src/tui/cmd-config.mjs +8 -8
  26. package/src/tui/cmd-eng.mjs +35 -9
  27. package/src/tui/cmd-mcp.mjs +9 -8
  28. package/src/tui/cmd-model.mjs +1 -1
  29. package/src/tui/cmd-new.mjs +6 -5
  30. package/src/tui/cmd-plan.mjs +9 -0
  31. package/src/tui/cmd-reindex.mjs +1 -1
  32. package/src/tui/cmd-restore.mjs +2 -2
  33. package/src/tui/cmd-session.mjs +24 -1
  34. package/src/tui/cmd-skills.mjs +1 -1
  35. package/src/tui/cmd-think.mjs +22 -9
  36. package/src/tui/config-helpers.mjs +1 -1
  37. package/src/tui/display-budget.mjs +33 -11
  38. package/src/tui/index.mjs +18 -10
  39. package/src/tui/interaction.mjs +16 -7
  40. package/src/tui/key-modes.mjs +9 -4
  41. package/src/tui/ledger-surface.mjs +22 -59
  42. package/src/tui/model-catalog.mjs +4 -4
  43. package/src/tui/model-picker.mjs +8 -7
  44. package/src/tui/mouse.mjs +11 -6
  45. package/src/tui/pickers.mjs +15 -2
  46. package/src/tui/render-conversation.mjs +1 -1
  47. package/src/tui/render-frame.mjs +15 -6
  48. package/src/tui/render-loop.mjs +1 -1
  49. package/src/tui/render-segments.mjs +3 -1
  50. package/src/tui/slash-commands.mjs +1 -1
  51. package/src/tui/startup.mjs +14 -14
  52. package/src/tui/subagent-blocks.mjs +20 -3
  53. package/src/tui/subagent-freeze.mjs +73 -2
  54. package/src/tui/suspension-drive.mjs +57 -23
  55. package/src/tui/tool-events.mjs +11 -8
  56. package/src/tui/tui-lifecycle.mjs +9 -0
  57. package/src/tui/wizard.mjs +3 -3
  58. package/src/tui/wrapped-spawn.mjs +6 -2
  59. package/src/abort-provenance.mjs +0 -116
  60. package/src/advisor/citations.mjs +0 -139
  61. package/src/advisor/compaction.mjs +0 -174
  62. package/src/advisor/convergence.mjs +0 -80
  63. package/src/advisor/history.mjs +0 -77
  64. package/src/advisor/loop.mjs +0 -293
  65. package/src/advisor/messages.mjs +0 -299
  66. package/src/advisor/project-context.mjs +0 -194
  67. package/src/advisor/repos.mjs +0 -150
  68. package/src/advisor/run.mjs +0 -293
  69. package/src/advisor/truncate.mjs +0 -57
  70. package/src/advisor.mjs +0 -290
  71. package/src/agent/completion.mjs +0 -146
  72. package/src/agent/dispatch.mjs +0 -489
  73. package/src/agent/helpers.mjs +0 -384
  74. package/src/agent/post-turn.mjs +0 -70
  75. package/src/agent/record-results.mjs +0 -174
  76. package/src/agent/relay-prefix.mjs +0 -39
  77. package/src/agent/run-stages.mjs +0 -244
  78. package/src/agent/setup-reminders.mjs +0 -69
  79. package/src/agent/setup.mjs +0 -354
  80. package/src/agent/spawn-child.mjs +0 -243
  81. package/src/agent-tools/advisor-async.mjs +0 -346
  82. package/src/agent-tools/advisor-settle.mjs +0 -231
  83. package/src/agent-tools/advisor.mjs +0 -260
  84. package/src/agent-tools/async-settle.mjs +0 -204
  85. package/src/agent-tools/batch-segment.mjs +0 -195
  86. package/src/agent-tools/consult.mjs +0 -473
  87. package/src/agent-tools/design-token.mjs +0 -117
  88. package/src/agent-tools/digest-budget.mjs +0 -76
  89. package/src/agent-tools/eng.mjs +0 -67
  90. package/src/agent-tools/escalate-async.mjs +0 -295
  91. package/src/agent-tools/goal.mjs +0 -119
  92. package/src/agent-tools/plan.mjs +0 -81
  93. package/src/agent-tools/read-history.mjs +0 -309
  94. package/src/agent-tools/recent-changes.mjs +0 -24
  95. package/src/agent-tools/review-streak.mjs +0 -93
  96. package/src/agent-tools/settings.mjs +0 -265
  97. package/src/agent-tools/skill.mjs +0 -47
  98. package/src/agent-tools/subagent-actions.mjs +0 -482
  99. package/src/agent-tools/subagent-async.mjs +0 -434
  100. package/src/agent-tools/subagent-panel.mjs +0 -160
  101. package/src/agent-tools/subagent-run.mjs +0 -205
  102. package/src/agent-tools/subagent-scheduler.mjs +0 -392
  103. package/src/agent-tools/subagent-spawn.mjs +0 -459
  104. package/src/agent-tools/subagent.mjs +0 -404
  105. package/src/agent-tools/task.mjs +0 -87
  106. package/src/agent-tools/timer.mjs +0 -46
  107. package/src/agent-tools/verify.mjs +0 -271
  108. package/src/agent-tools.mjs +0 -17
  109. package/src/agent.mjs +0 -417
  110. package/src/auto-think.mjs +0 -115
  111. package/src/config-migrate.mjs +0 -70
  112. package/src/config.mjs +0 -496
  113. package/src/context.mjs +0 -392
  114. package/src/conventions.mjs +0 -223
  115. package/src/embedding.mjs +0 -120
  116. package/src/escape.mjs +0 -152
  117. package/src/expand-home.mjs +0 -16
  118. package/src/explore-distill.mjs +0 -155
  119. package/src/generate-title.mjs +0 -88
  120. package/src/git/checkpoint.mjs +0 -448
  121. package/src/git/gitmem.mjs +0 -100
  122. package/src/hooks.mjs +0 -97
  123. package/src/ledger.mjs +0 -227
  124. package/src/log.mjs +0 -195
  125. package/src/markdown.mjs +0 -106
  126. package/src/mcp/helpers.mjs +0 -51
  127. package/src/mcp/transport-http.mjs +0 -248
  128. package/src/mcp/transport-stdio.mjs +0 -140
  129. package/src/mcp/transport-ws.mjs +0 -122
  130. package/src/mcp.mjs +0 -295
  131. package/src/memory/code-index.mjs +0 -219
  132. package/src/memory/code-sync.mjs +0 -415
  133. package/src/memory/core.mjs +0 -299
  134. package/src/memory/delete.mjs +0 -236
  135. package/src/memory/docs.mjs +0 -419
  136. package/src/memory/file-walk.mjs +0 -109
  137. package/src/memory/scan.mjs +0 -95
  138. package/src/memory/schema.mjs +0 -452
  139. package/src/memory.mjs +0 -21
  140. package/src/model-ref.mjs +0 -66
  141. package/src/model-specs.mjs +0 -179
  142. package/src/peer-domains.mjs +0 -265
  143. package/src/peer-instances.mjs +0 -231
  144. package/src/prompt-overlays.mjs +0 -82
  145. package/src/prompts/advisor-design.md +0 -41
  146. package/src/prompts/advisor-round1.md +0 -41
  147. package/src/prompts/advisor-round2.md +0 -46
  148. package/src/prompts/advisor-round3.md +0 -42
  149. package/src/prompts/common.md +0 -115
  150. package/src/prompts/consult-base.md +0 -19
  151. package/src/prompts/discipline-engineering.md +0 -258
  152. package/src/prompts/discipline-normal.md +0 -185
  153. package/src/prompts/persona-coder.md +0 -21
  154. package/src/prompts/persona-eng-coder.md +0 -37
  155. package/src/prompts/persona-eng-designer.md +0 -60
  156. package/src/prompts/persona-engineering.md +0 -55
  157. package/src/prompts/persona-explore.md +0 -15
  158. package/src/prompts/persona-normal.md +0 -27
  159. package/src/prompts/persona-plan.md +0 -26
  160. package/src/provider/anthropic.mjs +0 -225
  161. package/src/provider/core.mjs +0 -476
  162. package/src/provider/errors.mjs +0 -101
  163. package/src/provider/google.mjs +0 -257
  164. package/src/provider/index.mjs +0 -7
  165. package/src/provider/list-models.mjs +0 -93
  166. package/src/provider/normalize.mjs +0 -81
  167. package/src/provider/rate.mjs +0 -108
  168. package/src/provider/responses.mjs +0 -495
  169. package/src/provider/retry.mjs +0 -88
  170. package/src/provider/sse.mjs +0 -264
  171. package/src/proxy.mjs +0 -261
  172. package/src/rules.mjs +0 -53
  173. package/src/session-gc.mjs +0 -221
  174. package/src/session-guard.mjs +0 -59
  175. package/src/session-migrate.mjs +0 -48
  176. package/src/session-rename.mjs +0 -38
  177. package/src/session-segments.mjs +0 -100
  178. package/src/session-slots.mjs +0 -492
  179. package/src/session-store.mjs +0 -441
  180. package/src/session.mjs +0 -492
  181. package/src/skills.mjs +0 -153
  182. package/src/text-budget.mjs +0 -46
  183. package/src/token-ttl.mjs +0 -274
  184. package/src/tools/apply_patch.md +0 -15
  185. package/src/tools/bash.md +0 -37
  186. package/src/tools/bash.mjs +0 -268
  187. package/src/tools/checklist-sync.mjs +0 -181
  188. package/src/tools/checklist.md +0 -13
  189. package/src/tools/checklist.mjs +0 -299
  190. package/src/tools/delete.md +0 -13
  191. package/src/tools/edit-batch.mjs +0 -191
  192. package/src/tools/edit-diff.mjs +0 -348
  193. package/src/tools/edit.md +0 -30
  194. package/src/tools/execute.md +0 -21
  195. package/src/tools/execute.mjs +0 -228
  196. package/src/tools/fetch.md +0 -12
  197. package/src/tools/file.mjs +0 -469
  198. package/src/tools/file_ops.md +0 -17
  199. package/src/tools/get_current_time.md +0 -8
  200. package/src/tools/git-checkpoint.mjs +0 -143
  201. package/src/tools/git-ext.mjs +0 -173
  202. package/src/tools/git.md +0 -54
  203. package/src/tools/git.mjs +0 -356
  204. package/src/tools/glob-dialect.mjs +0 -130
  205. package/src/tools/glob.md +0 -11
  206. package/src/tools/grep.md +0 -19
  207. package/src/tools/hashline_edit.md +0 -14
  208. package/src/tools/index.mjs +0 -36
  209. package/src/tools/insert_after.md +0 -15
  210. package/src/tools/lint.md +0 -10
  211. package/src/tools/linter.mjs +0 -128
  212. package/src/tools/ls.md +0 -12
  213. package/src/tools/lsp.md +0 -10
  214. package/src/tools/lsp.mjs +0 -316
  215. package/src/tools/ops.mjs +0 -299
  216. package/src/tools/patch.mjs +0 -282
  217. package/src/tools/process.md +0 -10
  218. package/src/tools/question.md +0 -16
  219. package/src/tools/question.mjs +0 -26
  220. package/src/tools/read.md +0 -20
  221. package/src/tools/read_image.md +0 -8
  222. package/src/tools/repomap.mjs +0 -314
  223. package/src/tools/search.mjs +0 -236
  224. package/src/tools/shared.mjs +0 -446
  225. package/src/tools/tree.md +0 -14
  226. package/src/tools/tree.mjs +0 -66
  227. package/src/tools/wait_for.md +0 -22
  228. package/src/tools/web.mjs +0 -224
  229. package/src/tools/websearch.md +0 -16
  230. package/src/tools/write.md +0 -11
  231. package/src/traces/trace-store.mjs +0 -355
@@ -9,6 +9,11 @@
9
9
  * digest)输入禁用(INPUT-LOCK-ASYNC C'——提交吞——2026-09-09)、settle 事件驱动
10
10
  * auto-turn 消化(手动档 organize-only / AUTO 档全语义)、池空 + 无待处理输入 → 补发
11
11
  * done 冻结自然退出。状态机行表见 AGENT-LOOP.md §9.2。
12
+ * F-UC7(2026-09-19 批,AGENT-LOOP-SUBAGENT.md §6.27.12):第二开轮源 = 未 drain 的上行
13
+ * ask(`upstreamWaiting` 谓词——ask 入队即唤醒本驱动);唤醒轮同走 auto-turn(旗标
14
+ * `upstreamTurn` 供核域文本选择)。
15
+ * F-UC8(2026-09-21 批,§6.27.12.13):可见提示面**按因两档**(ask 携「谁 + 啥」——
16
+ * 核单点 `upstreamAskLabelVars` / digest)+ 起跑数行与轮尾收尾行同守 `pend0 > 0`。
12
17
  */
13
18
 
14
19
  // 函数级静态环(2026-09-05):drive 的 digestTurn/用户回合经 runAgentTurn 递归进入
@@ -16,15 +21,21 @@
16
21
  import { runAgentTurn } from "./agent-turn.mjs"
17
22
  import { freezeAllSubTasks, freezeReclaimDigestedBlocks } from "./subagent-blocks.mjs"
18
23
  import { sweepToolBlocks } from "./tool-events.mjs"
19
- import { logEvent } from "../log.mjs"
24
+ import { logEvent } from "@thincoder/core/log.mjs"
20
25
  import { C } from "./ansi.mjs"
21
26
  // ASYNC-RESULT-CONTAINER.md D1/D2:池 accessor(双池 absorb)+ pending 单容器停靠
22
- import { getAsyncPool, parkAsyncPending, releaseSettledEntry } from "../agent-tools/async-settle.mjs"
27
+ import { getAsyncPool, parkAsyncPending, releaseSettledEntry } from "@thincoder/core/agent-tools/async-settle.mjs"
28
+ // F-UC7(AGENT-LOOP-SUBAGENT.md §6.27.12——2026-09-19 批):上行 ask 开轮谓词单点(核导出)
29
+ // F-UC8(§6.27.12.13 ②——2026-09-21 批):ask 提示行携参单点(同档导出——显示面单源)
30
+ import { upstreamAskLabelVars, upstreamWaiting } from "@thincoder/core/agent-tools/parent-channel.mjs"
31
+ // X9(2026-09-20 端差·显示面消差批):消化收尾文案单源 = 核 i18n 容器(禁第三份字面)
32
+ import { t } from "@thincoder/core/i18n.mjs"
33
+ // 批 4 CLI-ASYNC-DISCARD(AGENT-LOOP-SUBAGENT.md §6.20):中止分支「只清已死」收尾单点
34
+ import { discardAbortedPool, discardAbortedAdvisors } from "@thincoder/core/agent-tools/async-discard.mjs"
23
35
 
24
36
  // INPUT-LOCK-ASYNC(C'——2026-09-09,本档 INPUT-LOCK-ASYNC.md):R15 排队
25
37
  // 用户指令合并(§11.3 D-24c——攒批计划/合并文案/上限常量)整批废弃
26
38
  // ——busy 提交吞 + pendingInput 单槽化(至多一条待交接——单消息逐发不攒批)。
27
- // 废弃记录见 AGENT-LOOP.md §11.3。
28
39
 
29
40
  /** 后台池计数(LOGGING susp/digest 事件字段——pendingN/poolN)。
30
41
  * poolN = _asyncSubagents + _asyncAdvisors(§11.2 D-24b advisor 池同面板计数——queued
@@ -153,27 +164,47 @@ function backgroundStatusText(agent) {
153
164
  /** 消化轮:系统驱动的 auto-turn(D-S6)。手动档不传权限/问答 handler(D-S7 装配
154
165
  * 契约——denied 不弹面板、不悬挂);AUTO 档沿用普通回调(autoApprove 短路自动
155
166
  * 执行)。_suspended 保持 true:消化中 settle 延迟冻结 + 移交 pending。R17:pending
156
- * 计数/消化触发 = 任一 pending 族(T-R17j——consult/escalate 空闲 settle 也触发消化轮)。 */
157
- async function digestTurn(ctx) {
167
+ * 计数/消化触发 = 任一 pending 族(T-R17j——consult/escalate 空闲 settle 也触发消化轮)。
168
+ * F-UC7(§6.27.12.5 D):`upstream` = 未 drain 的 ask 在场(唤醒轮)——旗标随 auto 轮
169
+ * 贯通到核 `runAgent`(域文本选择面)、`digest:*` 载荷条件携带 `upstream: true`(宿主
170
+ * 日志区分唤醒轮与 digest 轮——两端同规)。
171
+ * F-UC8(§6.27.12.13 ①–③):可见提示面**按因两档 × 全档**——标签行 ask 携「谁 + 啥」
172
+ * (核容器键,CLI 零自持字面)/ digest(manual 与 AUTO 同判——泛句退场);起跑数行
173
+ * 与轮尾收尾行同守 `pend0 > 0`(ask-only 轮两行皆不出)。 */
174
+ async function digestTurn(ctx, upstream = false) {
158
175
  const { agent, pushLine } = ctx
159
176
  const manual = !agent.autoApprove
160
- pushLine(manual
161
- ? "[auto-turn: digesting finished subagent reports…]"
162
- : "[auto-turn: continuing background work…]", C.dim)
177
+ // 起跑数前置(§6.27.12.13 ③):起跑行与轮尾收尾行同源(皆 = 起跑口径)
178
+ const pend0 = pendingFamilyCount(agent)
179
+ // 标签两档(§6.27.12.13 ①):ask 因恒优先——携参单源 = 核 `upstreamAskLabelVars`
180
+ // (队首 ask 的 `from` + 单行归一截断 `msg`);digest 因沿用既有字面;AUTO 档同判
181
+ // (泛句无生产者——开轮因穷尽;模式可见性另有 `AUTO│` 横幅载体)。
182
+ const ask = upstream ? upstreamAskLabelVars(agent) : null
183
+ pushLine(ask ? t("digest.turnLabelAsk", ask) : t("digest.turnLabel"), C.dim)
184
+ // 起跑数行(对位 VSC `.digest-status` 元素):`n > 0` 规则——ask-only 轮(`n = 0`)零行
185
+ if (pend0 > 0) pushLine(t("digest.start", { n: pend0 }), C.dim)
163
186
  const digestCtx = manual
164
187
  ? { ...ctx, askPermission: null, askBatchPermission: null, askQuestion: null }
165
188
  : ctx
166
189
  // LOGGING:digest:* 事件(D-S9 消化轮边界——LOGGING.md F-L4 挂起态覆盖)
167
190
  const d0 = Date.now()
168
- const pend0 = pendingFamilyCount(agent)
169
- logEvent("digest:start", { pendingN: pend0 })
170
- await runAgentTurn(digestCtx, "", { autoTurn: true, skipSession: true })
171
- logEvent("digest:end", { pendingN: pendingFamilyCount(agent), ms: Date.now() - d0 })
191
+ logEvent("digest:start", { pendingN: pend0, ...(upstream ? { upstream: true } : {}) })
192
+ const outcome = await runAgentTurn(digestCtx, "", { autoTurn: true, upstreamTurn: upstream, skipSession: true })
193
+ logEvent("digest:end", { pendingN: pendingFamilyCount(agent), ms: Date.now() - d0, ...(upstream ? { upstream: true } : {}) })
194
+ // X9(2026-09-20 端差·显示面消差批):轮尾**可见**收尾行(修前只有 `digest:end` 日志事件——
195
+ // 用户不可见;对位 VSC `webview/chat.js` `showDigestStatus` end 段)。计数口径 = **起跑数**
196
+ // `pend0`(与 VSC `suspension.mjs` 起跑 post 同源);文案单源 = 核 i18n `digest.done` /
197
+ // `digest.aborted`;终态 ≠ ok(中止/失败)⇒ aborted 形态(VSC ok 旗标同口径)。
198
+ // F-UC8 守卫(§6.27.12.13 ③):`pend0 = 0`(ask-only 轮)⇒ **零收尾行**——与起跑行
199
+ // 同规则(两行成对);done / aborted 两形态同判(VSC 零动作守卫先于 ok 判)。
200
+ const seconds = ((Date.now() - d0) / 1000).toFixed(1)
201
+ if (pend0 > 0) pushLine(t(outcome === "ok" ? "digest.done" : "digest.aborted", { n: pend0, seconds }), C.dim)
172
202
  }
173
203
 
174
204
  /**
175
205
  * §17 挂起会话驱动(D-S9 行表;由 runAgentTurn 回合尾进入,池空自然退出):
176
206
  * - suspension:池项 settle → 入 pending → 开 auto-turn(合并消化近邻 settle);
207
+ * 上行 ask 入队 → 唤醒 + 谓词 → 开唤醒轮(F-UC7——谓词先于池空退出判,见第 2 步);
177
208
  * 挂起空闲用户 Enter → pendingInput 单槽(busy 含 digest 提交吞——INPUT-LOCK-ASYNC)
178
209
  * ——用户输入优先于 digest;
179
210
  * - auto-turn:消化中 settle 不并发开新轮(单 runAgent 循环),轮末按 pending/池态
@@ -224,10 +255,13 @@ export async function suspensionSession(ctx) {
224
255
  state.status = backgroundStatusText(agent)
225
256
  continue
226
257
  }
227
- // 2. pending 任一族非空 → 合并消化轮(注入由 runAgent 首行统一完成——D-S3 单注入点;
228
- // R17 判据推广——consult/escalate 族同样触发——T-R17j)
229
- if (pendingFamiliesNonEmpty(agent)) {
230
- await digestTurn(ctx)
258
+ // 2. pending 任一族非空**或存在未 drain 的 ask** → 合并消化轮 / 唤醒轮(注入由 runAgent
259
+ // 首行 + 循环头统一完成——D-S3 单注入点;R17 判据推广——consult/escalate 族同样触发
260
+ // ——T-R17j。F-UC7(§6.27.12.5 D):谓词**必须先于第 3 步池空退出判**——「池空 +
261
+ // 队列留 ask」(子代理已 settle 且报告已消化)仍须开一轮把它 drain 出来)
262
+ const upstream = upstreamWaiting(agent)
263
+ if (pendingFamiliesNonEmpty(agent) || upstream) {
264
+ await digestTurn(ctx, upstream)
231
265
  // §17.5.5 实测修订(2026-09-03):digest 消化完成(pending 条目已注入)→ 逐条补发
232
266
  // done 冻结回收——不等池空——块从面板移除进流(settle 锚点 splice 落位——digest
233
267
  // 总览文本之前——round1 #1 裁定);池空 freeze-out 仅兜底未消化残项(挂起会话
@@ -256,15 +290,15 @@ export async function suspensionSession(ctx) {
256
290
  state._suspWake = null
257
291
  state.suspAbortArmed = false // round2 偏差 #4:会话退出即解除挂起中止武装(防跨会话粘滞)
258
292
  if (aborted) {
259
- // §15 abort 语义:清池不注入(用户显式停——不注入陈旧错误)
260
- agent._asyncSubagents?.clear()
261
- agent._asyncAdvisors?.clear()
262
- agent._asyncQueue = []
293
+ // §15 abort 语义:只清已死条目(AGENT-LOOP-SUBAGENT.md §6.20——墓碑/出池/队列剔除/
294
+ // 整批一次提醒;存活与已 settle 者留池消化——不注入陈旧错误)。
295
+ discardAbortedPool(agent)
296
+ discardAbortedAdvisors(agent)
263
297
  // ASYNC-RESULT-CONTAINER.md D2:pending 单容器——中止清容器不注入陈旧结果(四族
264
298
  // 统一一处清;consult 会话标记 stopped——settle 不入 digest 流——T-R17c)+
265
299
  // children abort。
266
300
  agent._pendingAsyncResults = []
267
- const { cleanupConsultSessions } = await import("../agent-tools/consult.mjs")
301
+ const { cleanupConsultSessions } = await import("@thincoder/core/agent-tools/consult.mjs")
268
302
  cleanupConsultSessions(agent)
269
303
  // §17 round2 偏差 #2-CLI(code review round2 #2-CLI)+ INPUT-LOCK 单槽化:中止时
270
304
  // 不静默丢弃挂起期输入——Enter 已清空输入框并入 pendingInput(用户视为已发送)——
@@ -278,8 +312,8 @@ export async function suspensionSession(ctx) {
278
312
  // D-S3 ③ 兜底:退出前残余(极端竞态)直注入再退——结果零丢失(AC-S2;
279
313
  // ASYNC-RESULT-CONTAINER.md D2:pending 单容器一处清——注入器按 role 分发
280
314
  // (consult → injectConsultResult;其余 → injectAsyncResult——四族同容器)。
281
- const { injectAsyncResult } = await import("../agent-tools/subagent.mjs")
282
- const { injectConsultResult } = await import("../agent-tools/consult.mjs")
315
+ const { injectAsyncResult } = await import("@thincoder/core/agent-tools/subagent.mjs")
316
+ const { injectConsultResult } = await import("@thincoder/core/agent-tools/consult.mjs")
283
317
  const residual = agent._pendingAsyncResults
284
318
  if (residual?.length) {
285
319
  for (const e of residual.splice(0)) {
@@ -19,7 +19,7 @@
19
19
  import { C } from "./ansi.mjs"
20
20
  import { formatToolSummary } from "./tool-summaries.mjs"
21
21
  import { describeToolArgs, toolArgsLines } from "./tool-args.mjs"
22
- import { ADVISOR_THINKING_PLACEHOLDER, resolveAdvisorProvider } from "../advisor/run.mjs"
22
+ import { ADVISOR_THINKING_PLACEHOLDER, resolveAdvisorProvider } from "@thincoder/core/advisor/run.mjs"
23
23
  import {
24
24
  SUBAGENT_ROLES, routeSubToken, routeSubReasoning, routeSubToolCall,
25
25
  routeSubToolOutput, finishSubTask, finishSubTaskKey, finishSubTasksByRole, freezeDoneSubTasks,
@@ -27,8 +27,10 @@ import {
27
27
  shiftFreezeAnchors,
28
28
  } from "./subagent-blocks.mjs"
29
29
  // 第 27 批 §12.3②:前缀正则换名 + import 源改文法模块(纯换名——语义零改)。
30
- import { RELAY_PREFIX_RE } from "../agent/relay-prefix.mjs"
31
- import { TURN_CAP_MARK, STOPPED_MARK } from "../agent/spawn-child.mjs"
30
+ import { RELAY_PREFIX_RE } from "@thincoder/core/agent/relay-prefix.mjs"
31
+ import { TURN_CAP_MARK, STOPPED_MARK } from "@thincoder/core/agent/spawn-child.mjs"
32
+ // 批 1 CORE-DEFECT-FIXES B3:onWait 相位值域 + 文案单源(消费面禁各自枚举——PROVIDER.md §6.20)
33
+ import { waitStatusText } from "@thincoder/core/provider/wait-status.mjs"
32
34
  // 2026-09-05 module-split:ticks/maps/sweep/slim/settle/探测/find 族迁 tool-display.mjs——
33
35
  // buildToolCallbacks 内部引用用本地 import;sweepToolBlocks re-export(agent-turn 消费面)
34
36
  import {
@@ -402,11 +404,12 @@ export function buildToolCallbacks(deps) {
402
404
  state.tokens.cacheMiss += usage.prompt_cache_miss_tokens ?? 0
403
405
  state.tokens.reasoningTokens += usage.completion_tokens_details?.reasoning_tokens ?? 0
404
406
  },
405
- // Throttle wait (active gate / 429 backoff): show in status bar so user knows it's not frozen
406
- onWait: ({ phase, seconds }) => {
407
- if (phase === "gate") state.status = `TPM throttle wait ~${seconds}s`
408
- else if (phase === "overloaded") state.status = `Server overloaded, retrying in ${seconds}s`
409
- else state.status = `Rate-limited 429, retry in ${seconds}s`
407
+ // Throttle wait (active gate / 429 backoff): show in status bar so user knows it's not frozen.
408
+ // 相位值域/文案取自核单源(wait-status.mjs)——未知相位与 warn 前置告警不显示(不落兜底误标)。
409
+ onWait: (ev) => {
410
+ const s = waitStatusText(ev)
411
+ if (!s) return
412
+ state.status = s
410
413
  render()
411
414
  },
412
415
  onTaskUpdate: (items) => {
@@ -15,6 +15,15 @@ export function writeStartupSequence(write = (s) => process.stdout.write(s)) {
15
15
  write(ansi.altBuffer + ansi.hideCursor + ansi.mouseOn + ansi.bracketedPasteOn + ansi.keyboardPush + ansi.modifyOtherKeysOn + ansi.wrapOff)
16
16
  }
17
17
 
18
+ // 启动加载画面(2026-09-21 用户):双进程冷启动(wrapped spawn + 全链 import)期间 alt buffer
19
+ // 全黑 → 用户以为死了。序列后立即写一行 loading(首帧 render 自然覆盖 ✗ 无需清除逻辑 ✗)。
20
+ // 版本号由调用方传入(bin 入口已读 package.json——零重复 IO);wrapped 父进程同款(spawn 前写——
21
+ // 父子两段冷启动都非黑)。纯文本行 ✗ 无 ANSI 位置控制 ✗ 首帧覆盖依赖 render-loop 全屏重绘 ✓
22
+ export function writeLoadingLine(write = (s) => process.stdout.write(s), version = "") {
23
+ const v = version ? ` v${version}` : ""
24
+ try { write(`\n\n ◌ thincoder loading...${v}\n`) } catch { /* 尽力面——TTY 不可写不阻断启动 */ }
25
+ }
26
+
18
27
  /** TUI 清理序列 = 异常退出恢复序列(单一来源——§9.2 表 2 候选 1):清屏 + 关闭鼠标/
19
28
  * 粘贴/键盘增强 + 退出 alt buffer + 显示光标 + 恢复环绕。
20
29
  * TUI-OOM-ROOTCAUSE(CRASH-REPORTS.md §9.3):提取为导出常量 `RECOVERY_SEQUENCE`——
@@ -6,7 +6,7 @@
6
6
  * ctx: { agent, state, pushLine, pushLabel, render, persistRaw, openModelPicker }
7
7
  */
8
8
 
9
- import { PROVIDER_PRESETS as PRESETS } from "../config.mjs"
9
+ import { PROVIDER_PRESETS as PRESETS } from "@thincoder/core/config.mjs"
10
10
  import { ansi, C } from "./ansi.mjs"
11
11
  import { computeLayout } from "./layout.mjs"
12
12
  import { probeChannelModels } from "./model-catalog.mjs"
@@ -202,7 +202,7 @@ export function createWizard(ctx) {
202
202
  agent.provider = { ...agent.providers.find((p) => p.name === f.name) }
203
203
  agent.provider.model = agent.activeModel
204
204
  if (agent.config?.agent?.compactThresholdAuto) {
205
- const { resolveCompactThreshold } = await import("../config.mjs")
205
+ const { resolveCompactThreshold } = await import("@thincoder/core/config.mjs")
206
206
  agent.config.agent.compactThreshold = resolveCompactThreshold(null, agent.provider).value
207
207
  }
208
208
  // agent.config 是 loadConfig merged——无 active* 键可写——defaultModel 随内存 merged 更新
@@ -226,7 +226,7 @@ export function createWizard(ctx) {
226
226
  agent.config.embedding ??= {}
227
227
  agent.config.embedding.apiKey = f.embedkey
228
228
  if (agent.memory && !agent.memory.embedder) {
229
- const { createEmbedder } = await import("../embedding.mjs")
229
+ const { createEmbedder } = await import("@thincoder/core/embedding.mjs")
230
230
  agent.memory.embedder = createEmbedder(agent.config.embedding)
231
231
  }
232
232
  pushLine(`Vector search enabled (${agent.config.embedding.model ?? "BAAI/bge-m3"})`, C.tool)
@@ -2,11 +2,11 @@
2
2
  * spawn 子(自身 bin)tee stderr → 终端 + crash-reports/tui-stderr-<ts>-<pid>.log(外部终止/
3
3
  * native abort——fd 2 进程内不可改——诊断唯一默认捕获路)。子死 → 日志收尾 → 同码退(null 映射
4
4
  * code??(signal?1:0)——评审 #1);spawn error → 注日志 + exit 1(评审 #5——不挂死)。 */
5
- import { appendFileSync, mkdirSync, writeFileSync } from "node:fs"
5
+ import { appendFileSync, mkdirSync, writeFileSync, readFileSync } from "node:fs"
6
6
  import { spawn } from "node:child_process"
7
7
  import { fileURLToPath } from "node:url"
8
8
  import { crashReportsDir } from "../crash-reports.mjs"
9
- import { RECOVERY_SEQUENCE } from "./tui-lifecycle.mjs"
9
+ import { RECOVERY_SEQUENCE, writeLoadingLine } from "./tui-lifecycle.mjs"
10
10
 
11
11
  // F-3 信号语义(raw mode 既有 key-handler 双按语义——Ctrl+C = stdin 字节不产生信号 → 子正常退 → 父收
12
12
  // exit 同码退):父忽略 SIGINT/SIGTERM——tee 不被打断。2026-09-09 实测:Windows process.kill(SIGINT)
@@ -21,6 +21,10 @@ export function spawnTuiWrapped({ dir = crashReportsDir(), script = fileURLToPat
21
21
  logPath = `${dir}/tui-stderr-${Date.now()}-${process.pid}.log`
22
22
  writeFileSync(logPath, `# tui-stderr ${new Date().toISOString()} pid=${process.pid} argv=${JSON.stringify(process.argv)}\n`, { flag: "a", mode: 0o600 })
23
23
  } catch { return false }
24
+ // ①⸸ 启动加载行(spawn 前——回车后立刻可见 ✗ 冷启动期非黑):复用 tui-lifecycle.writeLoadingLine(修正轮 F-A ✗ 父段副本删)
25
+ let v = ""
26
+ try { v = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8")).version } catch { /* 尽力面——读失败 → 无版本尾 */ }
27
+ writeLoadingLine(writeImpl, v)
24
28
  process.on("SIGINT", ignoreSignal); process.on("SIGTERM", ignoreSignal) // 父忽略信号(F-3)——tee 不被打断
25
29
  process.stderr.on("error", ignoreSignal) // 父 stderr 异步 EPIPE(终端/管道已关)→ 不炸父——tee 继续
26
30
  const note = (s) => { try { appendFileSync(logPath, s) } catch { /* 落盘尽力面——不阻断 tee */ } }
@@ -1,116 +0,0 @@
1
- /**
2
- * abort-provenance.mjs — abort 来源标注词汇表(单一权威源——AGENT-LOOP.md §20.3 第 1/2 条)
3
- *
4
- * 子代理 / 评审 / 会诊死亡的可诊断性:产生点标注结构化来源(`err.abortInfo`)→
5
- * controller 链 hop 逐跳保 reason(§20.3 第 4 条 #10)→ 结算 / 报告面用 `deathLine`
6
- * 合成死亡行(原 message 前缀逐字保留 + 来源后缀)。
7
- *
8
- * 纯函数、零 import——任意层可引、无环(provider / proxy / agent / settle 族共用)。
9
- * 词汇表:
10
- * - trigger(5 值):`user`(reason.interrupt)· `timeout`(TimeoutError / 定时器面)·
11
- * `cancel`(定向中止)· `stop`(整批 / 会话 / 回合级停)· `unknown`(残留 / 未标注——告警态)。
12
- * - layer(3 值):`provider` / `agent` / `settle`;未标注错误回落 `unrecorded`(合成器 token)。
13
- */
14
-
15
- /** trigger 枚举权威(测试锁计数 5——AGENT-LOOP.md §20.3 第 1 条表)。 */
16
- export const TRIGGERS = ["user", "timeout", "cancel", "stop", "unknown"]
17
-
18
- /**
19
- * trigger 判定(判据序 = §20.3 第 1 条表;signal.reason 四形态:
20
- * ① `{interrupt:true(,message)}`(既有 user 面)② `TimeoutError`(原生——timeout 面)
21
- * ③ `{abortTrigger:"cancel"|"stop"|"timeout"(,abortDetail)}`(新增——程序性中止)
22
- * ④ 缺失 / 无已知键(→ unknown——诊断告警态,不得静默)。
23
- */
24
- export function triggerOf(signal) {
25
- const reason = signal?.reason
26
- if (reason == null) return "unknown"
27
- if (reason.interrupt === true) return "user"
28
- if (reason.name === "TimeoutError" || reason.abortTrigger === "timeout") return "timeout"
29
- if (reason.abortTrigger === "cancel") return "cancel"
30
- if (reason.abortTrigger === "stop") return "stop"
31
- return "unknown"
32
- }
33
-
34
- /**
35
- * 产生点错误(abort 面):AbortError + `reason` 透传 + `abortInfo` 标注。
36
- * message 取既有通用文案(既有前缀断言零回归——死亡行前缀逐字保留)。
37
- */
38
- export function abortError(signal, layer, detail) {
39
- const e = new DOMException("The operation was aborted", "AbortError")
40
- e.reason = signal?.reason
41
- e.abortInfo = { trigger: triggerOf(signal), layer, detail }
42
- return e
43
- }
44
-
45
- /**
46
- * 产生点错误(定时器面):自有 message + `abortInfo{trigger:"timeout"}`。
47
- * name 保持 "Error"(既有分类谓词——`AbortError` 分支——零变化)。
48
- */
49
- export function timeoutError(message, layer, detail) {
50
- const e = new Error(message)
51
- e.abortInfo = { trigger: "timeout", layer, detail }
52
- return e
53
- }
54
-
55
- /**
56
- * 外部错误补标(undici / fetch 拒否等):缺 `abortInfo` 才补(不覆盖产生点标注);
57
- * 不改 `name` / `message`(消费面零回归)。
58
- * 第 4 参 detail 为站点名(站点表逐站点传——自由短串)。
59
- */
60
- export function annotateAbort(err, signal, layer, detail) {
61
- if (err?.abortInfo) return err
62
- try {
63
- err.abortInfo = { trigger: triggerOf(signal), layer, detail: detail ?? "unrecorded" }
64
- } catch { /* frozen error——保持未标注(合成器 unknown 告警兜底) */ }
65
- return err
66
- }
67
-
68
- /** 死亡行长度上限(AGENT-LOOP.md §20.3 第 3 条——超长优先截 detail)。 */
69
- const MAX_LEN = 300
70
- /** unknown 形态 detail token(§20.3 第 3 条逐字)。 */
71
- const NO_REASON = "no reason on signal"
72
- /** 未标注回落 token(layer / detail 两面兜底)。 */
73
- const UNRECORDED = "unrecorded"
74
-
75
- /** 信号域 detail 回落(§20.3 第 1 条形态③——`abortDetail` 载站点名;错误未标注时仍可辨发起动作)。 */
76
- const detailOf = (signal) => (typeof signal?.reason?.abortDetail === "string" ? signal.reason.abortDetail : null)
77
-
78
- /** 求值链(§20.3 第 1 条:err.abortInfo → triggerOf(signal) → err.name 兜底)。 */
79
- function resolveAbort(err, signal) {
80
- const info = err?.abortInfo
81
- if (info?.trigger) return { trigger: info.trigger, layer: info.layer ?? UNRECORDED, detail: info.detail || null }
82
- const fromSignal = triggerOf(signal)
83
- if (fromSignal !== "unknown") return { trigger: fromSignal, layer: UNRECORDED, detail: detailOf(signal) }
84
- if (err?.name === "TimeoutError") return { trigger: "timeout", layer: UNRECORDED, detail: detailOf(signal) }
85
- // AbortError / 无名——告警态(unknown 显式,空后缀即违规)
86
- return { trigger: "unknown", layer: UNRECORDED, detail: NO_REASON }
87
- }
88
-
89
- /** detail 裁剪:优先截 detail(budget = 上限内剩余字符)。 */
90
- function clipDetail(detail, budget) {
91
- if (budget <= 0) return ""
92
- return detail.length <= budget ? detail : detail.slice(0, Math.max(1, budget - 1)) + "…"
93
- }
94
-
95
- /**
96
- * 死亡行合成器(§20.3 第 3 条——报告面单点):
97
- * `<原 message>[ ← cause: <cause.message>][ · abort(<trigger>@<layer>:<detail>)]`
98
- * - 原 message 前缀逐字保留(既有前缀 / 包含断言零回归);
99
- * - 后缀出现条件 = `err.abortInfo` 存在 ∨ `signal?.aborted` ∨ `err.name ∈ {AbortError, TimeoutError}`;
100
- * - cause 段(P1)独立于后缀("fetch failed" 类网络死亡真因同判);
101
- * - 总长 ≤300 字符(超长先截 detail,仍超再截尾)。
102
- */
103
- export function deathLine(err, signal) {
104
- let line = err?.message ?? String(err ?? "")
105
- if (err?.cause?.message) line += ` ← cause: ${err.cause.message}`
106
- const abortish = Boolean(err?.abortInfo) || Boolean(signal?.aborted) ||
107
- err?.name === "AbortError" || err?.name === "TimeoutError"
108
- if (abortish) {
109
- const { trigger, layer, detail } = resolveAbort(err, signal)
110
- const head = `${line} · abort(${trigger}@${layer}:`
111
- // unknown 形态逐字用告警 token(§20.3 第 3 条——站点 detail 不进告警行);非串 detail 归一化
112
- const text = trigger === "unknown" ? NO_REASON : (detail == null ? UNRECORDED : String(detail))
113
- line = `${head}${clipDetail(text, MAX_LEN - head.length - 1)})`
114
- }
115
- return line.length > MAX_LEN ? line.slice(0, MAX_LEN - 1) + "…" : line
116
- }
@@ -1,139 +0,0 @@
1
- /**
2
- * citations.mjs — host-verified citation checking (decision d698434).
3
- * Extracted from advisor/run.mjs (file-size split). The evidence rule becomes
4
- * a host fact: every `file:line: content` reference in a review is mechanically
5
- * checked against the CURRENT disk state; mismatches mark the finding
6
- * unverified and cannot support a push-back.
7
- *
8
- * 第 11 批(C / F14 / §14.5):解析候选 = cwd + **评审对象声明范围派生根**(声明仓根 /
9
- * 声明文件目录 / 声明目录——纯路径派生,零扫描、零 git);命中判据三条件全中(围栏内 ∧
10
- * 可读 ∧ 目标行含引文内容)⇒ 零新增假命中。失败原因三分(file unreadable /
11
- * content mismatch @ path / path traversal)——父侧不再人肉复核。
12
- */
13
- import { readFileSync, realpathSync } from "node:fs"
14
- import { resolve, relative, dirname, extname, sep, isAbsolute, join } from "node:path"
15
-
16
- // `file:line: content` citations — the file group is narrowed to source/config
17
- // extensions so URLs (`example.com:8080: …`) don't become false-positive
18
- // citations that fail as "file unreadable" in the verification report.
19
- // Single-letter extensions (c/h) are kept — false positives (e.g. "a.c:1: x")
20
- // are rare and only add a failed-citation line to the report; the report is
21
- // advisory for the parent agent, never a crash path.
22
- const CITATION_RE = /([\w./\\-]+\.(?:mjs|cjs|js|ts|jsx|tsx|mts|cts|py|rs|go|c|h|cpp|hpp|java|rb|php|sh|bash|json|md|markdown|mdx|yaml|yml|toml|css|html)):(\d+):\s*([^`\n]{4,})/g
23
-
24
- /** Extract `file:line: content` citations from a review text. */
25
- export function extractCitations(text) {
26
- const out = []
27
- for (const m of text.matchAll(CITATION_RE)) {
28
- out.push({ file: m[1], line: Number(m[2]), content: m[3].trim() })
29
- }
30
- return out
31
- }
32
-
33
- /** 候选解析根(§14.5——纯路径派生,候选顺序 = cwd → 各声明路径的派生根):
34
- * 声明仓根(`cwd/<segs[0]>`)与 声明文件目录 / 声明目录本身。声明在 cwd 之外 → 不派生
35
- * (relative 越出 cwd 或跨盘符——候选与声明脱节即假命中面)。 */
36
- function citationRoots(cwd, scope) {
37
- const base = resolve(cwd)
38
- const roots = [base]
39
- for (const s of Array.isArray(scope) ? scope : []) {
40
- if (typeof s !== "string" || !s.trim()) continue
41
- const abs = resolve(base, s)
42
- const rel = relative(base, abs)
43
- if (!rel || rel.startsWith("..") || isAbsolute(rel)) continue
44
- const segs = rel.split(/[\\/]/).filter(Boolean)
45
- if (segs.length === 0) continue
46
- const repoRoot = join(base, segs[0])
47
- const declaredDir = extname(abs) ? dirname(abs) : abs
48
- for (const r of [repoRoot, declaredDir]) if (!roots.includes(r)) roots.push(r)
49
- }
50
- return roots
51
- }
52
-
53
- /** 单引文解析(§14.5):按候选顺序试 `resolve(root, file)`;三条件全中才算命中。
54
- * @returns {{matched: true, root: string, resolved: string}|{matched: false, reason: string}} */
55
- function resolveCitation(citation, roots, base) {
56
- const fence = base + sep
57
- let mismatch = null
58
- let traversal = false
59
- for (const root of roots) {
60
- let real
61
- try {
62
- // Path confinement: citation paths are LLM-generated — never trust them.
63
- // A hallucinated "../config.json" would otherwise read (and leak via the
64
- // report) files outside the project, including API-key configs.
65
- // realpathSync resolves symlinks too — a link inside the project that
66
- // points outside must not pass the prefix check.
67
- real = realpathSync(resolve(root, citation.file))
68
- } catch {
69
- continue // 该候选无此文件(或不可解析)——试下一候选
70
- }
71
- if (!real.startsWith(fence)) { traversal = true; continue }
72
- let line = ""
73
- try {
74
- line = readFileSync(real, "utf8").split("\n")[citation.line - 1] ?? ""
75
- } catch {
76
- continue // 存在但读不了(目录等)——按未命中处理,试下一候选
77
- }
78
- if (line.includes(citation.content)) {
79
- return { matched: true, root, resolved: relative(base, real).split(sep).join("/") }
80
- }
81
- mismatch ??= real
82
- }
83
- if (mismatch) return { matched: false, reason: `content mismatch @ ${relative(base, mismatch).split(sep).join("/")}` }
84
- if (traversal) return { matched: false, reason: "path traversal" }
85
- return { matched: false, reason: "file unreadable" }
86
- }
87
-
88
- /**
89
- * Mechanically verify citations against the CURRENT file state: read the file,
90
- * take the exact line, check it CONTAINS the quoted content. Reports
91
- * N/M matched + the mismatches. Unverified citations cannot support a
92
- * push-back — the evidence rule becomes a host fact, not a prompt wish.
93
- * @param {string} text — the review text
94
- * @param {string} cwd — the agent's working directory (workspace root)
95
- * @param {{scope?: string[]}} [opts] — scope = 评审对象声明路径(documents + paths);
96
- * 省略 ⇒ 旧行为(仅 cwd 候选——签名向后兼容)。
97
- */
98
- export function verifyCitations(text, cwd, opts = {}) {
99
- const citations = extractCitations(text)
100
- const matched = []
101
- const failed = []
102
- const base = resolve(cwd)
103
- const roots = citationRoots(cwd, opts?.scope)
104
- for (const c of citations) {
105
- const r = resolveCitation(c, roots, base)
106
- if (r.matched) {
107
- // 命中根记录(§14.5):经派生根解析(非 cwd 直解)的命中在报告中注明解析路径。
108
- matched.push(r.root === base ? c : { ...c, root: r.root, resolved: r.resolved })
109
- } else {
110
- failed.push({ ...c, reason: r.reason })
111
- }
112
- }
113
- return { total: citations.length, matched, failed }
114
- }
115
-
116
- /** Append the verification report to the review text (visible to the parent agent). */
117
- export function appendCitationReport(text, cwd, opts = {}) {
118
- const { total, matched, failed } = verifyCitations(text, cwd, opts)
119
- if (total === 0) return text // no citations — nothing to verify
120
- const lines = [
121
- "",
122
- "---",
123
- `[host-verified] ${matched.length}/${total} citations match current file state.`,
124
- ]
125
- // 命中根透明(F14/§14.5):经声明范围派生根解析的命中逐条注明解析路径(cwd 直解的不列
126
- // ——零噪音;列的正是修复前会被误报为 unreadable 的裸路径引用)。
127
- const derived = matched.filter((c) => c.resolved)
128
- if (derived.length > 0) {
129
- lines.push("Citations resolved via the declared review scope (bare path — resolved root noted):")
130
- for (const c of derived.slice(0, 10)) lines.push(`- ${c.file}:${c.line} → ${c.resolved}`)
131
- }
132
- if (failed.length > 0) {
133
- lines.push("Citations that do NOT match the current file state (treat their claims as unverified):")
134
- for (const f of failed.slice(0, 10)) {
135
- lines.push(`- ${f.file}:${f.line}: ${f.content.slice(0, 80)}${f.reason ? ` (${f.reason})` : ""}`)
136
- }
137
- }
138
- return text + lines.join("\n")
139
- }