@thincoder/core 0.9.5 → 0.10.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 (215) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +1 -1
  3. package/abort-provenance.mjs +10 -10
  4. package/advisor/citations.mjs +5 -5
  5. package/advisor/compaction.mjs +16 -15
  6. package/advisor/loop.mjs +12 -12
  7. package/advisor/messages.mjs +6 -6
  8. package/advisor/project-context.mjs +5 -5
  9. package/advisor/repos.mjs +3 -3
  10. package/advisor/run.mjs +22 -12
  11. package/advisor.mjs +4 -4
  12. package/agent/assemble.mjs +111 -0
  13. package/agent/chat-call.mjs +68 -0
  14. package/agent/child-marks.mjs +2 -2
  15. package/agent/completion.mjs +9 -2
  16. package/agent/continue-decision.mjs +25 -0
  17. package/agent/dispatch-gates.mjs +140 -0
  18. package/agent/dispatch-run.mjs +167 -0
  19. package/agent/dispatch.mjs +54 -296
  20. package/agent/family-tools.mjs +5 -5
  21. package/agent/helpers.mjs +53 -3
  22. package/agent/live-beat.mjs +49 -0
  23. package/agent/post-turn.mjs +4 -8
  24. package/agent/record-results.mjs +23 -20
  25. package/agent/relay-prefix.mjs +2 -2
  26. package/agent/run-stages.mjs +15 -11
  27. package/agent/run-start.mjs +111 -0
  28. package/agent/setup-reminders.mjs +9 -9
  29. package/agent/setup.mjs +32 -4
  30. package/agent/spawn-child.mjs +9 -9
  31. package/agent/suspension.mjs +89 -29
  32. package/agent/timers.mjs +113 -0
  33. package/agent/turn-loop.mjs +244 -0
  34. package/agent/write-gate.mjs +103 -6
  35. package/agent-tools/advisor-async.mjs +9 -9
  36. package/agent-tools/advisor-settle.mjs +12 -12
  37. package/agent-tools/advisor.mjs +36 -41
  38. package/agent-tools/async-discard.mjs +44 -1
  39. package/agent-tools/async-settle.mjs +21 -18
  40. package/agent-tools/audit-block.mjs +7 -7
  41. package/agent-tools/bash-async.mjs +281 -0
  42. package/agent-tools/batch-lifecycle.mjs +57 -18
  43. package/agent-tools/batch-paths.mjs +52 -8
  44. package/agent-tools/batch-skeleton.mjs +19 -1
  45. package/agent-tools/batch.mjs +12 -65
  46. package/agent-tools/checkpoint.mjs +121 -0
  47. package/agent-tools/child-permission.mjs +5 -3
  48. package/agent-tools/consult.mjs +56 -56
  49. package/agent-tools/context.mjs +4 -20
  50. package/agent-tools/design-token.mjs +3 -3
  51. package/agent-tools/digest-budget.mjs +1 -1
  52. package/agent-tools/eng.mjs +5 -5
  53. package/agent-tools/escalate-async.mjs +16 -11
  54. package/agent-tools/goal.mjs +3 -10
  55. package/agent-tools/panel-blocks.mjs +2 -2
  56. package/agent-tools/parent-channel.mjs +3 -13
  57. package/agent-tools/plan.mjs +3 -4
  58. package/agent-tools/read-history.mjs +12 -28
  59. package/agent-tools/recent-changes.mjs +4 -5
  60. package/agent-tools/review-facts.mjs +1 -1
  61. package/agent-tools/settings.mjs +47 -20
  62. package/agent-tools/skill.mjs +3 -5
  63. package/agent-tools/spawn-gates.mjs +3 -3
  64. package/agent-tools/subagent-actions-query.mjs +270 -0
  65. package/agent-tools/subagent-actions.mjs +21 -265
  66. package/agent-tools/subagent-async.mjs +25 -20
  67. package/agent-tools/subagent-panel.mjs +33 -4
  68. package/agent-tools/subagent-run.mjs +36 -21
  69. package/agent-tools/subagent-scheduler.mjs +28 -27
  70. package/agent-tools/subagent-spawn.mjs +45 -14
  71. package/agent-tools/subagent.mjs +33 -53
  72. package/agent-tools/task.mjs +3 -9
  73. package/agent-tools/timer.mjs +12 -12
  74. package/agent-tools/verify.mjs +14 -13
  75. package/agent-tools.mjs +0 -1
  76. package/agent.mjs +18 -355
  77. package/attachments.mjs +125 -0
  78. package/auto-think.mjs +1 -1
  79. package/config-presets.mjs +11 -6
  80. package/config-watch.mjs +83 -0
  81. package/config.mjs +17 -4
  82. package/context-degrade.mjs +81 -0
  83. package/context-echo.mjs +88 -0
  84. package/context-push.mjs +51 -0
  85. package/context.mjs +8 -187
  86. package/conventions.mjs +110 -161
  87. package/declaration.mjs +194 -0
  88. package/embedding.mjs +3 -1
  89. package/env-ref.mjs +81 -0
  90. package/explore-distill.mjs +2 -3
  91. package/file-links.mjs +53 -0
  92. package/file-refs.mjs +103 -0
  93. package/generate-title.mjs +28 -4
  94. package/history-window.mjs +17 -2
  95. package/i18n.mjs +2 -0
  96. package/index-discover.mjs +7 -7
  97. package/ledger-cmd.mjs +34 -126
  98. package/ledger-db.mjs +10 -2
  99. package/ledger-tools.mjs +188 -0
  100. package/ledger.mjs +9 -4
  101. package/log.mjs +12 -7
  102. package/manifest-discovery.mjs +131 -0
  103. package/manifest-schema.mjs +166 -0
  104. package/manifest.mjs +18 -217
  105. package/mcp.mjs +4 -1
  106. package/memory/code-search.mjs +149 -0
  107. package/memory/code-sync.mjs +89 -230
  108. package/memory/core.mjs +47 -32
  109. package/memory/docs.mjs +63 -262
  110. package/memory/file-list.mjs +89 -0
  111. package/memory/file-walk.mjs +7 -4
  112. package/memory/memory-tool.mjs +229 -0
  113. package/memory/schema.mjs +18 -3
  114. package/memory/sweep.mjs +289 -0
  115. package/memory/sync-tail.mjs +81 -0
  116. package/memory-status.mjs +69 -0
  117. package/memory.mjs +3 -0
  118. package/model-specs.mjs +16 -7
  119. package/notify-policy.mjs +51 -0
  120. package/package.json +1 -1
  121. package/peer-claims.mjs +2 -2
  122. package/peer-domains.mjs +7 -11
  123. package/peer-instances.mjs +9 -10
  124. package/process-probe-exec.mjs +187 -0
  125. package/process-probe.mjs +27 -175
  126. package/prompt-overlays.mjs +11 -10
  127. package/prompts/advisor-design.md +2 -2
  128. package/prompts/advisor-round2.md +1 -1
  129. package/prompts/advisor-round3.md +1 -1
  130. package/prompts/common.md +16 -8
  131. package/prompts/discipline-engineering.md +57 -16
  132. package/prompts/discipline-normal.md +6 -3
  133. package/prompts/persona-coder.md +0 -1
  134. package/prompts/persona-eng-coder.md +1 -5
  135. package/prompts/persona-eng-designer.md +2 -3
  136. package/prompts/persona-engineering.md +32 -11
  137. package/prompts/persona-explore.md +0 -1
  138. package/prompts/persona-normal.md +1 -1
  139. package/prompts/persona-plan.md +3 -4
  140. package/prompts/subagent-base.md +14 -0
  141. package/provider/core.mjs +11 -48
  142. package/provider/list-models.mjs +3 -0
  143. package/provider/responses-request.mjs +237 -0
  144. package/provider/responses.mjs +6 -228
  145. package/provider-flows.mjs +248 -0
  146. package/queued.mjs +90 -0
  147. package/rules.mjs +209 -2
  148. package/session-guard.mjs +27 -7
  149. package/session-lifecycle.mjs +90 -18
  150. package/session-rename.mjs +5 -1
  151. package/session-slot-claims.mjs +230 -0
  152. package/session-slot-scan.mjs +299 -0
  153. package/session-slot-verify.mjs +304 -0
  154. package/session-slot-write.mjs +74 -8
  155. package/session-slots-manifest.mjs +163 -241
  156. package/session-slots.mjs +99 -71
  157. package/session.mjs +30 -10
  158. package/shell-candidates.mjs +78 -0
  159. package/text-budget.mjs +2 -2
  160. package/think-off.mjs +49 -0
  161. package/token-ttl.mjs +15 -4
  162. package/tool-args.mjs +73 -0
  163. package/tool-docs/advisor.md +9 -0
  164. package/tool-docs/batch.md +1 -0
  165. package/tool-docs/code_search.md +1 -0
  166. package/tool-docs/consult_start.md +5 -0
  167. package/tool-docs/consult_stop.md +3 -0
  168. package/tool-docs/context.md +7 -0
  169. package/tool-docs/doc_search.md +1 -0
  170. package/tool-docs/edit.md +2 -13
  171. package/tool-docs/eng.md +1 -0
  172. package/tool-docs/execute.md +1 -9
  173. package/tool-docs/git.md +25 -51
  174. package/tool-docs/goal.md +6 -0
  175. package/tool-docs/ledger_add.md +1 -0
  176. package/tool-docs/ledger_close.md +1 -0
  177. package/tool-docs/ledger_count.md +1 -0
  178. package/tool-docs/ledger_query.md +1 -0
  179. package/tool-docs/ledger_update.md +1 -0
  180. package/tool-docs/main_history.md +1 -0
  181. package/tool-docs/memory.md +10 -0
  182. package/tool-docs/notify_parent.md +9 -0
  183. package/tool-docs/peer_instances.md +1 -0
  184. package/tool-docs/plan.md +5 -0
  185. package/tool-docs/process.md +7 -3
  186. package/tool-docs/read_history.md +7 -0
  187. package/tool-docs/recent_changes.md +1 -0
  188. package/tool-docs/repo_outline.md +1 -0
  189. package/tool-docs/settings.md +6 -0
  190. package/tool-docs/skill.md +5 -0
  191. package/tool-docs/subagent.md +31 -0
  192. package/tool-docs/task.md +7 -0
  193. package/tool-docs/timer.md +6 -0
  194. package/tool-docs/verify.md +1 -0
  195. package/tool-docs/wait_for.md +2 -1
  196. package/tools/bash.mjs +28 -4
  197. package/tools/edit-batch.mjs +4 -4
  198. package/tools/edit-diff.mjs +4 -23
  199. package/tools/exec-run.mjs +102 -4
  200. package/tools/execute.mjs +5 -2
  201. package/tools/file.mjs +4 -9
  202. package/tools/git-run.mjs +1 -1
  203. package/tools/git.mjs +0 -24
  204. package/tools/index.mjs +2 -1
  205. package/tools/linter.mjs +2 -1
  206. package/tools/node-child.mjs +21 -0
  207. package/tools/ops.mjs +50 -13
  208. package/tools/patch.mjs +4 -2
  209. package/tools/repomap.mjs +20 -9
  210. package/tools/shared.mjs +8 -6
  211. package/tools/web.mjs +5 -2
  212. package/tools/write-path.mjs +1 -1
  213. package/traces/trace-store.mjs +17 -14
  214. package/vision-reader.mjs +76 -0
  215. package/agent-tools/batch-segment.mjs +0 -18
@@ -14,7 +14,7 @@
14
14
  * 2026-09-05 拆分轮: status/escalate/panel 动作执行器 → ./subagent-actions.mjs;§20
15
15
  * 调度器全套 → ./subagent-scheduler.mjs——本文件 import 源随之改写。
16
16
  * 2026-09-05 模块拆分轮(726 > 500 硬限): spawn 前置 helpers(summarizeEngTaskBook/
17
- * effectiveSubagentModel/resolveDesignSlot)+ §20 准入(prepareScheduling)+ child
17
+ * effectiveSubagentModel/resolveDesignSlot)+ AGENT-LOOP-SUBAGENT.md §6.9 准入(prepareScheduling)+ child
18
18
  * 装配(buildSpawnChild)→ ./subagent-spawn.mjs;async 分支(executeAsyncSpawn)
19
19
  * → ./subagent-run.mjs——execute 只保留动作分流 + 装配调用 + 阻塞路径。
20
20
  */
@@ -33,6 +33,8 @@ import { executeStatusAction, executeEscalateAction, executePanelAction, execute
33
33
  import { prepareScheduling, buildSpawnChild, executeConsumeDesignAction } from "./subagent-spawn.mjs"
34
34
  import { executeAsyncSpawn } from "./subagent-run.mjs"
35
35
  import { ROUND_VALUES } from "./spawn-gates.mjs"
36
+ // #15 描述外置:描述文本单点 = tool-docs/subagent.md(DESC 单解析面,缺档抛错语义不变)
37
+ import { DESC } from "../tools/shared.mjs"
36
38
 
37
39
  // ─── SYNC-CANCEL 纯函数(可测——无 io)──────────────────────────────────────────
38
40
 
@@ -71,7 +73,7 @@ export function classifySyncAbort(ctxSignal, baseSignal, ctrlSignal, err) {
71
73
  export function armSyncChildAbort(parent, key, baseSignal, announce = null) {
72
74
  const ctrl = new AbortController()
73
75
  if (baseSignal) {
74
- // §20.3 站点 #10(第 24 批):hop 逐跳保 reason(下游可判定「谁杀的」)
76
+ // AGENT-LOOP-SUBAGENT.md §6.12 站点 #10(第 24 批):hop 逐跳保 reason(下游可判定「谁杀的」)
75
77
  if (baseSignal.aborted) ctrl.abort(baseSignal.reason)
76
78
  else baseSignal.addEventListener("abort", () => ctrl.abort(baseSignal.reason), { once: true })
77
79
  }
@@ -102,9 +104,9 @@ export function buildSyncStoppedReport(role, capturedOutput, designId) {
102
104
  /**
103
105
  * subagent tool — ONE tool, EIGHT actions (AGENT-LOOP-SUBAGENT.md §6.7 +
104
106
  * SUBAGENT-OBSERVE-SEND): spawn (default) / status (non-blocking pool query) / observe
105
- * (inspect a running/queued/done async child's recent activity + current tool — §7.2) /
107
+ * (inspect a running/queued/done async child's recent activity + current tool — docs/cli/design/TUI.md §6.8) /
106
108
  * send (inject a direction into a RUNNING async child — consumed at its next turn
107
- * boundary as an ordinary instruction — §7.2) / escalate (飞刀 — hand implementation to
109
+ * boundary as an ordinary instruction — §6.8) / escalate (飞刀 — hand implementation to
108
110
  * a stronger model) / cancel (stop ONE background subagent — AGENT-LOOP-SUBAGENT.md §6.7.2) / panel (view + fix
109
111
  * the live subagent panel — AGENT-LOOP-SUBAGENT.md §6.7.2) / consume-design (parent-side chain-terminal token
110
112
  * consumption — ENGINEERING-MODE.md §2.6, 2026-09-07). The check
@@ -117,56 +119,34 @@ export function buildSyncStoppedReport(role, capturedOutput, designId) {
117
119
  * - no role specified — invalid by design since the 2026-08-25 fail-closed gate
118
120
  * (role is mandatory; "no role → same tool set as parent" was removed with the
119
121
  * coder-leak fix and the header text above predates it)
120
- * - non-recursive: child agents do not get the subagent tool (depth > 0 is not injected)
122
+ * - non-recursive for non-eng children: child agents do not get the subagent tool (depth > 0 is not
123
+ * injected); eng-coder / eng-designer children get a spawn-only variant (`agent/family-tools.mjs`)
124
+ * - 描述文本单点 = `thincoder-core/tool-docs/subagent.md`(模型可见文本不在此复述——本块只留实现注记)
121
125
  */
122
126
 
123
127
  export const subagentTool = {
124
128
  name: "subagent",
125
- description:
126
- "ONE tool, EIGHT actions — pick by what you need:\n" +
127
- "- action:'spawn' (DEFAULT): spawn a sub-agent to handle an independent subtask in an isolated context; the sub-agent returns only its final report. Spawn MULTIPLE subagents in the SAME response for parallel work—they run concurrently.\n" +
128
- "- action:'status': NON-BLOCKING progress query — returns immediately and consumes nothing. Give the spawn's id for one child ({id, role, status: running|queued|done, model, elapsedSec, turn, maxTurns, position?), or omit it for an overview of the whole pool ({overview: {running: [{id, role, model, elapsedSec, turn, maxTurns}], queued: [{id, role, position}], done: [{id, role}]}}). Touched-files summary: running entries also carry touchedFiles (first 5, relative to your cwd), touchedMore (count beyond 5) and, when nothing was touched yet, the placeholder touched (\"—(尚无改动)\"); queued (not yet started) entries carry the placeholder touched (\"—(未启动)\") — see what a running child has changed BEFORE deciding to cancel it. Use THIS to see progress — it never blocks and never consumes a result (async results are delivered to you automatically). Background advisor reviews share this status surface: pass a review id and it answers in the same shape with role:'advisor' plus reviewType (design|code) / round / elapsedSec — and the overview lists reviews alongside subagents.\n" +
129
- "- action:'observe': SEE what a running async subagent is DOING right now (progress vs stuck) — pass the spawn id. Returns {id, role, status, turn, maxTurns, touched…, currentTool? — array of tool name(s) currently executing (read from its dispatch state; omitted when none in flight — present when stuck in a long tool call), recentTurns: [last N one-line turn summaries, newest-first; default 5, parameterizable via recent]}. Readonly — observable on running/queued/done: running shows live activity, queued (not started) returns a placeholder, done (settled, report auto-delivered) returns the activity summary only — NOT the full report (that rides the auto channel; observe stays terse to keep your context lean). Use it to judge whether a long-running child is stuck vs progressing BEFORE deciding to cancel or steer it.\n" +
130
- "- action:'send': STEER a running async subagent mid-flight — pass the spawn id + message (a direction like \"check X, don't fixate on Y\"). The message queues and the child consumes it at its next turn boundary as an ORDINARY user instruction (non-interrupting — its current tool finishes first; its convergence/audit discipline is unchanged — injection is guidance, not a deviation waiver). Returns {id, status:'delivered', queued}. Only a RUNNING async subagent is targetable — sync (you're waiting on it, no relay), queued (not started), settled or unknown ids error clearly. If the child settles before its next turn boundary, its settle report carries an 'undelivered' note so you don't assume the guidance landed.\n" +
131
- "- action:'escalate' (飞刀 — a flown-in expert): hand an implementation task to a STRONGER model from your consult models (agent.consultModels). It gets WRITE access and does the work itself — reads, edits, runs tests — then returns a post-op report (what changed, why, verification). Use it when YOU judge the task calls for stronger hands (complex multi-file refactoring, an intractable bug, intricate algorithm work — or work beyond your comfortable ability); escalate EARLY, not after burning attempts. model: pick a candidate as 'provider:model' (default = the first consult model). Not available in engineering mode (implementation goes through eng-coder spawns there). DEFAULT-ASYNC at depth 0: the launch returns an ack {id, role:'escalate', status:'running'} and the flight runs in the background (pooled with the other role-domain spawns) — its post-op report is delivered to you automatically with its mutations merged into your bookkeeping.\n" +
132
- "- action:'cancel': STOP one background subagent — pass the id from the async spawn return (REQUIRED — omitting it errors; a blanket cancel is unsupported, Ctrl+C stops everything). Running target aborts immediately ({id, status:'cancelled'}); a queued target is removed from the queue ({id, status:'cancelled', was:'queued'} and later queue positions shift forward). Other children and the session keep running — cancellation is targeted. Use it when a background child is going the wrong way (e.g. burning turns) and you must stop it before its report arrives. Cancel is a last resort: verify alarming signals with reliable checks (git/node — not guesses) first; prefer scoped recovery (restore a single affected file) over killing the child — a running child's in-flight work dies with it, partial changes stay unmerged and unaudited. Background advisor reviews are cancelable on this same action: pass the review id — its controller aborts, no token is issued for a cancelled review.\n" +
133
- "- action:'consume-design' (engineering mode, parent side — chain-terminal token consumption): after the delivery is verified and the chain closes out, consume this design's token slot — pass the designId (optional for a single-design session). The slot is consumed; a further spawn for the same designId is mechanically rejected, and any new work (including deviation fixes) requires a fresh design review and token. Idempotent: an unknown designId / already-consumed slot is a no-op notice, never an error. Do NOT call it while the chain is still open — fix rounds reuse the same slot (same designId + designToken).\n" +
134
- "- action:'panel': DIAGNOSE + fix the subagent panel — the collapsible blocks under the conversation the user sees (CLI TUI panel mirror; headless/VS Code degrade to a 'no panel' pool view). view (default — call it with no params or view:true): returns the live panel blocks [{key, role, status: running|done|awaitingDigest} — running entries also carry elapsedSec; awaitingDigest entries whose report is ALREADY digested carry digested:true (stuck blocks — the freezable ones — explain odd panel states here)] exactly as the user sees them. freeze: pass the block key of a digested-stuck block ({action:'panel', freeze:'role#N'}) to reclaim it into the conversation — the freeze ONLY passes for awaitingDigest blocks with no live pool entry and no pending report (gated); freezing a block whose report is still pending would break the digestion order and is refused with a clear error.\n\n" +
135
- "Why delegate? A sub-agent runs in its own isolated context — its reads, searches, tool calls and edits never enter your history or pollute your window; only its final report comes back. Delegation keeps your working context lean (you see the whole session, not the child's noise) and the child single-mindedly focused on one task. Parallel children run concurrently, saving wall-clock time. Every coder/eng-coder child carries its own verify + advisor self-review discipline — handed-off work is already verified before you read a word of it.\n\n" +
136
- "Available roles (which roles are exposed depends on the active mode — see Mode filtering below):\n" +
137
- "- explore — read-only search & analysis. Toolset: the read/search family (grep, read, glob, code_search, doc_search, repo_outline, lsp, tree...). No git context injected—evidence from read/glob/grep and the task book. Its report must list what it searched and what it did NOT find. Fast — specify thoroughness in the task: quick / medium / thorough (default medium).\n" +
138
- "- plan — read-only implementation planning. Same read/search toolset; NEVER edits files. Returns a step-by-step plan for the parent to execute.\n" +
139
- "- coder — full implementation. The parent's complete read/write/execute toolset plus verify and advisor for self-review. Its final report must include a delivery transparency table with one row per task requirement (Done / Simplified / Not done — no deferred column).\n" +
140
- "- eng-coder — engineering-mode coder (available only in engineering mode, replacing coder). Same full toolset as coder plus the design-driven methodology overlay; REQUIRES a valid designToken arg obtained from a passed advisor(type='design') review. The advisor's Approved reply also echoes a designId — pass it as the designId arg: required to pick between designs when several approved reviews are active, optional for a single design. The delivery report echoes the designId back for the audit fix round. ALSO REQUIRES a batchDoc arg — the batch record path (docs/batches/<batch>-<topic>.md), the batch §2 task book this spawn implements: a spawn without it, or with a path that does not resolve to a readable file, is mechanically refused.\n" +
141
- "- eng-designer — engineering-mode design writer (available only in engineering mode): the SOLE author of the requirements + design documents and of the batch record §2 (the batch task book) — revisions included. Writes no implementation code, does not edit prompt files, does not fire reviews, and needs NO designToken (its authorization is the confirmed requirements). It surveys on its own, but may only spawn read-only 'explore' children (sync, ≤6 per batch). ALSO REQUIRES a batchDoc arg — the batch record path (docs/batches/<batch>-<topic>.md); the same mechanical gate as eng-coder: a spawn without it, or with a path that does not resolve to a readable file, is mechanically refused.\n" +
142
- "Mode filtering: normal mode exposes explore/plan/coder (eng-coder and eng-designer are refused); engineering mode exposes explore/eng-designer/eng-coder (plan and coder are refused — plan is a normal-mode role, coder is replaced by eng-coder). The role enum is filtered per mode at assembly, and the spawn gate refuses out-of-mode roles mechanically.\n\n" +
143
- "Async spawn: pass async:true to spawn WITHOUT waiting — returns {id, role, status:\"running\"} immediately so you can keep working in your own turn (read/check files, run other tools) while the child runs in the background. The child's report is delivered to you automatically — there is no fetch action; use action:'status' only to see progress, never to wait for the result. Top-level spawns are ALWAYS async — never pass `async:false` at depth-0 (the report arrives automatically; if your next step needs it, end the turn and let the digest deliver it). Inside subagents (depth>0) spawns are always synchronous (platform rule). Eng-coder's delivery protocol runs fully inside the child (implementation → audit → self-fix → advisor re-review → converged delivery). Async spawns are pooled per role domain: at most 4 concurrent eng-coders and 4 concurrent other-role spawns by default (agent.poolLimits overrides both) — a full domain queues further spawns with a position while the other domain keeps starting (domains never block each other), and top-level only. After an async spawn the turn winds down normally — nothing expects you to wait for it: the child runs in the background and its report is delivered to you automatically — before your next turn, or digested in the suspension session — so end the turn; do not poll or wait for the result.\n\n" +
144
- "Task scheduling: declare the scheduling metadata to let the SCHEDULER order your spawns — files: the file paths this task will modify, dependsOn: ids from prior async spawn returns whose outcome this task needs. Overlapping-file tasks are serialized and dependent tasks are started in order automatically: a spawn that would conflict, or whose dependencies have not settled, queues instead of running ({id, status:\"queued\", position, reason} — the waiting task auto-starts when the conflict clears / its dependency settles; cancel a queued task to drop it). A spawn whose dependency was cancelled or failed stays queued and marked \"dependency cancelled\" until you decide (cancel it) — in an AUTO session it starts by itself. Referencing an unknown id errors; an id already consumed (auto-delivered by the auto channel) counts as satisfied. Omit both parameters for the plain immediate spawn (no scheduler involvement).\n\n" +
145
- "Writing the prompt:\n" +
146
- "- The sub-agent starts with zero context — it has not seen this conversation. Brief it like a colleague who just walked into the room: state the goal, list what you already know, hand over the specifics.\n" +
147
- "- Put exact paths and commands in the prompt when you know them. The sub-agent should not search for things you already know.\n" +
148
- "- Do not delegate understanding: if the task hinges on a file path or line number, find it yourself first and write it into the prompt.\n" +
149
- "- Once a sub-agent is running, leave that scope to it: don't redo its searches in parallel, and don't abandon it midway to finish manually.",
129
+ description: DESC("subagent"),
150
130
  parameters: {
151
131
  type: "object",
152
132
  properties: {
153
- action: { type: "string", enum: ["spawn", "status", "escalate", "cancel", "panel", "consume-design", "observe", "send"], description: "Which subagent-family action — spawn (default), status (non-blocking progress query — never consumes; async results arrive automatically — no fetch action), observe (inspect a running/queued/done async subagent's recent activity — recent turn summaries + current in-flight tool + turn/touched), send (inject a direction into a RUNNING async subagent, consumed at its next turn boundary as an ordinary instruction), escalate (飞刀 — hand implementation to a stronger consult model), cancel (stop ONE background subagent — pass its id; never omit), panel (view the live subagent panel / freeze a digested-stuck block), consume-design (engineering mode, parent side: chain-terminal token consumption — close out a design's token slot after the delivery is verified and the chain closes out, 2026-09-07). See the tool description for the full action matrix." },
154
- view: { type: "boolean", description: "action:'panel' only: true (default) = return the live panel blocks (the mirror of what the user sees). false with no freeze = nothing to do — error. Mutually exclusive with freeze (freeze wins)." },
155
- freeze: { type: "string", description: "action:'panel' only: block key of a digested-stuck awaitingDigest block (e.g. \"eng-coder#9\") to reclaim into the conversation via the gated done-freeze event. Refused when the block is running/done/unknown or its report is still pending digestion (would break the digestion order). Requires the CLI TUI panel mirror — headless/VS Code report the freeze unavailable." },
156
- task: { type: "string", description: "Required for action:'spawn' (the self-contained task brief) and action:'escalate' (goal, constraints, entry files, acceptance criteria). Not used by status." },
133
+ action: { type: "string", enum: ["spawn", "status", "escalate", "cancel", "panel", "consume-design", "observe", "send"], description: "Which action — spawn (default) / status / observe / send / escalate / cancel / panel / consume-design; semantics per action in the tool description." },
134
+ view: { type: "boolean", description: "action:'panel' only: true (default) = the live panel blocks as the user sees them; false without freeze = error. Mutually exclusive with freeze (freeze wins)." },
135
+ freeze: { type: "string", description: "action:'panel' only: block key of a digested-stuck block — reclaims it into the conversation; refused for running/done/unknown blocks or a still-pending report; requires the CLI TUI panel mirror." },
136
+ task: { type: "string", description: "Required for action:'spawn' (the self-contained task brief) and action:'escalate' (goal, constraints, entry files, acceptance criteria)." },
157
137
  context: { type: "string", description: "Optional background the sub-agent needs (it cannot see this conversation); action:'spawn' only." },
158
- role: { type: "string", enum: ["explore", "plan", "coder", "eng-coder", "eng-designer"], description: "The sub-agent role — see the tool description for the role capability matrix. Exact spelling required. action:'spawn' only (escalate spawns its own expert internally)." },
159
- model: { type: "string", description: "action:'spawn': provider/model override for this sub-agent ('provider:model', a provider name, or a model name on the parent's provider — defaults to config.agent.subagentModels[role], then config.agent.subagentModel, then the parent's provider). pass \"default\" to explicitly inherit the default model — equivalent to omitting the parameter. action:'escalate': pick a consult candidate as 'provider:model' (default = the first consult model)." },
160
- designToken: { type: "string", description: "Required when role='eng-coder': the token returned by advisor(type='design') after the design review passed. Without a valid token, eng-coder cannot modify files." },
161
- designId: { type: "string", description: "Optional when role='eng-coder': the designId echoed with the approved token by advisor(type='design'). Required to pick between designs when several approved reviews are active in the session — each eng-coder carries its own designId+token pair so parallel implementations never overwrite each other. Optional for a single design. action:'consume-design': the design whose slot to close out — optional for a single-design session; required to pick when several approved designs are active (the consume gate refuses to guess)." },
162
- batchDoc: { type: "string", description: "REQUIRED for role='eng-coder' and role='eng-designer': the batch record path (docs/batches/<batch>-<topic>.md) — the batch §2 task book this spawn implements (or writes). The spawn is mechanically refused without it, and also when the path does not resolve (cwd-relative or absolute) to a readable file; the CONTENT is never validated (the batch record owns that). explore/plan/coder spawns ignore it." },
163
- round: { type: "string", enum: ROUND_VALUES, description: "REQUIRED for role='eng-coder' and role='eng-designer' (explore/plan/coder exempt — and advisor is review-only, never a spawn write role): the implementation round this spawn is — initial (first implementation round for this design) or fix (a correction round reusing the same designId+designToken; docs FIRST). No default: a missing round is a dispatch defect and the spawn is mechanically refused." },
164
- async: { type: "boolean", description: "action:'spawn': true = spawn without waiting — returns {id, status:\"running\"} immediately; the report is delivered to you automatically (there is no fetch action). Default: depth-0 → true (async — every role); depth>0 → sync (forced). action:'escalate': same semantics — default async at depth 0; async:false keeps the legacy synchronous flight (mechanism parameter — see the Async spawn section for top-level guidance)." },
165
- files: { type: "array", items: { type: "string" }, description: "action:'spawn' only: the file write-domain this task declares (cwd-relative or absolute paths). files must be file-level paths (one per file you will modify). Directory declarations are NOT supported — they bypass the conflict detector and are rejected with an error. Tasks with overlapping files are serialized automatically — a conflicting spawn queues ({id, status:\"queued\", position, reason}) instead of running concurrently and starts when the conflict clears. Omit to skip conflict detection (plain immediate spawn)." },
166
- dependsOn: { type: "array", items: { type: "string" }, description: "action:'spawn' only: ids from prior async spawn returns whose outcome this task needs — the task queues ({id, status:\"queued\", position, reason}) until every dependency settles, then starts automatically. Ids already consumed (auto-delivered to the model) count as satisfied; a dependency cancelled or failed leaves the task queued marked 'dependency cancelled' until you decide (cancel it — AUTO sessions auto-start). Unknown ids error." },
167
- id: { type: "string", description: "action:'status'/'observe'/'send'/'cancel': the subagent id from the async spawn return. status: omit = overview of the whole pool; observe/send/cancel: REQUIRED (observe needs the child to inspect; send needs the target; never omit on cancel — a blanket cancel is unsupported)." },
168
- message: { type: "string", description: "action:'send' only (REQUIRED there): the direction to inject — the running async subagent consumes it at its next turn boundary as an ordinary user instruction (non-interrupting; its convergence/audit discipline is unchanged — injection is guidance, not a deviation waiver)." },
169
- recent: { type: "integer", description: "action:'observe' only (optional, default 5): how many recent-turn summaries to return (clamped 1..20 — N2 keeps observe terse)." },
138
+ role: { type: "string", enum: ["explore", "plan", "coder", "eng-coder", "eng-designer"], description: "The sub-agent role — see the role matrix in the tool description. Exact spelling required. action:'spawn' only (escalate spawns its own expert internally)." },
139
+ model: { type: "string", description: "action:'spawn': provider/model override ('provider:model', a provider, or a model name on the parent's provider) — defaults config.agent.subagentModels[role] → config.agent.subagentModel → the parent's provider; \"default\" inherits. action:'escalate': a consult candidate (default = the first)." },
140
+ designToken: { type: "string", description: "Required when role='eng-coder': the token from advisor(type='design') after the review passed — without it, eng-coder cannot modify files." },
141
+ designId: { type: "string", description: "role='eng-coder': the designId echoed with the approved token — required to pick between concurrent designs (optional for one). action:'consume-design': the slot to close out (required to pick between several; the gate refuses to guess)." },
142
+ batchDoc: { type: "string", description: "REQUIRED for role='eng-coder'/'eng-designer': the batch record path (docs/batches/<batch>-<topic>.md) — the task book this spawn implements/writes. Refused when absent or when the path does not resolve to a readable file; the content is never validated." },
143
+ round: { type: "string", enum: ROUND_VALUES, description: "REQUIRED for role='eng-coder'/'eng-designer': initial (first round for this design) or fix (a correction round reusing the same designId+designToken; docs FIRST). No default — missing = refused." },
144
+ async: { type: "boolean", description: "action:'spawn'/'escalate': default at depth 0 = true (async; the report arrives automatically); depth>0 always sync. async:false forces the blocking flight." },
145
+ files: { type: "array", items: { type: "string" }, description: "action:'spawn' only: the file write-domain this task declares — file-level paths only (cwd-relative or absolute; directories rejected — they bypass the conflict detector). Overlapping-file tasks serialize automatically. Omit = no conflict detection." },
146
+ dependsOn: { type: "array", items: { type: "string" }, description: "action:'spawn' only: ids from prior async returns whose outcome this task needs — queues until every dependency settles, then starts. Consumed ids count as satisfied; a cancelled/failed dependency stays queued marked 'dependency cancelled' until you decide (AUTO sessions auto-start). Unknown ids error." },
147
+ id: { type: "string", description: "action:'status'/'observe'/'send'/'cancel': the subagent id from the spawn return. status: omit = pool overview; observe/send/cancel: REQUIRED." },
148
+ message: { type: "string", description: "action:'send' only (REQUIRED there): the direction to inject — consumed by the running child at its next turn boundary as an ordinary instruction (non-interrupting)." },
149
+ recent: { type: "integer", description: "action:'observe' only (optional, default 5): how many recent-turn summaries (1..20)." },
170
150
  },
171
151
  required: [],
172
152
  },
@@ -239,7 +219,7 @@ export const subagentTool = {
239
219
  if (!ROLES.has(role)) {
240
220
  throw new Error(`Unknown subagent role: ${JSON.stringify(role)}. Valid roles: explore, plan, coder, eng-coder, eng-designer (exact spelling).`)
241
221
  }
242
- // §18 D-E3 internal-spawn gate: an eng-coder sub-agent may only spawn sync
222
+ // AGENT-LOOP-SUBAGENT.md §6.7.6 D-E3 internal-spawn gate: an eng-coder sub-agent may only spawn sync
243
223
  // explore (audit) children — non-explore roles and async are refused here
244
224
  // (mechanical), the audit budget is enforced (7th audit spawn refused), and
245
225
  // the returned attempt number marks this spawn as an audit for the task-book
@@ -300,7 +280,7 @@ export const subagentTool = {
300
280
  // stopped 折叠语义丢失:无 ⟦ev⟧stopped/无 STOPPED_MARK——块冻结标 done 而非
301
281
  // stopped——评审 🟡#2)。stopped 分支改抛 AbortError——runWithContinue 只捕
302
282
  // ContinueError——原样上抛 → 阻塞 catch 三分支②折叠("child 随即在 abort 检出点
303
- // 解绕折叠"——AGENT-LOOP §7.2 机制文)。abort 恒已在途(stopped 只由
283
+ // 解绕折叠"——AGENT-LOOP-SUBAGENT.md §6.7.2 机制文)。abort 恒已在途(stopped 只由
304
284
  // cancelSyncChild 与 ctrl.abort 同时置位)——信号语义真实。
305
285
  if (parent._syncChildAborts?.get(key)?.stopped) throw abortError(ctrl.signal, "settle", "sync-stopped")
306
286
  const go = await ctx.onPermissionRequest("continue", { turns: e.turn, agent: key })
@@ -348,14 +328,14 @@ export const subagentTool = {
348
328
  const cls = classifySyncAbort(ctx.signal, baseSignal, ctrl.signal, e)
349
329
  if (cls === "base") {
350
330
  // ① 整回合停(现状逐字保留——挂起场景 base 命中而 ctx.signal 未 abort——R2):
351
- // §27 R23:外层 abort 传播的中断——内层开块随之外层冻结前先收尾定格
331
+ // docs/cli/design/TUI.md §6.8.2 R23:外层 abort 传播的中断——内层开块随之外层冻结前先收尾定格
352
332
  // (D-R23c1 stopped——T-R23c.2a 生成侧路径;TUI 冻结兜底仍在 freezeSubTaskLines)
353
333
  emitNestedChildEvent(ctx, relayPrefix, "stopped")
354
334
  throw e // 用户停——不落错误事件
355
335
  }
356
336
  if (cls === "error") {
357
337
  // ③ 其他错误(现状逐字保留——:249-253):
358
- // §27 R23 error-run 映射(实现批补一行):run 错误(非 abort)→ 同样发 stopped
338
+ // §6.8.2 R23 error-run 映射(实现批补一行):run 错误(非 abort)→ 同样发 stopped
359
339
  // ——内层子块定格不悬空(T-R23a.3——工具错/运行错误路径)。
360
340
  emitNestedChildEvent(ctx, relayPrefix, "stopped")
361
341
  logEvent("child:error", { role, id: child._logId, ms: Date.now() - blockT0, err: errText(deathLine(e, ctrl?.signal), 200) })
@@ -370,7 +350,7 @@ export const subagentTool = {
370
350
  // 块冻结标 stopped(R6——非 done):⏹ 定向中止的 TUI 顶层块立即定格 stopped
371
351
  // (async settle cancelled 分支同款直发——async-settle.mjs settleAsyncEntry);
372
352
  // 嵌套(eng-coder 内 explore 审计)经 emitNestedChildEvent 定格子块——stopped
373
- // 幂等无害(重复/迟到 done 由 §27.1 F2 done 子块定格丢弃兜底)。
353
+ // 幂等无害(重复/迟到 done 由 docs/cli/design/TUI.md §6.8.2 F2 done 子块定格丢弃兜底)。
374
354
  ctx.callbacks?.onToken?.(`${relayPrefix}⟦ev⟧stopped\x1e0\x1e0\x1estopped\x1e`)
375
355
  emitNestedChildEvent(ctx, relayPrefix, "stopped")
376
356
  } finally {
@@ -378,7 +358,7 @@ export const subagentTool = {
378
358
  // 口径 = 成功/折叠/整回合停——错误③ 同样 rethrow 经 finally——同归本注销)
379
359
  disarm()
380
360
  }
381
- // §27 R23 D-R23c1(评审 #1 🅰——生成侧补发射):sync spawn 同步收尾——若本 spawn
361
+ // §6.8.2 R23 D-R23c1(评审 #1 🅰——生成侧补发射):sync spawn 同步收尾——若本 spawn
382
362
  // 处于嵌套上下文(ctx.callbacks 已是嵌套 wrapper——eng-coder 内 explore 审计)→
383
363
  // 发内层 ⟦ev⟧done(完整嵌套前缀——wrapper 链自动补外层)→ 主 TUI 路由子块定格
384
364
  // (T-R23c.1)。非嵌套(depth-0)零变化——冻结仍由 dispatch subKey 精确冻承接。
@@ -386,7 +366,7 @@ export const subagentTool = {
386
366
  // 冻结管线复用——迟到 done 对已定格 stopped 块被丢弃——幂等无害)。
387
367
  emitNestedChildEvent(ctx, relayPrefix, "done")
388
368
  logEvent("child:done", { role, id: child._logId, ms: Date.now() - blockT0, kind: String(pipelineReport).includes(TURN_CAP_MARK) || String(pipelineReport).includes(STOPPED_MARK) ? "partial" : "ok" })
389
- // §7.2.3 sync spawn 完成精确冻结(方案 e):execute 返回前 ctx 留子代理 key
369
+ // docs/cli/design/TUI.md §6.8 sync spawn 完成精确冻结(方案 e):execute 返回前 ctx 留子代理 key
390
370
  // (relayPrefix 去尾 = `role#N`)——dispatch runOne 读它作 onToolResult 第 4 参 →
391
371
  // TUI finishSubTaskKey 按 key 精确冻(async eng-coder 先启动时不再误冻其块——
392
372
  // T-F2)。仅成功/折叠路径设置:async 分支不设(round2 #2——ack 带 status:running 由
@@ -1,5 +1,6 @@
1
1
  // F-CC4(CONTEXT-COMPACTION.md §6.16.5):task 列表变更 ⇒ 方向转换轻推(本档只调用;文案/去重单源 = agent-tools/context.mjs)
2
2
  import { pushContextNudge } from "./context.mjs"
3
+ import { DESC } from "../tools/shared.mjs"
3
4
 
4
5
  /** Common synonyms LLMs tend to use — normalize to canonical values */
5
6
  const STATUS_ALIASES = {
@@ -26,18 +27,11 @@ function normalizeStatus(raw) {
26
27
 
27
28
  /**
28
29
  * task tool: multi-step task planning and progress tracking (Claude Code's todo mode).
29
- * Each call replaces the entire list; only modifies agent internal state (no external world), so readonly.
30
- * Accesses the caller agent via ctx.agent (injected by runAgent).
30
+ * 描述面 = `tool-docs/task.md`(DESC() 加载);仅改 agent 内部状态(readonly);ctx.agent 由 runAgent 注入。
31
31
  */
32
32
  export const taskTool = {
33
33
  name: "task",
34
- description:
35
- "Plan and track a task list for complex multi-step work. Each call replaces the entire list. " +
36
- "Keep exactly one item in_progress at a time; mark items done as you complete them; never mark done if tests fail or work is partial. " +
37
- "Statuses: pending | in_progress | done — synonyms (completed/finished/complete, todo/open/waiting, active/running/working, …) are accepted and normalized with a warning. " +
38
- "IMPORTANT: title is required and must be a non-empty string — items with empty titles are silently dropped. " +
39
- "For cross-session / project-level tracking, use `/ledger`. " +
40
- "Returns the updated task list (or the new item's ID on add).",
34
+ description: DESC("task"),
41
35
  parameters: {
42
36
  type: "object",
43
37
  properties: {
@@ -1,18 +1,13 @@
1
+ import { TIMER_MAX_PENDING } from "../agent/timers.mjs"
2
+ import { DESC } from "../tools/shared.mjs"
3
+
1
4
  /**
2
5
  * timer tool: set a time budget for thinking before the agent insists on action.
3
- * Call this when starting to analyze code or debug — it gives a bounded
4
- * thinking window. When the timer fires, a system reminder is injected
5
- * suggesting the model try running code, adding logs, or otherwise acting
6
- * instead of continuing to think.
6
+ * 描述面 = `tool-docs/timer.md`(DESC() 加载;机制 = AGENT-LOOP-ASYNC-POOL.md §6.30.3 D-TW8)。
7
7
  */
8
8
  export const timerTool = {
9
9
  name: "timer",
10
- description:
11
- "Set a timer before you start analyzing code. When the timer fires, " +
12
- "a system reminder will be injected suggesting you try running code or " +
13
- "adding debug logs. Use this to enforce a thinking budget: you get " +
14
- "N seconds to reason, then the timer reminds you to act. " +
15
- "Returns the set confirmation — the reminder fires at the deadline.",
10
+ description: DESC("timer"),
16
11
  parameters: {
17
12
  type: "object",
18
13
  properties: {
@@ -38,8 +33,13 @@ export const timerTool = {
38
33
  const expiresAt = Date.now() + seconds * 1000
39
34
  const message = args.message || `⏰ Time's up (${seconds}s). Have you tried running the code, adding a console.log, or checking the output? Thinking more without data is guessing.`
40
35
 
41
- ctx.agent._pendingTimers = ctx.agent._pendingTimers ?? []
42
- ctx.agent._pendingTimers.push({ id: Date.now(), expiresAt, message })
36
+ // §6.30.3 门三件②(D-TW6):在途帽 8——超限显式拒(不静默丢 / 不静默清;形态先例 = 队满
37
+ // 「拒 + 提示 + 保留」)。拒点时在途表零变动(本次不推入)。
38
+ const pending = (ctx.agent._pendingTimers = ctx.agent._pendingTimers ?? [])
39
+ if (pending.length >= TIMER_MAX_PENDING) {
40
+ throw new Error(`timer: ${pending.length} timers already pending (max ${TIMER_MAX_PENDING}) — wait for one to fire before setting another`)
41
+ }
42
+ pending.push({ id: Date.now(), expiresAt, message })
43
43
 
44
44
  return `Timer set for ${seconds} seconds. A reminder will appear when time is up.`
45
45
  },
@@ -11,11 +11,13 @@
11
11
  * path (doc-only changes return early) — the task list and self-review checklist.
12
12
  */
13
13
 
14
- import { isCodePath, isDocPath, loadConventions } from "../conventions.mjs"
14
+ import { isCodePath, isDocPath, loadProjectDeclaration } from "../conventions.mjs"
15
15
  // #96(信息段与执行方式按端注入——CORE-UNIFICATION §2.13.4):git / node --check 执行
16
16
  // 全部经核内执行面单点(默认径 = execFileSync 等价——CLI 语义零行为变;端侧 injected ⇒
17
17
  // 可中断执行器);编辑器诊断段经 `configureVerifyDiagnostics` 注入(缺省不注入)。
18
18
  import { runCommand } from "../tools/exec-run.mjs"
19
+ // #15 描述外置:描述文本单点 = tool-docs/verify.md(DESC 单解析面,缺档抛错语义不变)
20
+ import { DESC } from "../tools/shared.mjs"
19
21
  import { existsSync } from "node:fs"
20
22
  import { resolve } from "node:path"
21
23
 
@@ -34,7 +36,7 @@ export function configureVerifyDiagnostics(impl) {
34
36
  export function resetVerifyDiagnostics() { injectedDiagnostics = null }
35
37
 
36
38
  /**
37
- * §18.12 D-VR1 path normalization — mirrors the §20.5 file-domain handling:
39
+ * D-VR1 path normalization — mirrors the file-domain handling (AGENT-LOOP-SUBAGENT.md §6.9):
38
40
  * resolved to an absolute path with forward slashes; dedup uses the win32
39
41
  * lowercase comparison key (changedFileKey) so "D:/x/src/a.mjs" and
40
42
  * "d:\\x\SRC\a.mjs" count as the same file.
@@ -74,28 +76,27 @@ function appendBlockGuidance(lines, codeFiles) {
74
76
  */
75
77
  export const verifyTool = {
76
78
  name: "verify",
77
- description:
78
- "Generic pre-completion verification gate — language/framework/project agnostic. verify does NOT run any test/verification command for you and does NOT require a test per change. You DECLARE your verification status via the verification argument: { status: 'passed' | 'failed' | 'skipped', command?, summary? }. 'passed' → allowed; 'failed' → blocked (cannot claim done); 'skipped' → allowed but requires a summary reason. Whether/how to verify is described in natural language in the project's AGENTS.md and is decided + executed by you — verify only mechanically gates on your declaration. Also reports changed files (git diff), an advisory node --check hint on changed .js/.mjs (not gating), the task list, and a self-review checklist. Call this BEFORE declaring a coding task complete — do not say 'done' until verify passes.",
79
+ description: DESC("verify"),
79
80
  parameters: {
80
81
  type: "object",
81
82
  properties: {
82
83
  verification: {
83
84
  type: "object",
84
- description: "The model's declaration of whether it already ran the project's verification and its outcome. verify does not execute the command — it is self-reported evidence.",
85
+ description: "Your declaration of whether you already ran the project's verification and its outcome — self-reported evidence (verify does not execute the command).",
85
86
  properties: {
86
87
  status: { type: "string", enum: ["passed", "failed", "skipped"], description: "The verification outcome you declare: 'passed' → allowed; 'failed' → blocked; 'skipped' → allowed but requires a summary reason." },
87
- command: { type: "string", description: "Optional: the verification command you ran (self-reported — verify does not execute it)." },
88
- summary: { type: "string", description: "Optional: summary/result of the verification. Required when status='skipped' — a concrete reason (e.g. \"project has no automated tests — verified by manual review\")." },
88
+ command: { type: "string", description: "Optional: the verification command you ran (self-reported)." },
89
+ summary: { type: "string", description: "Optional: the result. Required when status='skipped' — a concrete reason (e.g. \"project has no automated tests — verified by manual review\")." },
89
90
  },
90
91
  required: ["status"],
91
92
  },
92
- workdir: { type: "string", description: "Optional: run verify in this subdirectory (relative to cwd or absolute) — for monorepos" },
93
+ workdir: { type: "string", description: "Optional: run verify in this subdirectory (relative to cwd or absolute) — monorepos" },
93
94
  },
94
95
  },
95
96
  readonly: true,
96
97
  async execute(args, ctx) {
97
98
  const cwd = ctx.agent.cwd
98
- // Changed-file resolution (§18.12 D-VR3): _touchedFiles (per-run bookkeeping,
99
+ // Changed-file resolution (D-VR3): _touchedFiles (per-run bookkeeping,
99
100
  // absolute paths) ∪ git diff fallback — git is tried at testCwd
100
101
  // (workdir-resolved) first, then at ctx.agent.cwd; first success wins.
101
102
  // git-diff paths are repo-root-relative — resolved against the discovered
@@ -105,7 +106,7 @@ export const verifyTool = {
105
106
  lines.push("=== VERIFICATION REPORT ===")
106
107
  lines.push("")
107
108
 
108
- // 1. Changed files — _touchedFiles ∪ git diff (§18.12 D-VR1)
109
+ // 1. Changed files — _touchedFiles ∪ git diff (D-VR1)
109
110
  let gitOk = false
110
111
  let gitStat = ""
111
112
  let gitFiles = []
@@ -131,7 +132,7 @@ export const verifyTool = {
131
132
  lines.push("Changed files: (not a git repo or git unavailable)")
132
133
  }
133
134
  // _touchedFiles: files written by the tools this run — captured regardless
134
- // of git state (§18.12 F-VR1: subagent cwd ≠ git root must still locate).
135
+ // of git state (F-VR1: subagent cwd ≠ git root must still locate).
135
136
  const touchedFiles = (ctx.agent._touchedFiles ?? []).map((p) => normalizeChangedPath(p, cwd))
136
137
  const gitKeys = new Set(gitFiles.map(changedFileKey))
137
138
  const touchedOnly = touchedFiles.filter((p) => !gitKeys.has(changedFileKey(p)))
@@ -173,10 +174,10 @@ export const verifyTool = {
173
174
  // for doc changes, and the task list/self-review checklist add nothing either.
174
175
  // Paths inside a declared code segment (default: src — incl. prompts/*.md) are
175
176
  // product code — excluded from the fast path, consistent with the design gate.
176
- // The project's own layout is declarable (.thincoder/conventions.json).
177
+ // The project's own layout is declarable (the manifest's codePaths).
177
178
  // Empty list (no changes / git unavailable)
178
179
  // intentionally falls through to the normal path below.
179
- const conv = loadConventions(cwd)
180
+ const conv = loadProjectDeclaration(cwd)
180
181
  if (changedFiles.length > 0 && changedFiles.every((f) => isDocPath(f, conv))) {
181
182
  lines.push("")
182
183
  lines.push("Documentation-only changes — skipping syntax checks and tests.")
package/agent-tools.mjs CHANGED
@@ -21,7 +21,6 @@ export { batchTool } from "./agent-tools/batch.mjs"
21
21
  // 批次档生命周期工具(BATCH-RECORD §4.3 挂载表——主名 `batch` 单工具四 action:depth-0 主 agent
22
22
  // create/close + append §1/§4/§6 + status §1(轮 2 裁定②:§4/§6 状态面走普通文档写)(D-BR18);
23
23
  // eng 子代理 eng 分支绑定段写入;评审 §3。
24
- // 过渡别名 batchSegmentTool 不入登记册(§4.14——生产挂载面全切主名;别名仅供 shim 导出面)。
25
24
  // CORE-UNIFICATION TOOLS #83(统一登记册——VSC `agent-tools/index.mjs:15` 含 consult 家族;
26
25
  // CLI 原把 consult 另挂 `agent/setup.mjs:173` ⇒ 归位:登记册即单一来源,装配方只读本档)。
27
26
  export { consultStartTool, consultStopTool } from "./agent-tools/consult.mjs"