@fitlab-ai/agent-infra 0.9.9 → 0.9.11

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 (309) hide show
  1. package/README.md +13 -3
  2. package/README.zh-CN.md +13 -3
  3. package/bin/cli.ts +17 -4
  4. package/dist/bin/cli.js +19 -4
  5. package/dist/bin/internal-cli.js +55 -4
  6. package/dist/lib/agent-client.js +0 -1
  7. package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +17 -10
  8. package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-context.js +11 -31
  9. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +15 -33
  10. package/dist/lib/agent-clients/adapters/codex-sandbox.js +72 -11
  11. package/dist/lib/agent-clients/adapters/index.js +3 -1
  12. package/dist/lib/agent-clients/adapters/opencode-sandbox.js +0 -4
  13. package/dist/lib/agent-clients/adapters/traecli.js +65 -0
  14. package/dist/lib/agent-clients/config.js +14 -99
  15. package/dist/lib/agent-clients/reconcile.js +1 -11
  16. package/dist/lib/agent-clients/schema.js +5 -4
  17. package/dist/lib/agent-clients/tokens.js +7 -17
  18. package/dist/lib/agent-clients/types.js +2 -1
  19. package/dist/lib/decide.js +14 -30
  20. package/dist/lib/defaults.json +9 -0
  21. package/dist/lib/init.js +2 -2
  22. package/dist/lib/internal/codex-lifecycle.js +2 -2
  23. package/dist/lib/internal/codex-sandbox-controller.js +4 -3
  24. package/dist/lib/internal/release-workflow.js +0 -1
  25. package/dist/lib/internal/sandbox-control.js +2 -2
  26. package/dist/lib/internal/task-finalization.js +19 -9
  27. package/dist/lib/internal/task-ledger.js +8 -2
  28. package/dist/lib/internal/task-lifecycle.js +17 -93
  29. package/dist/lib/internal/task-orchestration.js +30 -216
  30. package/dist/lib/{task/commands/validate.js → internal/task-validate.js} +77 -43
  31. package/dist/lib/merge.js +205 -103
  32. package/dist/lib/process-data/normalize.js +2 -9
  33. package/dist/lib/run/host.js +46 -16
  34. package/dist/lib/run/tui.js +5 -2
  35. package/dist/lib/sandbox/agent-client-reconciler.js +4 -12
  36. package/dist/lib/sandbox/commands/create.js +3 -3
  37. package/dist/lib/sandbox/commands/enter.js +18 -4
  38. package/dist/lib/sandbox/config.js +3 -12
  39. package/dist/lib/sandbox/control/controller-registration.js +4 -2
  40. package/dist/lib/sandbox/control/executor.js +135 -40
  41. package/dist/lib/sandbox/control/lifecycle.js +29 -20
  42. package/dist/lib/sandbox/control/server.js +18 -16
  43. package/dist/lib/sandbox/mounts.js +3 -2
  44. package/dist/lib/sandbox/recovery.js +123 -22
  45. package/dist/lib/sandbox/workspace-identity.js +14 -0
  46. package/dist/lib/sandbox/workspace-view.js +8 -9
  47. package/dist/lib/self-update.js +278 -0
  48. package/dist/lib/server/protocol.js +5 -5
  49. package/dist/lib/task/activity-log.js +4 -1
  50. package/dist/lib/task/artifact-lifecycle.js +4 -2
  51. package/dist/lib/task/codex-orchestration.js +32 -35
  52. package/dist/lib/task/command-options.js +1 -3
  53. package/dist/lib/task/commands/cat.js +0 -5
  54. package/dist/lib/task/commands/decisions.js +76 -92
  55. package/dist/lib/task/commands/files.js +6 -6
  56. package/dist/lib/task/commands/grep.js +25 -61
  57. package/dist/lib/task/commands/issue-body.js +6 -6
  58. package/dist/lib/task/commands/log.js +25 -10
  59. package/dist/lib/task/commands/show.js +5 -6
  60. package/dist/lib/task/commands/status.js +8 -8
  61. package/dist/lib/task/commit-operation.js +21 -25
  62. package/dist/lib/task/commit-policy.js +1 -1
  63. package/dist/lib/task/control-authority.js +431 -0
  64. package/dist/lib/task/create.js +6 -1
  65. package/dist/lib/task/current-contract.js +35 -0
  66. package/dist/lib/task/decision-details.js +263 -0
  67. package/dist/lib/task/decision-intents.js +5 -2
  68. package/dist/lib/task/delegation-receipts.js +8 -7
  69. package/dist/lib/task/events.js +11 -5
  70. package/dist/lib/task/finalization.js +72 -46
  71. package/dist/lib/task/index.js +19 -29
  72. package/dist/lib/task/ledger-intents.js +8 -9
  73. package/dist/lib/task/ledger.js +13 -6
  74. package/dist/lib/task/lifecycle.js +16 -0
  75. package/dist/lib/task/orchestration.js +51 -14
  76. package/dist/lib/task/review-artifacts.js +31 -12
  77. package/dist/lib/task/review-finalization.js +19 -6
  78. package/dist/lib/task/verification-engine.js +38 -18
  79. package/dist/lib/task/verification-review.js +1 -1
  80. package/dist/lib/task/workspace-snapshot.js +5 -11
  81. package/dist/lib/task/write.js +11 -1
  82. package/dist/lib/update.js +6 -37
  83. package/dist/lib/version.js +5 -1
  84. package/dist/lifecycle-build-manifest.json +3 -3
  85. package/lib/agent-client.ts +0 -1
  86. package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +25 -10
  87. package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +16 -33
  88. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +16 -35
  89. package/lib/agent-clients/adapters/codex-sandbox.ts +89 -14
  90. package/lib/agent-clients/adapters/index.ts +3 -1
  91. package/lib/agent-clients/adapters/opencode-sandbox.ts +0 -5
  92. package/lib/agent-clients/adapters/traecli.ts +66 -0
  93. package/lib/agent-clients/config.ts +14 -144
  94. package/lib/agent-clients/reconcile.ts +1 -12
  95. package/lib/agent-clients/schema.ts +5 -4
  96. package/lib/agent-clients/tokens.ts +7 -20
  97. package/lib/agent-clients/types.ts +2 -1
  98. package/lib/decide.ts +12 -22
  99. package/lib/defaults.json +9 -0
  100. package/lib/init.ts +2 -2
  101. package/lib/internal/codex-lifecycle.ts +2 -2
  102. package/lib/internal/codex-sandbox-controller.ts +4 -3
  103. package/lib/internal/release-workflow.ts +0 -1
  104. package/lib/internal/sandbox-control.ts +2 -2
  105. package/lib/internal/task-finalization.ts +27 -13
  106. package/lib/internal/task-ledger.ts +8 -2
  107. package/lib/internal/task-lifecycle.ts +33 -76
  108. package/lib/internal/task-orchestration.ts +33 -190
  109. package/lib/{task/commands/validate.ts → internal/task-validate.ts} +77 -41
  110. package/lib/merge.ts +236 -120
  111. package/lib/process-data/normalize.ts +2 -8
  112. package/lib/run/host.ts +63 -14
  113. package/lib/run/tui.ts +5 -3
  114. package/lib/sandbox/agent-client-reconciler.ts +5 -15
  115. package/lib/sandbox/commands/create.ts +4 -4
  116. package/lib/sandbox/commands/enter.ts +24 -4
  117. package/lib/sandbox/config.ts +2 -14
  118. package/lib/sandbox/control/container-identity.ts +2 -2
  119. package/lib/sandbox/control/controller-registration.ts +8 -3
  120. package/lib/sandbox/control/executor.ts +156 -53
  121. package/lib/sandbox/control/lifecycle.ts +34 -28
  122. package/lib/sandbox/control/protocol.ts +1 -3
  123. package/lib/sandbox/control/server.ts +15 -12
  124. package/lib/sandbox/control/state.ts +7 -7
  125. package/lib/sandbox/mounts.ts +4 -3
  126. package/lib/sandbox/recovery.ts +162 -34
  127. package/lib/sandbox/tools.ts +1 -1
  128. package/lib/sandbox/workspace-identity.ts +28 -0
  129. package/lib/sandbox/workspace-view.ts +14 -10
  130. package/lib/self-update.ts +341 -0
  131. package/lib/server/protocol.ts +5 -5
  132. package/lib/task/activity-log.ts +6 -1
  133. package/lib/task/artifact-lifecycle.ts +5 -3
  134. package/lib/task/codex-orchestration.ts +40 -45
  135. package/lib/task/command-options.ts +1 -2
  136. package/lib/task/commands/cat.ts +0 -4
  137. package/lib/task/commands/decisions.ts +80 -75
  138. package/lib/task/commands/files.ts +6 -6
  139. package/lib/task/commands/grep.ts +23 -51
  140. package/lib/task/commands/issue-body.ts +6 -6
  141. package/lib/task/commands/log.ts +22 -10
  142. package/lib/task/commands/show.ts +5 -6
  143. package/lib/task/commands/status.ts +8 -8
  144. package/lib/task/commit-operation.ts +25 -30
  145. package/lib/task/commit-policy.ts +1 -2
  146. package/lib/task/control-authority.ts +563 -0
  147. package/lib/task/create.ts +6 -1
  148. package/lib/task/current-contract.ts +49 -0
  149. package/lib/task/decision-details.ts +340 -0
  150. package/lib/task/decision-intents.ts +7 -2
  151. package/lib/task/delegation-receipts.ts +8 -6
  152. package/lib/task/events.ts +10 -5
  153. package/lib/task/finalization.ts +84 -42
  154. package/lib/task/index.ts +19 -28
  155. package/lib/task/ledger-intents.ts +6 -8
  156. package/lib/task/ledger.ts +17 -7
  157. package/lib/task/lifecycle.ts +20 -0
  158. package/lib/task/orchestration.ts +42 -16
  159. package/lib/task/review-artifacts.ts +53 -15
  160. package/lib/task/review-finalization.ts +33 -7
  161. package/lib/task/verification-engine.ts +33 -21
  162. package/lib/task/verification-review.ts +1 -1
  163. package/lib/task/workspace-snapshot.ts +4 -13
  164. package/lib/task/write.ts +20 -1
  165. package/lib/update.ts +6 -40
  166. package/lib/version.ts +6 -1
  167. package/package.json +1 -1
  168. package/templates/.agents/QUICKSTART.en.md +2 -3
  169. package/templates/.agents/QUICKSTART.zh-CN.md +2 -3
  170. package/templates/.agents/README.en.md +5 -3
  171. package/templates/.agents/README.zh-CN.md +5 -3
  172. package/templates/.agents/rules/README.en.md +1 -0
  173. package/templates/.agents/rules/README.zh-CN.md +1 -0
  174. package/templates/.agents/rules/compatibility-policy.en.md +40 -0
  175. package/templates/.agents/rules/compatibility-policy.zh-CN.md +40 -0
  176. package/templates/.agents/rules/create-issue.github.en.md +1 -1
  177. package/templates/.agents/rules/create-issue.github.zh-CN.md +1 -1
  178. package/templates/.agents/rules/human-decision-context.en.md +9 -7
  179. package/templates/.agents/rules/human-decision-context.zh-CN.md +9 -7
  180. package/templates/.agents/rules/issue-fields.github.en.md +1 -1
  181. package/templates/.agents/rules/issue-fields.github.zh-CN.md +1 -1
  182. package/templates/.agents/rules/issue-pr-commands.en.md +1 -1
  183. package/templates/.agents/rules/issue-pr-commands.github.en.md +1 -1
  184. package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +1 -1
  185. package/templates/.agents/rules/issue-pr-commands.zh-CN.md +1 -1
  186. package/templates/.agents/rules/issue-sync.github.en.md +1 -1
  187. package/templates/.agents/rules/issue-sync.github.zh-CN.md +2 -2
  188. package/templates/.agents/rules/lifecycle-orchestration.en.md +4 -4
  189. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +4 -4
  190. package/templates/.agents/rules/milestone-inference.github.en.md +1 -1
  191. package/templates/.agents/rules/milestone-inference.github.zh-CN.md +1 -1
  192. package/templates/.agents/rules/next-step-output.en.md +3 -3
  193. package/templates/.agents/rules/next-step-output.zh-CN.md +3 -3
  194. package/templates/.agents/rules/no-mid-flow-questions.en.md +1 -1
  195. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +1 -1
  196. package/templates/.agents/rules/pr-sync.en.md +2 -2
  197. package/templates/.agents/rules/pr-sync.github.en.md +3 -2
  198. package/templates/.agents/rules/pr-sync.github.zh-CN.md +3 -1
  199. package/templates/.agents/rules/pr-sync.zh-CN.md +2 -1
  200. package/templates/.agents/rules/release-commands.github.en.md +6 -0
  201. package/templates/.agents/rules/release-commands.github.zh-CN.md +6 -0
  202. package/templates/.agents/rules/review-handshake.en.md +1 -1
  203. package/templates/.agents/rules/review-handshake.zh-CN.md +1 -1
  204. package/templates/.agents/rules/review-method.en.md +1 -0
  205. package/templates/.agents/rules/review-method.zh-CN.md +1 -0
  206. package/templates/.agents/rules/task-management.en.md +1 -2
  207. package/templates/.agents/rules/task-management.zh-CN.md +1 -2
  208. package/templates/.agents/skills/analyze-task/SKILL.en.md +4 -3
  209. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +4 -3
  210. package/templates/.agents/skills/block-task/SKILL.en.md +2 -2
  211. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
  212. package/templates/.agents/skills/cancel-task/SKILL.en.md +2 -2
  213. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +2 -2
  214. package/templates/.agents/skills/check-task/SKILL.en.md +2 -2
  215. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +2 -2
  216. package/templates/.agents/skills/close-codescan/SKILL.en.md +1 -1
  217. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +1 -1
  218. package/templates/.agents/skills/close-dependabot/SKILL.en.md +1 -1
  219. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +1 -1
  220. package/templates/.agents/skills/code-task/SKILL.en.md +4 -3
  221. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +5 -3
  222. package/templates/.agents/skills/commit/SKILL.en.md +27 -7
  223. package/templates/.agents/skills/commit/SKILL.zh-CN.md +3 -4
  224. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +5 -5
  225. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +5 -5
  226. package/templates/.agents/skills/complete-task/SKILL.en.md +3 -3
  227. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
  228. package/templates/.agents/skills/create-pr/SKILL.en.md +15 -11
  229. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +15 -11
  230. package/templates/.agents/skills/create-pr/reference/change-report.en.md +46 -0
  231. package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +46 -0
  232. package/templates/.agents/skills/create-pr/scripts/change-report.mjs +203 -0
  233. package/templates/.agents/skills/create-release-note/SKILL.en.md +5 -6
  234. package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +5 -6
  235. package/templates/.agents/skills/create-task/SKILL.en.md +1 -1
  236. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +1 -1
  237. package/templates/.agents/skills/import-issue/SKILL.en.md +1 -1
  238. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +1 -1
  239. package/templates/.agents/skills/plan-task/SKILL.en.md +5 -4
  240. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +5 -4
  241. package/templates/.agents/skills/restore-task/SKILL.en.md +1 -1
  242. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +1 -1
  243. package/templates/.agents/skills/review-analysis/SKILL.en.md +5 -3
  244. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +5 -3
  245. package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -0
  246. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -0
  247. package/templates/.agents/skills/review-code/SKILL.en.md +5 -3
  248. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -3
  249. package/templates/.agents/skills/review-code/config/verify.en.json +1 -0
  250. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -0
  251. package/templates/.agents/skills/review-code/reference/migration-risks.en.md +9 -6
  252. package/templates/.agents/skills/review-code/reference/migration-risks.zh-CN.md +9 -6
  253. package/templates/.agents/skills/review-plan/SKILL.en.md +5 -3
  254. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +5 -3
  255. package/templates/.agents/skills/review-plan/config/verify.en.json +1 -0
  256. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -0
  257. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +7 -3
  258. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +7 -3
  259. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +1 -1
  260. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +1 -1
  261. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -2
  262. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -2
  263. package/templates/.agents/skills/run-task/SKILL.en.md +6 -2
  264. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +6 -2
  265. package/templates/.agents/skills/run-task/reference/host-validation.en.md +2 -2
  266. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +2 -2
  267. package/templates/.agents/skills/update-agent-infra/SKILL.en.md +2 -2
  268. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +2 -2
  269. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +159 -104
  270. package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
  271. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
  272. package/templates/.agents/templates/task.en.md +1 -1
  273. package/templates/.agents/templates/task.zh-CN.md +1 -1
  274. package/templates/.claude/commands/analyze-task.en.md +1 -1
  275. package/templates/.claude/commands/analyze-task.zh-CN.md +1 -1
  276. package/templates/.claude/commands/block-task.en.md +1 -1
  277. package/templates/.claude/commands/block-task.zh-CN.md +1 -1
  278. package/templates/.claude/commands/cancel-task.en.md +1 -1
  279. package/templates/.claude/commands/cancel-task.zh-CN.md +1 -1
  280. package/templates/.claude/commands/check-task.en.md +1 -1
  281. package/templates/.claude/commands/check-task.zh-CN.md +1 -1
  282. package/templates/.claude/commands/code-task.en.md +1 -1
  283. package/templates/.claude/commands/code-task.zh-CN.md +1 -1
  284. package/templates/.claude/commands/commit.en.md +1 -1
  285. package/templates/.claude/commands/commit.zh-CN.md +1 -1
  286. package/templates/.claude/commands/complete-manual-validation.en.md +1 -1
  287. package/templates/.claude/commands/complete-manual-validation.zh-CN.md +1 -1
  288. package/templates/.claude/commands/complete-task.en.md +1 -1
  289. package/templates/.claude/commands/complete-task.zh-CN.md +1 -1
  290. package/templates/.claude/commands/create-pr.en.md +1 -1
  291. package/templates/.claude/commands/create-pr.zh-CN.md +1 -1
  292. package/templates/.claude/commands/plan-task.en.md +1 -1
  293. package/templates/.claude/commands/plan-task.zh-CN.md +1 -1
  294. package/templates/.claude/commands/review-analysis.en.md +1 -1
  295. package/templates/.claude/commands/review-analysis.zh-CN.md +1 -1
  296. package/templates/.claude/commands/review-code.en.md +1 -1
  297. package/templates/.claude/commands/review-code.zh-CN.md +1 -1
  298. package/templates/.claude/commands/review-plan.en.md +1 -1
  299. package/templates/.claude/commands/review-plan.zh-CN.md +1 -1
  300. package/templates/.claude/commands/run-manual-validation.en.md +1 -1
  301. package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
  302. package/templates/.claude/commands/run-task.en.md +1 -1
  303. package/templates/.claude/commands/run-task.zh-CN.md +1 -1
  304. package/templates/.claude/commands/watch-pr.en.md +1 -1
  305. package/templates/.claude/commands/watch-pr.zh-CN.md +1 -1
  306. package/templates/.traecli/commands/update-agent-infra.en.md +7 -0
  307. package/templates/.traecli/commands/update-agent-infra.zh-CN.md +7 -0
  308. package/dist/lib/builtin-tuis.js +0 -40
  309. package/lib/builtin-tuis.ts +0 -56
@@ -0,0 +1,263 @@
1
+ const DECISION_ID = /^(AN|PL|CD|HD)-[1-9]\d*(?=$|[\s::])/i;
2
+ const HEADING = /^ {0,3}(#{1,6})[ \t]+(.+?)\s*$/;
3
+ const FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})(.*)$/;
4
+ const FENCE_CLOSE = /^ {0,3}(`{3,}|~{3,})\s*$/;
5
+ const ANCHOR = /^\s*<a\s+id=(['"])([^'"]+)\1\s*><\/a>\s*$/;
6
+ const FIELD = /^\s*[-*]\s+\*\*([^*]+)\*\*\s*[::]\s*/;
7
+ const OPTION_HEADING = /^(?:方案|Option)\s+[A-Z](?:\b|\s*[::])/i;
8
+ function sourceLines(content) {
9
+ const lines = [];
10
+ let start = 0;
11
+ while (start < content.length) {
12
+ const newline = content.indexOf('\n', start);
13
+ const end = newline === -1 ? content.length : newline;
14
+ lines.push({ start, end, text: content.slice(start, end).replace(/\r$/, '') });
15
+ start = newline === -1 ? content.length : newline + 1;
16
+ }
17
+ return lines;
18
+ }
19
+ function scanVisibleMarkdown(content) {
20
+ const lines = sourceLines(content);
21
+ const visible = [];
22
+ const headings = [];
23
+ const anchors = [];
24
+ let fence = null;
25
+ for (const line of lines) {
26
+ if (fence) {
27
+ const close = line.text.match(FENCE_CLOSE);
28
+ if (close && close[1][0] === fence.character && close[1].length >= fence.length)
29
+ fence = null;
30
+ continue;
31
+ }
32
+ const open = line.text.match(FENCE_OPEN);
33
+ const marker = open?.[1];
34
+ const info = open?.[2] ?? '';
35
+ if (marker && !(marker[0] === '`' && info.includes('`'))) {
36
+ fence = { character: marker[0], length: marker.length };
37
+ continue;
38
+ }
39
+ visible.push(line);
40
+ const heading = line.text.match(HEADING);
41
+ if (heading)
42
+ headings.push({ ...line, level: heading[1].length, text: heading[2].trim() });
43
+ const anchor = line.text.match(ANCHOR);
44
+ if (anchor)
45
+ anchors.push({ ...line, id: anchor[2] });
46
+ }
47
+ return { lines: visible, headings, anchors };
48
+ }
49
+ function parseDecisionDetailBlocks(content) {
50
+ const scanned = scanVisibleMarkdown(content);
51
+ const details = scanned.headings.filter((heading) => heading.level === 3 && DECISION_ID.test(heading.text));
52
+ return details.map((heading) => {
53
+ const next = scanned.headings.find((candidate) => (candidate.start > heading.start && candidate.level <= 3));
54
+ const end = next?.start ?? content.length;
55
+ return {
56
+ id: heading.text.match(DECISION_ID)[0],
57
+ heading: heading.text,
58
+ canonical: /\[needs-human-decision\]/i.test(heading.text),
59
+ start: heading.start,
60
+ end,
61
+ content: content.slice(heading.start, end).trim()
62
+ };
63
+ });
64
+ }
65
+ function parseDecisionEvidence(evidence) {
66
+ const separator = evidence.indexOf('#');
67
+ if (separator <= 0 || evidence.indexOf('#', separator + 1) !== -1)
68
+ return { ok: false };
69
+ const artifact = evidence.slice(0, separator);
70
+ const anchor = evidence.slice(separator + 1);
71
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*\.md$/.test(artifact))
72
+ return { ok: false };
73
+ if (!anchor || /[\s\u0000-\u001f#]/.test(anchor))
74
+ return { ok: false };
75
+ return { ok: true, artifact, anchor };
76
+ }
77
+ function sameId(left, right) {
78
+ return left.toUpperCase() === right.toUpperCase();
79
+ }
80
+ function resolveDecisionDetail(content, rowId, anchor) {
81
+ const scanned = scanVisibleMarkdown(content);
82
+ const blocks = parseDecisionDetailBlocks(content);
83
+ const explicit = scanned.anchors.filter((candidate) => candidate.id === anchor);
84
+ if (explicit.length > 1) {
85
+ return { status: 'ambiguous', reason: `evidence anchor '${anchor}' occurs more than once`, candidates: [] };
86
+ }
87
+ if (explicit.length === 1) {
88
+ const marker = explicit[0];
89
+ const followingLine = scanned.lines.find((line) => line.start > marker.end && line.text.trim());
90
+ const followingHeading = followingLine
91
+ ? scanned.headings.find((heading) => heading.start === followingLine.start)
92
+ : undefined;
93
+ const candidate = followingHeading?.level === 3
94
+ ? blocks.find((block) => block.start === followingHeading.start)
95
+ : undefined;
96
+ if (!candidate || !sameId(candidate.id, rowId) || !candidate.canonical) {
97
+ return { status: 'missing', reason: `evidence anchor '${anchor}' does not identify a canonical ${rowId} detail` };
98
+ }
99
+ return { status: 'found', block: candidate };
100
+ }
101
+ if (!sameId(anchor, rowId)) {
102
+ return { status: 'missing', reason: `evidence anchor '${anchor}' is not the ledger id '${rowId}'` };
103
+ }
104
+ const matches = blocks.filter((block) => sameId(block.id, rowId) && block.canonical);
105
+ if (matches.length === 0)
106
+ return { status: 'missing', reason: `no canonical ${rowId} detail was found` };
107
+ if (matches.length > 1) {
108
+ return { status: 'ambiguous', reason: `more than one canonical ${rowId} detail was found`, candidates: matches };
109
+ }
110
+ return { status: 'found', block: matches[0] };
111
+ }
112
+ const INFORMAL_SHORT_HEADING = /^(?:AN|PL|CD|HD)-[1-9]\d*[::]\s*(?:上一轮)?简短复核$/i;
113
+ const INFORMAL_SHORT_FRAGMENT = /^简短(?:结论|说明)$/;
114
+ function isInformalShortBlock(block) {
115
+ if (block.canonical || block.content.length > 600)
116
+ return false;
117
+ if (!INFORMAL_SHORT_HEADING.test(block.heading))
118
+ return false;
119
+ const scanned = scanVisibleMarkdown(block.content);
120
+ const bodyLines = scanned.lines
121
+ .filter((line) => !scanned.headings.some((heading) => heading.start === line.start) && line.text.trim())
122
+ .map((line) => line.text);
123
+ if (bodyLines.length === 0 || bodyLines.length > 2)
124
+ return false;
125
+ const fragments = bodyLines.map((line) => line.match(/^\s*[-*]\s+([^\s].*?)\s*$/)?.[1] ?? null);
126
+ if (fragments.some((fragment) => fragment === null))
127
+ return false;
128
+ return fragments.every((fragment) => INFORMAL_SHORT_FRAGMENT.test(fragment));
129
+ }
130
+ function hasDirectPrecedingAnchor(block, scanned) {
131
+ return scanned.anchors.some((anchor) => (anchor.end < block.start
132
+ && !scanned.lines.some((line) => line.start > anchor.end && line.start < block.start && line.text.trim())));
133
+ }
134
+ function inspectDecisionDetailDuplicates(content) {
135
+ const blocks = parseDecisionDetailBlocks(content);
136
+ const scanned = scanVisibleMarkdown(content);
137
+ const grouped = new Map();
138
+ for (const block of blocks) {
139
+ const key = block.id.toUpperCase();
140
+ const group = grouped.get(key) ?? [];
141
+ group.push(block);
142
+ grouped.set(key, group);
143
+ }
144
+ const duplicates = [];
145
+ for (const group of grouped.values()) {
146
+ if (group.length < 2)
147
+ continue;
148
+ const canonical = group.filter((block) => block.canonical);
149
+ const repairable = canonical.length === 1 && group.every((block) => (block.canonical || (!hasDirectPrecedingAnchor(block, scanned) && isInformalShortBlock(block))));
150
+ duplicates.push({ id: canonical[0]?.id ?? group[0].id, blocks: group, repairable });
151
+ }
152
+ if (duplicates.length === 0)
153
+ return { ok: true, blocks };
154
+ const summary = duplicates.map((duplicate) => {
155
+ const types = duplicate.blocks.map((block) => block.canonical ? 'canonical' : 'informal').join(', ');
156
+ return `${duplicate.id} (${duplicate.blocks.length} blocks: ${types}${duplicate.repairable ? '; safe repair available' : ''})`;
157
+ }).join(', ');
158
+ return {
159
+ ok: false,
160
+ code: 'DECISION_DETAIL_DUPLICATE',
161
+ message: `Duplicate decision detail ids: ${summary}`,
162
+ blocks,
163
+ duplicates
164
+ };
165
+ }
166
+ function repairDecisionDetailDuplicates(content) {
167
+ const inspection = inspectDecisionDetailDuplicates(content);
168
+ if (inspection.ok)
169
+ return { ok: true, changed: false, content };
170
+ const unsafe = inspection.duplicates.filter((duplicate) => !duplicate.repairable);
171
+ if (unsafe.length > 0) {
172
+ return {
173
+ ok: false,
174
+ code: 'DECISION_DETAIL_AMBIGUOUS',
175
+ message: `Cannot safely repair duplicate decision detail ids: ${unsafe.map((duplicate) => duplicate.id).join(', ')}`,
176
+ content
177
+ };
178
+ }
179
+ const removals = inspection.duplicates.flatMap((duplicate) => duplicate.blocks.filter((block) => !block.canonical));
180
+ let repaired = content;
181
+ for (const block of removals.sort((left, right) => right.start - left.start)) {
182
+ repaired = repaired.slice(0, block.start) + repaired.slice(block.end);
183
+ }
184
+ return { ok: true, changed: repaired !== content, content: repaired };
185
+ }
186
+ const FIELD_KEYS = {
187
+ '为什么现在要决定': 'why', 'why decide now': 'why', 'why a decision is needed now': 'why',
188
+ '要决定什么': 'decision', 'what to decide': 'decision', 'what needs a decision': 'decision',
189
+ '会影响什么': 'impact', 'what it affects': 'impact', 'what this affects': 'impact',
190
+ '如果选错或暂不处理': 'risk', 'if wrong or postponed': 'risk',
191
+ 'what could go wrong': 'risk',
192
+ '打个比方': 'analogy', '例子': 'analogy', 'example': 'analogy',
193
+ '建议选哪个': 'recommend', 'recommended option': 'recommend', 'recommended choice': 'recommend',
194
+ '为什么这样建议': 'reason', 'why this recommendation': 'reason', 'why this choice': 'reason',
195
+ '是否需要实现': 'implementation', 'implementation required': 'implementation'
196
+ };
197
+ const TERMINAL_FIELD_KEYS = new Set(['recommend', 'reason', 'implementation']);
198
+ function normalizeDecisionDetailForDisplay(block) {
199
+ const body = block.content;
200
+ const scanned = scanVisibleMarkdown(body);
201
+ const heading = scanned.headings.find((candidate) => candidate.level === 3);
202
+ if (!heading)
203
+ return body.trim();
204
+ const options = scanned.headings.filter((candidate) => candidate.level === 4 && OPTION_HEADING.test(candidate.text));
205
+ const optionRanges = options.map((option, index) => ({
206
+ start: option.start,
207
+ end: options[index + 1]?.start
208
+ ?? scanned.headings.find((candidate) => candidate.start > option.start && candidate.level <= 3)?.start
209
+ ?? scanned.lines.find((line) => (line.start > option.start
210
+ && FIELD.test(line.text)
211
+ && TERMINAL_FIELD_KEYS.has(FIELD_KEYS[line.text.match(FIELD)[1].trim().toLowerCase()])))?.start
212
+ ?? body.length
213
+ }));
214
+ const topLines = scanned.lines.filter((line) => (line.start > heading.end && !optionRanges.some((range) => line.start >= range.start && line.start < range.end)));
215
+ const fields = [];
216
+ for (const line of topLines) {
217
+ const match = line.text.match(FIELD);
218
+ if (!match)
219
+ continue;
220
+ const key = FIELD_KEYS[match[1].trim().toLowerCase()];
221
+ fields.push({ key: key ?? null, line });
222
+ }
223
+ if (fields.some((field) => field.key === null))
224
+ return body.trim();
225
+ const byKey = new Map();
226
+ const selectedRanges = [];
227
+ for (let index = 0; index < fields.length; index += 1) {
228
+ const field = fields[index];
229
+ if (!field.key)
230
+ continue;
231
+ const nextOption = optionRanges.find((range) => range.start > field.line.start)?.start ?? body.length;
232
+ const end = Math.min(fields[index + 1]?.line.start ?? body.length, nextOption);
233
+ const value = body.slice(field.line.start, end).trim();
234
+ byKey.set(field.key, value);
235
+ selectedRanges.push({ start: field.line.start, end });
236
+ }
237
+ selectedRanges.push(...optionRanges);
238
+ const required = ['decision', 'why', 'impact', 'recommend', 'reason'];
239
+ if (required.some((key) => !byKey.has(key)) || options.length < 2)
240
+ return body.trim();
241
+ const sortedRanges = selectedRanges.sort((left, right) => left.start - right.start);
242
+ let cursor = heading.end;
243
+ for (const range of sortedRanges) {
244
+ if (/\S/.test(body.slice(cursor, range.start)))
245
+ return body.trim();
246
+ cursor = Math.max(cursor, range.end);
247
+ }
248
+ if (/\S/.test(body.slice(cursor)))
249
+ return body.trim();
250
+ const ordered = [
251
+ byKey.get('decision'), byKey.get('why'), byKey.get('impact')
252
+ ];
253
+ for (const optional of ['risk', 'analogy'])
254
+ if (byKey.has(optional))
255
+ ordered.push(byKey.get(optional));
256
+ ordered.push(...optionRanges.map((range) => body.slice(range.start, range.end).trim()));
257
+ ordered.push(byKey.get('recommend'), byKey.get('reason'));
258
+ if (byKey.has('implementation'))
259
+ ordered.push(byKey.get('implementation'));
260
+ return [body.slice(heading.start, heading.end).trim(), ...ordered].join('\n\n').trim();
261
+ }
262
+ export { inspectDecisionDetailDuplicates, normalizeDecisionDetailForDisplay, parseDecisionDetailBlocks, parseDecisionEvidence, repairDecisionDetailDuplicates, resolveDecisionDetail };
263
+ //# sourceMappingURL=decision-details.js.map
@@ -2,7 +2,7 @@ import fs from 'node:fs';
2
2
  import { appendActivityEntry, locateActivityLog } from './activity-log.js';
3
3
  import { isDecisionItem, listDecisionItems, selectDecisionItem } from './decision-items.js';
4
4
  import { createImplementationInput, finalizeImplementationInput, IMPLEMENTATION_INPUT_ALIASES, parseImplementationInputs, renderImplementationInputs, selectDeclaredImplementationInput } from './implementation-inputs.js';
5
- import { LEDGER_COLUMNS, LEDGER_HEADINGS, parseLedger } from './ledger.js';
5
+ import { LEDGER_COLUMNS, LEDGER_HEADINGS, LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument } from './ledger.js';
6
6
  import { resolveTaskContext } from './resolve-ref.js';
7
7
  import { extractSection, extractSubSection, findSectionHeading } from './sections.js';
8
8
  import { captureTaskWriteMetadata, writeTask } from './write.js';
@@ -64,7 +64,10 @@ function applyHumanDecision(request, options = {}) {
64
64
  let rows;
65
65
  try {
66
66
  content = fs.readFileSync(resolved.taskMdPath, 'utf8');
67
- rows = parseLedger(content);
67
+ const ledger = parseLedgerDocument(content);
68
+ if (!ledger.present)
69
+ return failed(LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, resolved.taskId);
70
+ rows = ledger.rows;
68
71
  }
69
72
  catch (error) {
70
73
  return failed('DECISION_DOCUMENT_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
@@ -15,6 +15,12 @@ function hasExactKeys(value, required, optional = []) {
15
15
  function exactText(value) {
16
16
  return typeof value === 'string' && value.length > 0 && value.trim() === value;
17
17
  }
18
+ function validCodexBuildMetadata(value) {
19
+ return Number.isSafeInteger(value.protocolVersion) && value.protocolVersion > 0
20
+ && exactText(value.packageVersion)
21
+ && exactText(value.internalExecutableBuildHash)
22
+ && exactText(value.lifecycleContractHash);
23
+ }
18
24
  function nullableText(value) {
19
25
  return value === null || exactText(value);
20
26
  }
@@ -92,9 +98,6 @@ function hasCurrentCodexEvidence(receipt) {
92
98
  const host = receipt.hostEvidence;
93
99
  if (host?.kind !== 'codex-lifecycle-v2'
94
100
  || host.protocolVersion !== provenance.protocolVersion
95
- || host.packageVersion !== provenance.packageVersion
96
- || host.internalExecutableBuildHash !== provenance.internalExecutableBuildHash
97
- || host.lifecycleContractHash !== provenance.lifecycleContractHash
98
101
  || host.hookDefinitionHash !== provenance.hookDefinitionHash
99
102
  || host.hookSource !== provenance.hookSource
100
103
  || host.hookSourcePathDigest !== provenance.hookSourcePathDigest
@@ -379,9 +382,6 @@ function activateDelegation(receipt, event, options = {}) {
379
382
  const activationDeadlineAt = Date.parse(receipt.activationDeadlineAt);
380
383
  if (!expected
381
384
  || event.hostEvidence.protocolVersion !== expected.protocolVersion
382
- || event.hostEvidence.packageVersion !== expected.packageVersion
383
- || event.hostEvidence.internalExecutableBuildHash !== expected.internalExecutableBuildHash
384
- || event.hostEvidence.lifecycleContractHash !== expected.lifecycleContractHash
385
385
  || event.hostEvidence.hookDefinitionHash !== expected.hookDefinitionHash
386
386
  || event.hostEvidence.hookSource !== expected.hookSource
387
387
  || event.hostEvidence.hookSourcePathDigest !== expected.hookSourcePathDigest
@@ -395,7 +395,8 @@ function activateDelegation(receipt, event, options = {}) {
395
395
  || spawnObservedAt > activationDeadlineAt
396
396
  || event.parentId !== expected.capabilitySessionId
397
397
  || (event.hostEvidence.controllerInstanceDigest ?? null) !== expected.controllerInstanceDigest
398
- || (event.hostEvidence.controlGeneration ?? null) !== expected.controlGeneration)
398
+ || (event.hostEvidence.controlGeneration ?? null) !== expected.controlGeneration
399
+ || !validCodexBuildMetadata(event.hostEvidence))
399
400
  return fail('DELEGATION_HOST_EVIDENCE_INVALID', 'Codex lifecycle provenance does not match the prepared receipt');
400
401
  }
401
402
  return { ok: true, receipt: Object.freeze({
@@ -1,11 +1,11 @@
1
1
  import fs from 'node:fs';
2
- import { appendActivityEntry, locateActivityLog, pairEntries } from './activity-log.js';
2
+ import { appendActivityEntry, locateActivityLog, pairEntries, startedBackedRows } from './activity-log.js';
3
3
  import { buildArtifactLinkSection, artifactName, inspectArtifactDirectory, parseReviewedInputReference, parseCodePlanInputReference, parseArtifactName, resolveArtifactContext, validateCompletedArtifact } from './artifact-lifecycle.js';
4
4
  import { ArtifactReceiptError, sha256File, upsertArtifactReceipt } from './artifact-receipts.js';
5
5
  import { parseTypedTaskFrontmatter } from './frontmatter.js';
6
6
  import { consumeImplementationInput, IMPLEMENTATION_INPUT_ALIASES, parseImplementationInputs, renderImplementationInputs } from './implementation-inputs.js';
7
- import { parseLedger, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
8
- import { parseReviewSummary } from './review-artifacts.js';
7
+ import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
8
+ import { parseReviewSummary, resolveCanonicalVerdict } from './review-artifacts.js';
9
9
  import { resolveTaskRef } from './resolve-ref.js';
10
10
  import { findSectionHeading } from './sections.js';
11
11
  import { validateLifecycleExecution } from './lifecycle-execution.js';
@@ -101,7 +101,10 @@ function validateReviewFindingCounts(request, content, family, artifactPath) {
101
101
  const stage = REVIEW_LEDGER_STAGES[family];
102
102
  if (!stage || !artifactPath)
103
103
  return null;
104
- const rows = parseLedger(content);
104
+ const ledger = parseLedgerDocument(content);
105
+ if (!ledger.present)
106
+ return { code: 'EVENT_FINDING_COUNT_MISMATCH', message: `${LEDGER_SECTION_MISSING_CODE}: ${LEDGER_SECTION_MISSING_MESSAGE}` };
107
+ const rows = ledger.rows;
105
108
  const invalid = validateLedgerRows(rows);
106
109
  if (invalid)
107
110
  return { code: 'TASK_DOCUMENT_INVALID', message: `${invalid.code}: ${invalid.message}` };
@@ -125,6 +128,9 @@ function validateReviewFindingCounts(request, content, family, artifactPath) {
125
128
  message: parsed.ok ? 'review summary finding counts are not finalized' : parsed.message
126
129
  };
127
130
  }
131
+ const canonical = resolveCanonicalVerdict(parsed.summary);
132
+ if (!canonical.ok)
133
+ return { code: 'EVENT_VERDICT_INVALID', message: `${canonical.code}: ${canonical.message}` };
128
134
  const report = parsed.summary.counts;
129
135
  const reportVerdict = parsed.summary.verdict === 'Approved'
130
136
  ? 'approved'
@@ -352,7 +358,7 @@ function applyTaskEventUnlocked(request, options = {}) {
352
358
  const section = locateActivityLog(content);
353
359
  if (!section)
354
360
  return failed(request, { code: 'EVENT_LOG_MISSING', message: 'task has no unique Activity Log section' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
355
- const rows = pairEntries(section.entries);
361
+ const rows = startedBackedRows(pairEntries(section.entries));
356
362
  let normalized = request;
357
363
  let artifactContext = null;
358
364
  const initialParts = eventParts(request.event);
@@ -269,11 +269,18 @@ function mutationPatch(current, mutation) {
269
269
  }
270
270
  if (mutation.scope === 'verification') {
271
271
  if (mutation.operation === 'succeeded') {
272
+ const resolvedTaskWarning = current.warnings.some((warning) => warning.step === 'verification' && warning.status === 'open');
272
273
  const warnings = resolveStepWarnings(current, 'verification');
273
- return { verification: 'done', warningProjection: warnings.length > 0 ? 'pending' : 'done', warnings, lastError: null };
274
+ return {
275
+ verification: 'done',
276
+ taskComment: resolvedTaskWarning ? 'pending' : current.taskComment,
277
+ warningProjection: warnings.length > 0 ? 'pending' : 'done',
278
+ warnings,
279
+ lastError: null
280
+ };
274
281
  }
275
282
  const warnings = replaceWarning(current, warningFromError('verification', mutation.error), 'open');
276
- return { verification: 'pending', warningProjection: 'pending', warnings, lastError: mutation.error };
283
+ return { verification: 'pending', taskComment: 'pending', warningProjection: 'pending', warnings, lastError: mutation.error };
277
284
  }
278
285
  return mutation.operation === 'succeeded'
279
286
  ? { warningProjection: 'done', lastError: null }
@@ -339,6 +346,50 @@ function reconcileWarningProjection(repoRoot, taskId, receipt, consumed) {
339
346
  return receipt;
340
347
  }
341
348
  }
349
+ function syncPendingTaskComment(input) {
350
+ const { repoRoot, taskId, agent, commentSync, consumedCapabilities } = input;
351
+ let receipt = input.receipt;
352
+ if (receipt.taskComment !== 'pending') {
353
+ return {
354
+ receipt,
355
+ step: { status: receipt.taskComment === 'skipped' ? 'skipped' : 'no-op', changed: false, error: null },
356
+ changed: false,
357
+ error: null
358
+ };
359
+ }
360
+ try {
361
+ const result = commentSync(taskId, { kind: 'task', agent, cwd: repoRoot });
362
+ const step = commentStep(result);
363
+ if (result.status === 'applied' || result.status === 'no-op') {
364
+ const skipped = result.error?.code === 'ISSUE_NOT_LINKED';
365
+ const capability = issueCapability(receipt, 'task-comment');
366
+ receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
367
+ scope: 'task-comment', operation: 'succeeded', state: skipped ? 'skipped' : 'done'
368
+ }, consumedCapabilities);
369
+ receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
370
+ return { receipt, step: skipped ? { ...step, status: 'skipped' } : step, changed: result.changed, error: null };
371
+ }
372
+ const detail = step.error ?? { code: 'COMMENT_SYNC_FAILED', message: 'task comment synchronization failed', retryable: true };
373
+ const capability = issueCapability(receipt, 'task-comment');
374
+ receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
375
+ scope: 'task-comment', operation: 'failed', error: detail
376
+ }, consumedCapabilities);
377
+ receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
378
+ return { receipt, step, changed: result.changed, error: detail };
379
+ }
380
+ catch (error) {
381
+ const detail = errorOf(error, 'COMMENT_SYNC_FAILED', true);
382
+ try {
383
+ const capability = issueCapability(receipt, 'task-comment');
384
+ receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
385
+ scope: 'task-comment', operation: 'failed', error: detail
386
+ }, consumedCapabilities);
387
+ receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
388
+ }
389
+ catch { /* preserve the primary error */ }
390
+ return { receipt, step: { status: 'blocked', changed: false, error: detail }, changed: false, error: detail };
391
+ }
392
+ }
342
393
  function terminalResult(taskId, receipt, steps, changed, error = null) {
343
394
  const pending = pendingSteps(receipt);
344
395
  const blocked = [steps.lifecycle, steps.taskComment, steps.verification].some((step) => step?.status === 'blocked');
@@ -458,50 +509,15 @@ function applyUnderLock(request, taskId, options) {
458
509
  return failed(taskId, detail, { lifecycle: lifecycleResult });
459
510
  }
460
511
  let taskComment = null;
461
- try {
462
- receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
463
- if (receipt.taskComment !== 'pending') {
464
- taskComment = {
465
- status: receipt.taskComment === 'skipped' ? 'skipped' : 'no-op',
466
- changed: false,
467
- error: null
468
- };
469
- }
470
- else {
471
- const result = commentSync(taskId, { kind: 'task', agent: request.agent, cwd: repoRoot });
472
- taskComment = commentStep(result);
473
- if (result.status === 'applied' || result.status === 'no-op') {
474
- const skipped = result.error?.code === 'ISSUE_NOT_LINKED';
475
- const capability = issueCapability(receipt, 'task-comment');
476
- receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
477
- scope: 'task-comment', operation: 'succeeded', state: skipped ? 'skipped' : 'done'
478
- }, consumedCapabilities);
479
- receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
480
- taskComment = skipped ? { ...taskComment, status: 'skipped' } : taskComment;
481
- changed = changed || result.changed;
482
- }
483
- else {
484
- const detail = taskComment.error ?? { code: 'COMMENT_SYNC_FAILED', message: 'task comment synchronization failed', retryable: true };
485
- const capability = issueCapability(receipt, 'task-comment');
486
- receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
487
- scope: 'task-comment', operation: 'failed', error: detail
488
- }, consumedCapabilities);
489
- receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
490
- return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification: null }, changed, taskComment.error);
491
- }
492
- }
493
- }
494
- catch (error) {
495
- const detail = errorOf(error, 'COMMENT_SYNC_FAILED', true);
496
- try {
497
- const capability = issueCapability(receipt, 'task-comment');
498
- receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
499
- scope: 'task-comment', operation: 'failed', error: detail
500
- }, consumedCapabilities);
501
- receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
502
- }
503
- catch { /* preserve the primary error */ }
504
- return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment: { status: 'blocked', changed: false, error: detail }, verification: null }, changed, detail);
512
+ receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
513
+ const initialComment = syncPendingTaskComment({
514
+ repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities
515
+ });
516
+ receipt = initialComment.receipt;
517
+ taskComment = initialComment.step;
518
+ changed = changed || initialComment.changed;
519
+ if (initialComment.error) {
520
+ return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification: null }, changed, initialComment.error);
505
521
  }
506
522
  let verification = null;
507
523
  try {
@@ -518,6 +534,16 @@ function applyUnderLock(request, taskId, options) {
518
534
  scope: 'verification', operation: 'succeeded'
519
535
  }, consumedCapabilities);
520
536
  receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
537
+ const finalComment = syncPendingTaskComment({
538
+ repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities
539
+ });
540
+ receipt = finalComment.receipt;
541
+ if (finalComment.step.status !== 'no-op')
542
+ taskComment = finalComment.step;
543
+ changed = changed || finalComment.changed;
544
+ if (finalComment.error) {
545
+ return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification }, changed, finalComment.error);
546
+ }
521
547
  }
522
548
  else {
523
549
  const detail = verification.error ?? { code: 'VERIFY_FAILED', message: 'verification failed', retryable: true };
@@ -1,36 +1,33 @@
1
1
  const USAGE = `Usage: ai task <command> [options]
2
2
 
3
3
  Commands:
4
- cat [--task <ref>] <artifact | N> Print a task artifact (by name or number)
5
- decisions, d [--task <ref>] [-i item] List review decisions, or show one item's detail
6
- files [--task <ref>] List artifacts in a task dir (numbered)
7
- grep <pattern> [--current|--task <ref>] Literal search across task artifacts (global by default)
8
- issue-body [--task <ref>] [--template] Print a deterministic Issue body from task.md
9
- log [--task <ref>] Render a task's activity log as a timeline
4
+ cat [--task <ref> | -t <ref>] <artifact | N> Print a task artifact (by name or number)
5
+ decisions, d [--task <ref> | -t <ref>] [--item <selector> | -i <selector>] List review decisions, or show one item's detail
6
+ files [--task <ref> | -t <ref>] List artifacts in a task dir (numbered)
7
+ grep [--current | --task <ref> | -t <ref>] <pattern> Literal search in the current or selected task
8
+ issue-body [--task <ref> | -t <ref>] [--template <path>] Print a deterministic Issue body from task.md
9
+ log [--task <ref> | -t <ref>] Render a task's activity log as a timeline
10
10
  ls [--all | --blocked | --completed] List tasks (default: active)
11
- show [--task <ref>] Print a task.md
12
- status [--task <ref>] Aggregated status view (metadata / artifacts / git / platform)
13
- validate <ref> [options] -- <command> Run host validation in snapshot or inplace scope
11
+ show [--task <ref> | -t <ref>] Print a task.md
12
+ status [--task <ref> | -t <ref>] Aggregated status view (metadata / artifacts / git / platform)
14
13
 
15
14
  Examples:
16
- ai task cat 11 analysis
17
15
  ai task cat analysis
18
- ai task cat 11 3
19
- ai task decisions 11
16
+ ai task cat --task 11 analysis
17
+ ai task decisions --task 11
20
18
  ai task decisions --item 1
21
- ai task d 11 PL-3 --format markdown
22
- ai task files 11
19
+ ai task d --task 11 --item PL-3 --format markdown
20
+ ai task files --task 11
23
21
  ai task grep resolveArtifact
24
22
  ai task grep resolveArtifact --current
25
- ai task grep resolveArtifact 11
26
- ai task issue-body 11
27
- ai task issue-body 11 --template .github/ISSUE_TEMPLATE/05_other.yml
28
- ai task log 11
23
+ ai task grep resolveArtifact --task 11
24
+ ai task issue-body --task 11
25
+ ai task issue-body --task 11 --template .github/ISSUE_TEMPLATE/05_other.yml
26
+ ai task log --task 11
29
27
  ai task ls
30
- ai task show 11
31
- ai task show TASK-20260612-162737
32
- ai task status 11
33
- ai task validate 11 -- npm test
28
+ ai task show --task 11
29
+ ai task show --task TASK-20260612-162737
30
+ ai task status --task 11
34
31
 
35
32
  Run 'ai task <command> --help' for details.`;
36
33
  export async function runTask(args) {
@@ -91,13 +88,6 @@ export async function runTask(args) {
91
88
  status(rest);
92
89
  break;
93
90
  }
94
- case 'validate': {
95
- const { validate } = await import('./commands/validate.js');
96
- const exitCode = await validate(rest);
97
- if (exitCode !== 0)
98
- process.exitCode = exitCode;
99
- break;
100
- }
101
91
  default:
102
92
  process.stderr.write(`Unknown task command: ${subcommand}\n\n`);
103
93
  process.stdout.write(`${USAGE}\n`);
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { declareImplementationInput, IMPLEMENTATION_INPUT_ALIASES, parseImplementationInputs, renderImplementationInputs } from './implementation-inputs.js';
4
- import { LEDGER_COLUMNS, LEDGER_HEADINGS, nextHdId, parseLedger, validateLedgerRows } from './ledger.js';
4
+ import { LEDGER_COLUMNS, LEDGER_HEADINGS, LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, nextHdId, parseLedgerDocument, validateLedgerRows } from './ledger.js';
5
5
  import { resolveTaskRef } from './resolve-ref.js';
6
6
  import { writeTask } from './write.js';
7
7
  import { allowsManualOverride } from './guard-override.js';
@@ -26,6 +26,8 @@ function normalizedEvidence(value) {
26
26
  }
27
27
  function ledgerReadErrorCode(error) {
28
28
  const code = error && typeof error === 'object' && 'code' in error ? String(error.code) : '';
29
+ if (code === LEDGER_SECTION_MISSING_CODE)
30
+ return code;
29
31
  return code === 'TABLE_DUPLICATE_KEY' ? 'LEDGER_DUPLICATE_ID' : 'LEDGER_DOCUMENT_INVALID';
30
32
  }
31
33
  function maxHandshakeRounds(repoRoot) {
@@ -60,13 +62,7 @@ function rowMutation(row) {
60
62
  };
61
63
  }
62
64
  function ledgerSectionMutation(content) {
63
- if (/^##\s+(审查分歧账本|Review Disagreement Ledger)\s*$/m.test(content))
64
- return [];
65
- const english = /^##\s+Activity Log\s*$/m.test(content);
66
- return [{
67
- kind: 'section', aliases: LEDGER_HEADINGS, heading: english ? 'Review Disagreement Ledger' : '审查分歧账本',
68
- body: `| ${LEDGER_COLUMNS.join(' | ')} |\n|----|-------|-------|----------|--------|----------|`
69
- }];
65
+ return [];
70
66
  }
71
67
  function implementationInputMutation(content, rows) {
72
68
  const english = /^##\s+Activity Log\s*$/m.test(content);
@@ -92,7 +88,10 @@ function applyLedgerIntent(intent, options = {}) {
92
88
  let rows;
93
89
  try {
94
90
  content = fs.readFileSync(resolved.taskMdPath, 'utf8');
95
- rows = parseLedger(content);
91
+ const ledger = parseLedgerDocument(content);
92
+ if (!ledger.present)
93
+ return failed(intent, LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, resolved.taskId);
94
+ rows = ledger.rows;
96
95
  }
97
96
  catch (error) {
98
97
  return failed(intent, ledgerReadErrorCode(error), error instanceof Error ? error.message : String(error), resolved.taskId);