@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
@@ -85,6 +85,13 @@ type TaskFinalizationStep = Readonly<{
85
85
  pendingSteps?: readonly string[];
86
86
  }>;
87
87
 
88
+ type TaskCommentSyncOutcome = Readonly<{
89
+ receipt: TaskFinalizationReceipt;
90
+ step: TaskFinalizationStep;
91
+ changed: boolean;
92
+ error: FinalizationError | null;
93
+ }>;
94
+
88
95
  type TaskFinalizationResult = Readonly<{
89
96
  status: 'completed' | 'failed' | 'blocked';
90
97
  changed: boolean;
@@ -390,11 +397,18 @@ function mutationPatch(current: TaskFinalizationReceipt, mutation: FinalizationM
390
397
  }
391
398
  if (mutation.scope === 'verification') {
392
399
  if (mutation.operation === 'succeeded') {
400
+ const resolvedTaskWarning = current.warnings.some((warning) => warning.step === 'verification' && warning.status === 'open');
393
401
  const warnings = resolveStepWarnings(current, 'verification');
394
- return { verification: 'done', warningProjection: warnings.length > 0 ? 'pending' : 'done', warnings, lastError: null };
402
+ return {
403
+ verification: 'done',
404
+ taskComment: resolvedTaskWarning ? 'pending' : current.taskComment,
405
+ warningProjection: warnings.length > 0 ? 'pending' : 'done',
406
+ warnings,
407
+ lastError: null
408
+ };
395
409
  }
396
410
  const warnings = replaceWarning(current, warningFromError('verification', mutation.error), 'open');
397
- return { verification: 'pending', warningProjection: 'pending', warnings, lastError: mutation.error };
411
+ return { verification: 'pending', taskComment: 'pending', warningProjection: 'pending', warnings, lastError: mutation.error };
398
412
  }
399
413
  return mutation.operation === 'succeeded'
400
414
  ? { warningProjection: 'done', lastError: null }
@@ -472,6 +486,56 @@ function reconcileWarningProjection(repoRoot: string, taskId: string, receipt: T
472
486
  catch { return receipt; }
473
487
  }
474
488
 
489
+ function syncPendingTaskComment(input: {
490
+ repoRoot: string;
491
+ taskId: string;
492
+ agent: string;
493
+ receipt: TaskFinalizationReceipt;
494
+ commentSync: typeof syncPlatformComment;
495
+ consumedCapabilities: Set<string>;
496
+ }): TaskCommentSyncOutcome {
497
+ const { repoRoot, taskId, agent, commentSync, consumedCapabilities } = input;
498
+ let receipt = input.receipt;
499
+ if (receipt.taskComment !== 'pending') {
500
+ return {
501
+ receipt,
502
+ step: { status: receipt.taskComment === 'skipped' ? 'skipped' : 'no-op', changed: false, error: null },
503
+ changed: false,
504
+ error: null
505
+ };
506
+ }
507
+ try {
508
+ const result = commentSync(taskId, { kind: 'task', agent, cwd: repoRoot });
509
+ const step = commentStep(result);
510
+ if (result.status === 'applied' || result.status === 'no-op') {
511
+ const skipped = result.error?.code === 'ISSUE_NOT_LINKED';
512
+ const capability = issueCapability(receipt, 'task-comment');
513
+ receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
514
+ scope: 'task-comment', operation: 'succeeded', state: skipped ? 'skipped' : 'done'
515
+ }, consumedCapabilities);
516
+ receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
517
+ return { receipt, step: skipped ? { ...step, status: 'skipped' } : step, changed: result.changed, error: null };
518
+ }
519
+ const detail = step.error ?? { code: 'COMMENT_SYNC_FAILED', message: 'task comment synchronization failed', retryable: true };
520
+ const capability = issueCapability(receipt, 'task-comment');
521
+ receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
522
+ scope: 'task-comment', operation: 'failed', error: detail
523
+ }, consumedCapabilities);
524
+ receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
525
+ return { receipt, step, changed: result.changed, error: detail };
526
+ } catch (error) {
527
+ const detail = errorOf(error, 'COMMENT_SYNC_FAILED', true);
528
+ try {
529
+ const capability = issueCapability(receipt, 'task-comment');
530
+ receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
531
+ scope: 'task-comment', operation: 'failed', error: detail
532
+ }, consumedCapabilities);
533
+ receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
534
+ } catch { /* preserve the primary error */ }
535
+ return { receipt, step: { status: 'blocked', changed: false, error: detail }, changed: false, error: detail };
536
+ }
537
+ }
538
+
475
539
  function terminalResult(
476
540
  taskId: string,
477
541
  receipt: TaskFinalizationReceipt,
@@ -603,46 +667,15 @@ function applyUnderLock(
603
667
  }
604
668
 
605
669
  let taskComment: TaskFinalizationStep | null = null;
606
- try {
607
- receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
608
- if (receipt.taskComment !== 'pending') {
609
- taskComment = {
610
- status: receipt.taskComment === 'skipped' ? 'skipped' : 'no-op',
611
- changed: false,
612
- error: null
613
- };
614
- } else {
615
- const result = commentSync(taskId, { kind: 'task', agent: request.agent, cwd: repoRoot });
616
- taskComment = commentStep(result);
617
- if (result.status === 'applied' || result.status === 'no-op') {
618
- const skipped = result.error?.code === 'ISSUE_NOT_LINKED';
619
- const capability = issueCapability(receipt, 'task-comment');
620
- receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
621
- scope: 'task-comment', operation: 'succeeded', state: skipped ? 'skipped' : 'done'
622
- }, consumedCapabilities);
623
- receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
624
- taskComment = skipped ? { ...taskComment, status: 'skipped' } : taskComment;
625
- changed = changed || result.changed;
626
- } else {
627
- const detail = taskComment.error ?? { code: 'COMMENT_SYNC_FAILED', message: 'task comment synchronization failed', retryable: true };
628
- const capability = issueCapability(receipt, 'task-comment');
629
- receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
630
- scope: 'task-comment', operation: 'failed', error: detail
631
- }, consumedCapabilities);
632
- receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
633
- return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification: null }, changed, taskComment.error);
634
- }
635
- }
636
- } catch (error) {
637
- const detail = errorOf(error, 'COMMENT_SYNC_FAILED', true);
638
- try {
639
- const capability = issueCapability(receipt, 'task-comment');
640
- receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
641
- scope: 'task-comment', operation: 'failed', error: detail
642
- }, consumedCapabilities);
643
- receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
644
- } catch { /* preserve the primary error */ }
645
- return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment: { status: 'blocked', changed: false, error: detail }, verification: null }, changed, detail);
670
+ receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
671
+ const initialComment = syncPendingTaskComment({
672
+ repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities
673
+ });
674
+ receipt = initialComment.receipt;
675
+ taskComment = initialComment.step;
676
+ changed = changed || initialComment.changed;
677
+ if (initialComment.error) {
678
+ return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification: null }, changed, initialComment.error);
646
679
  }
647
680
 
648
681
  let verification: TaskFinalizationStep | null = null;
@@ -662,6 +695,15 @@ function applyUnderLock(
662
695
  scope: 'verification', operation: 'succeeded'
663
696
  }, consumedCapabilities);
664
697
  receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
698
+ const finalComment = syncPendingTaskComment({
699
+ repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities
700
+ });
701
+ receipt = finalComment.receipt;
702
+ if (finalComment.step.status !== 'no-op') taskComment = finalComment.step;
703
+ changed = changed || finalComment.changed;
704
+ if (finalComment.error) {
705
+ return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification }, changed, finalComment.error);
706
+ }
665
707
  } else {
666
708
  const detail = verification.error ?? { code: 'VERIFY_FAILED', message: 'verification failed', retryable: true };
667
709
  const capability = issueCapability(receipt, 'verification');
package/lib/task/index.ts CHANGED
@@ -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
 
@@ -95,12 +92,6 @@ export async function runTask(args: string[]): Promise<void> {
95
92
  status(rest);
96
93
  break;
97
94
  }
98
- case 'validate': {
99
- const { validate } = await import('./commands/validate.ts');
100
- const exitCode = await validate(rest);
101
- if (exitCode !== 0) process.exitCode = exitCode;
102
- break;
103
- }
104
95
  default:
105
96
  process.stderr.write(`Unknown task command: ${subcommand}\n\n`);
106
97
  process.stdout.write(`${USAGE}\n`);
@@ -7,7 +7,7 @@ import {
7
7
  parseImplementationInputs,
8
8
  renderImplementationInputs
9
9
  } from './implementation-inputs.ts';
10
- import { LEDGER_COLUMNS, LEDGER_HEADINGS, nextHdId, parseLedger, validateLedgerRows } from './ledger.ts';
10
+ import { LEDGER_COLUMNS, LEDGER_HEADINGS, LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, nextHdId, parseLedgerDocument, validateLedgerRows } from './ledger.ts';
11
11
  import type { LedgerRow, ReviewStage } from './ledger.ts';
12
12
  import { resolveTaskRef } from './resolve-ref.ts';
13
13
  import { writeTask } from './write.ts';
@@ -61,6 +61,7 @@ function normalizedEvidence(value: string): string {
61
61
 
62
62
  function ledgerReadErrorCode(error: unknown): string {
63
63
  const code = error && typeof error === 'object' && 'code' in error ? String(error.code) : '';
64
+ if (code === LEDGER_SECTION_MISSING_CODE) return code;
64
65
  return code === 'TABLE_DUPLICATE_KEY' ? 'LEDGER_DUPLICATE_ID' : 'LEDGER_DOCUMENT_INVALID';
65
66
  }
66
67
 
@@ -96,12 +97,7 @@ function rowMutation(row: LedgerRow): TaskMutation {
96
97
  }
97
98
 
98
99
  function ledgerSectionMutation(content: string): TaskMutation[] {
99
- if (/^##\s+(审查分歧账本|Review Disagreement Ledger)\s*$/m.test(content)) return [];
100
- const english = /^##\s+Activity Log\s*$/m.test(content);
101
- return [{
102
- kind: 'section', aliases: LEDGER_HEADINGS, heading: english ? 'Review Disagreement Ledger' : '审查分歧账本',
103
- body: `| ${LEDGER_COLUMNS.join(' | ')} |\n|----|-------|-------|----------|--------|----------|`
104
- }];
100
+ return [];
105
101
  }
106
102
 
107
103
  function implementationInputMutation(content: string, rows: Parameters<typeof renderImplementationInputs>[0]): TaskMutation {
@@ -127,7 +123,9 @@ function applyLedgerIntent(intent: LedgerIntent, options: TaskWriteOptions = {})
127
123
  let rows: LedgerRow[];
128
124
  try {
129
125
  content = fs.readFileSync(resolved.taskMdPath, 'utf8');
130
- rows = parseLedger(content);
126
+ const ledger = parseLedgerDocument(content);
127
+ if (!ledger.present) return failed(intent, LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, resolved.taskId);
128
+ rows = ledger.rows as LedgerRow[];
131
129
  } catch (error) {
132
130
  return failed(intent, ledgerReadErrorCode(error), error instanceof Error ? error.message : String(error), resolved.taskId);
133
131
  }
@@ -20,6 +20,10 @@ type LedgerRow = {
20
20
  /** Zero-based line within the normalized ledger section body. */
21
21
  sourceLine: number;
22
22
  };
23
+ type LedgerDocument = {
24
+ present: boolean;
25
+ rows: readonly LedgerRow[];
26
+ };
23
27
 
24
28
  type ReviewStage = 'analysis' | 'plan' | 'code';
25
29
  type ReviewSeverity = 'blocker' | 'major' | 'minor';
@@ -40,6 +44,8 @@ const LEDGER_TERMINAL = new Set(['confirmed', 'closed', 'human-decided']);
40
44
  // Statuses that represent a decision row awaiting or carrying a human ruling.
41
45
  const HUMAN_DECISION_STATUSES = new Set(['needs-human-decision', 'human-decided']);
42
46
  const FINDING_STATUSES = new Set(['open', 'accepted', 'adjusted', 'refuted', 'cannot-judge', 'confirmed', 'needs-human-decision', 'closed', 'human-decided']);
47
+ const LEDGER_SECTION_MISSING_CODE = 'LEDGER_SECTION_MISSING';
48
+ const LEDGER_SECTION_MISSING_MESSAGE = 'review disagreement ledger section is missing';
43
49
 
44
50
  function isReviewStage(stage: string): stage is ReviewStage {
45
51
  return stage === 'analysis' || stage === 'plan' || stage === 'code';
@@ -48,12 +54,16 @@ function isReviewStage(stage: string): stage is ReviewStage {
48
54
  // Parse all rows of the disagreement ledger table. Skips the heading, the
49
55
  // header row (`| id | ... |`) and the `|---|` separator; ignores non-`|` lines.
50
56
  // Rows with fewer than 6 columns are skipped (mirrors the JS gate parser).
51
- function parseLedger(content: string): LedgerRow[] {
57
+ function parseLedgerDocument(content: string): LedgerDocument {
52
58
  const table = parseTable(content, { sectionAliases: LEDGER_HEADINGS, columns: LEDGER_COLUMNS });
53
- return table?.rows.map(({ values, sourceLine }) => ({
54
- id: values.id!, stage: values.stage!, round: values.round!, severity: values.severity!,
55
- status: values.status!, evidence: values.evidence!, sourceLine
56
- })) ?? [];
59
+ if (!table) return { present: false, rows: [] };
60
+ return {
61
+ present: true,
62
+ rows: table.rows.map(({ values, sourceLine }) => ({
63
+ id: values.id!, stage: values.stage!, round: values.round!, severity: values.severity!,
64
+ status: values.status!, evidence: values.evidence!, sourceLine
65
+ }))
66
+ };
57
67
  }
58
68
 
59
69
  function validateLedgerRows(rows: readonly LedgerRow[]): LedgerValidationError | null {
@@ -130,5 +140,5 @@ function nextHdId(rows: readonly LedgerRow[]): string {
130
140
  return `HD-${max + 1}`;
131
141
  }
132
142
 
133
- export { LEDGER_HEADINGS, LEDGER_COLUMNS, parseLedger, nextHdId, isReviewStage, validateLedgerRows, summarizeLedgerStage, LEDGER_TERMINAL, HUMAN_DECISION_STATUSES };
134
- export type { LedgerRow, ReviewStage, ReviewSeverity, LedgerValidationError, LedgerStageStatus };
143
+ export { LEDGER_HEADINGS, LEDGER_COLUMNS, parseLedgerDocument, nextHdId, isReviewStage, validateLedgerRows, summarizeLedgerStage, LEDGER_TERMINAL, HUMAN_DECISION_STATUSES, LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE };
144
+ export type { LedgerRow, LedgerDocument, ReviewStage, ReviewSeverity, LedgerValidationError, LedgerStageStatus };
@@ -12,6 +12,8 @@ import {
12
12
  } from './short-id.ts';
13
13
  import { captureTaskWriteMetadata, writeTask } from './write.ts';
14
14
  import type { TaskFileSystem, TaskMutation, TaskOperationSummary, TaskWriteMetadata } from './write.ts';
15
+ import { validateCurrentTaskContract } from './current-contract.ts';
16
+ import { isValidAgentInfraVersion } from '../version.ts';
15
17
 
16
18
  const lifecycleIntentCatalog = [
17
19
  'block', 'activate', 'cancel', 'complete', 'close-codescan', 'close-dependabot', 'restore'
@@ -126,6 +128,14 @@ function failed(request: Pick<TaskLifecycleRequest, 'taskRef' | 'intent'>, error
126
128
  };
127
129
  }
128
130
 
131
+ export function taskLifecycleFailure(
132
+ request: Pick<TaskLifecycleRequest, 'taskRef' | 'intent'>,
133
+ error: LifecycleError,
134
+ taskId: string | null = null
135
+ ): TaskLifecycleResult {
136
+ return failed(request, error, { taskId });
137
+ }
138
+
129
139
  function normalizedRequest(request: TaskLifecycleRequest): TaskLifecycleRequest | LifecycleError {
130
140
  if (!request || typeof request !== 'object' || !lifecycleIntentCatalog.includes(request.intent as TaskLifecycleIntent)) {
131
141
  return { code: 'LIFECYCLE_PAYLOAD_INVALID', message: 'intent is not in the lifecycle catalog' };
@@ -385,6 +395,10 @@ function applyTaskLifecycle(requestInput: TaskLifecycleRequest, options: TaskLif
385
395
  catch (error) { return failed(request, { code: 'LIFECYCLE_DOCUMENT_INVALID', message: error instanceof Error ? error.message : String(error) }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath }); }
386
396
  if (frontmatter.id !== taskId) return failed(request, { code: 'LIFECYCLE_IDENTITY_INVALID', message: 'task.md id does not match its lifecycle task id' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
387
397
  if (!locateActivityLog(content) && !allowsManualOverride(options, 'LIFECYCLE_LOG_MISSING')) return failed(request, { code: 'LIFECYCLE_LOG_MISSING', message: 'task has no unique Activity Log section' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
398
+ if (spec.target === 'active') {
399
+ const contract = validateCurrentTaskContract(content);
400
+ if (!contract.ok) return failed(request, { code: 'LIFECYCLE_DOCUMENT_INVALID', message: contract.message }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
401
+ }
388
402
  const registryRequest = executeShortIdCommand({
389
403
  operation: 'list', activeDir: path.join(repoRoot, '.agents', 'workspace', 'active'),
390
404
  shortIdLength: configuredShortIdLength(repoRoot)
@@ -408,6 +422,9 @@ function applyTaskLifecycle(requestInput: TaskLifecycleRequest, options: TaskLif
408
422
  try { journal = parseJournal(io.readFileSync(initialJournalPath)); }
409
423
  catch (error) { return failed(request, { code: 'LIFECYCLE_JOURNAL_INVALID', message: String(error) }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath: initialJournalPath }); }
410
424
  if (journal.taskId !== taskId || journal.intent !== request.intent) return failed(request, { code: 'LIFECYCLE_INTENT_CONFLICT', message: 'an in-progress lifecycle journal belongs to a different request' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath: initialJournalPath });
425
+ if (spec.target === 'active' && !isValidAgentInfraVersion(journal.metadata.agentInfraVersion)) {
426
+ return failed(request, { code: 'LIFECYCLE_DOCUMENT_INVALID', message: `journal metadata agentInfraVersion must be a valid v-prefixed semver (received ${journal.metadata.agentInfraVersion})` }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath: initialJournalPath });
427
+ }
411
428
  if (request.dryRun) {
412
429
  if (!journal.failure) return failed(request, { code: 'LIFECYCLE_JOURNAL_INVALID', message: 'existing lifecycle journal has no durable failure evidence for a read-only probe' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath: initialJournalPath });
413
430
  return failed(request, journal.failure, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath: initialJournalPath, completedSteps: journal.completedSteps, pendingSteps: STEPS.filter((step) => !journal.completedSteps.includes(step)) });
@@ -418,6 +435,9 @@ function applyTaskLifecycle(requestInput: TaskLifecycleRequest, options: TaskLif
418
435
  let metadata: TaskWriteMetadata;
419
436
  try { metadata = (options.metadataProvider ?? captureTaskWriteMetadata)(); }
420
437
  catch (error) { return failed(request, { code: 'LIFECYCLE_METADATA_FAILED', message: String(error) }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath }); }
438
+ if (spec.target === 'active' && !isValidAgentInfraVersion(metadata.agentInfraVersion)) {
439
+ return failed(request, { code: 'LIFECYCLE_DOCUMENT_INVALID', message: `metadata agentInfraVersion must be a valid v-prefixed semver (received ${metadata.agentInfraVersion})` }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
440
+ }
421
441
  journal = { version: 1, taskId, intent: request.intent, intentDigest: digest, sourceState, targetState: spec.target, metadata, completedSteps: [] };
422
442
  if (request.dryRun) {
423
443
  const plannedMutations = mutationsFor(request, content, metadata, restoredFiles, allowsManualOverride(options, 'LIFECYCLE_LOG_MISSING'));
@@ -3,9 +3,11 @@ import path from 'node:path';
3
3
  import { randomUUID } from 'node:crypto';
4
4
 
5
5
  import { parseTypedTaskFrontmatter } from './frontmatter.ts';
6
- import { parseReviewSummary } from './review-artifacts.ts';
6
+ import { validateCurrentTaskContract } from './current-contract.ts';
7
+ import { parseReviewSummary, resolveCanonicalVerdict } from './review-artifacts.ts';
7
8
  import { inspectArtifactDirectory } from './artifact-lifecycle.ts';
8
- import { parseLedger, summarizeLedgerStage, validateLedgerRows } from './ledger.ts';
9
+ import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.ts';
10
+ import type { LedgerDocument } from './ledger.ts';
9
11
  import { resolveTaskRef } from './resolve-ref.ts';
10
12
  import {
11
13
  activateDelegation,
@@ -119,6 +121,7 @@ type OrchestrationResult = Readonly<{
119
121
  taskId: string | null;
120
122
  run: OrchestrationRun | null;
121
123
  next: OrchestrationNext | null;
124
+ warnings?: readonly Readonly<{ code: string; message: string; action: string }>[];
122
125
  error: Readonly<{
123
126
  code: string;
124
127
  message: string;
@@ -181,7 +184,7 @@ function orchestrationPath(taskDir: string): string {
181
184
  return path.join(taskDir, 'orchestration.json');
182
185
  }
183
186
 
184
- const ORCHESTRATION_STATE_INVALID_MESSAGE = 'orchestration.json does not match the current runtime structure; finish or clear active runs before upgrading';
187
+ const ORCHESTRATION_STATE_INVALID_MESSAGE = 'orchestration.json does not match the current runtime structure; the file was left unchanged; rebuild the sandbox or manually repair the state before retrying';
185
188
 
186
189
  class OrchestrationStateError extends Error {
187
190
  readonly code = 'ORCHESTRATION_STATE_INVALID';
@@ -529,14 +532,20 @@ function hasReviewAfterArtifact(taskDir: string, family: string, reviewFamily: s
529
532
  && review.reviewedInput?.name === artifact.latest.name;
530
533
  }
531
534
 
532
- function latestReviewApproved(taskDir: string, family: 'review-analysis' | 'review-plan' | 'review-code'): boolean {
535
+ function latestReviewApproved(taskDir: string, family: 'review-analysis' | 'review-plan' | 'review-code'): { approved: boolean; error: string | null } {
533
536
  const round = highestRound(taskDir, family);
534
- if (round === 0) return false;
535
- const parsed = parseReviewSummary(fs.readFileSync(path.join(taskDir, artifactName(family, round)), 'utf8'));
536
- return parsed.ok && parsed.summary.verdict === 'Approved' && parsed.summary.counts !== null;
537
+ if (round === 0) return { approved: false, error: null };
538
+ let content: string;
539
+ try { content = fs.readFileSync(path.join(taskDir, artifactName(family, round)), 'utf8'); }
540
+ catch (error) { return { approved: false, error: String(error) }; }
541
+ const parsed = parseReviewSummary(content);
542
+ if (!parsed.ok) return { approved: false, error: `${parsed.code}: ${parsed.message}` };
543
+ const verdict = resolveCanonicalVerdict(parsed.summary);
544
+ if (!verdict.ok) return { approved: false, error: `${verdict.code}: ${verdict.message}` };
545
+ return { approved: verdict.verdict === 'Approved', error: null };
537
546
  }
538
547
 
539
- function routeFromFacts(taskDir: string): Omit<OrchestrationNext, 'requestedModel' | 'requestedReasoningEffort'> | null {
548
+ function routeFromFacts(taskDir: string): Omit<OrchestrationNext, 'requestedModel' | 'requestedReasoningEffort'> | { error: string } | null {
540
549
  const analysisRound = highestRound(taskDir, 'analysis');
541
550
  const analysisReviewRound = highestRound(taskDir, 'review-analysis');
542
551
  const planRound = highestRound(taskDir, 'plan');
@@ -551,7 +560,9 @@ function routeFromFacts(taskDir: string): Omit<OrchestrationNext, 'requestedMode
551
560
  const round = analysisReviewRound + 1;
552
561
  return { action: 'review-analysis', role: 'reviewer', stage: 'review-analysis', round, artifact: artifactName('review-analysis', round) };
553
562
  }
554
- if (!latestReviewApproved(taskDir, 'review-analysis')) {
563
+ const analysisVerdict = latestReviewApproved(taskDir, 'review-analysis');
564
+ if (analysisVerdict.error) return { error: analysisVerdict.error };
565
+ if (!analysisVerdict.approved) {
555
566
  const round = analysisRound + 1;
556
567
  return { action: 'analyze-task', role: 'executor', stage: 'analysis', round, artifact: artifactName('analysis', round) };
557
568
  }
@@ -563,7 +574,9 @@ function routeFromFacts(taskDir: string): Omit<OrchestrationNext, 'requestedMode
563
574
  const round = planReviewRound + 1;
564
575
  return { action: 'review-plan', role: 'reviewer', stage: 'review-plan', round, artifact: artifactName('review-plan', round) };
565
576
  }
566
- if (!latestReviewApproved(taskDir, 'review-plan')) {
577
+ const planVerdict = latestReviewApproved(taskDir, 'review-plan');
578
+ if (planVerdict.error) return { error: planVerdict.error };
579
+ if (!planVerdict.approved) {
567
580
  const round = planRound + 1;
568
581
  return { action: 'plan-task', role: 'executor', stage: 'plan', round, artifact: artifactName('plan', round) };
569
582
  }
@@ -575,7 +588,9 @@ function routeFromFacts(taskDir: string): Omit<OrchestrationNext, 'requestedMode
575
588
  const round = codeReviewRound + 1;
576
589
  return { action: 'review-code', role: 'reviewer', stage: 'review-code', round, artifact: artifactName('review-code', round) };
577
590
  }
578
- if (!latestReviewApproved(taskDir, 'review-code')) {
591
+ const codeVerdict = latestReviewApproved(taskDir, 'review-code');
592
+ if (codeVerdict.error) return { error: codeVerdict.error };
593
+ if (!codeVerdict.approved) {
579
594
  const round = codeRound + 1;
580
595
  return { action: 'code-task', role: 'executor', stage: 'code', round, artifact: artifactName('code', round) };
581
596
  }
@@ -598,12 +613,21 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
598
613
  return failed('ORCHESTRATION_TASK_READ_FAILED', String(error), resolved.taskId);
599
614
  }
600
615
  let metadata: ReturnType<typeof parseTypedTaskFrontmatter>;
601
- try {
602
- metadata = parseTypedTaskFrontmatter(content);
603
- } catch (error) {
604
- return failed('ORCHESTRATION_TASK_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
616
+ let currentLedger: LedgerDocument | null = null;
617
+ if (resolved.state === 'active') {
618
+ const contract = validateCurrentTaskContract(content);
619
+ if (!contract.ok) return failed('ORCHESTRATION_CURRENT_CONTRACT_INVALID', contract.message, resolved.taskId);
620
+ metadata = contract.metadata;
621
+ currentLedger = contract.ledger;
622
+ } else {
623
+ try {
624
+ metadata = parseTypedTaskFrontmatter(content);
625
+ } catch (error) {
626
+ return failed('ORCHESTRATION_TASK_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
627
+ }
605
628
  }
606
629
  const routed = routeFromFacts(resolved.taskDir);
630
+ if (routed && 'error' in routed) return failed('ORCHESTRATION_REVIEW_INVALID', routed.error, resolved.taskId);
607
631
  if (!routed) return failed('ORCHESTRATION_ROUTE_UNKNOWN', 'cannot determine a unique lifecycle action', resolved.taskId);
608
632
  const run = readRun(resolved.taskDir);
609
633
  const policy = run ? rolePolicy(run, routed.role) : null;
@@ -621,7 +645,9 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
621
645
  if (!review.ok || review.summary.manualValidation === null) {
622
646
  return failed('ORCHESTRATION_REVIEW_INVALID', 'latest code review has no numeric manual-validation count', resolved.taskId);
623
647
  }
624
- const rows = parseLedger(content);
648
+ const ledger = currentLedger ?? parseLedgerDocument(content);
649
+ if (!ledger.present) return failed('ORCHESTRATION_CURRENT_CONTRACT_INVALID', `${LEDGER_SECTION_MISSING_CODE}: ${LEDGER_SECTION_MISSING_MESSAGE}`, resolved.taskId);
650
+ const rows = ledger.rows;
625
651
  const ledgerError = validateLedgerRows(rows);
626
652
  if (ledgerError) return failed('ORCHESTRATION_LEDGER_INVALID', ledgerError.message, resolved.taskId);
627
653
  if (!summarizeLedgerStage(rows, 'code').canAdvance) {
@@ -18,6 +18,21 @@ type ReviewSummaryErrorCode =
18
18
  | 'REVIEW_SUMMARY_NOT_FOUND'
19
19
  | 'REVIEW_SUMMARY_PLACEHOLDER_INVALID'
20
20
  | 'REVIEW_SUMMARY_COUNT_MISMATCH';
21
+ type VerdictSemanticErrorCode =
22
+ | 'REVIEW_VERDICT_FINDING_MISMATCH'
23
+ | 'REVIEW_FINDING_COUNTS_NOT_FINALIZED';
24
+ type CanonicalVerdictResult =
25
+ | { ok: true; verdict: ReviewVerdict }
26
+ | { ok: false; verdict: null; code: VerdictSemanticErrorCode; message: string };
27
+ type PathVerdictErrorCode =
28
+ | 'REVIEW_ARTIFACT_NOT_FOUND'
29
+ | 'REVIEW_ARTIFACT_READ_FAILED'
30
+ | 'REVIEW_SUMMARY_NOT_FOUND'
31
+ | 'REVIEW_SUMMARY_PLACEHOLDER_INVALID'
32
+ | VerdictSemanticErrorCode;
33
+ type PathVerdictResult =
34
+ | { ok: true; verdict: ReviewVerdict }
35
+ | { ok: false; verdict: null; code: PathVerdictErrorCode; message: string };
21
36
  type ReviewSummaryParseResult =
22
37
  | { ok: true; summary: ReviewSummary; findingsStart: number; findingsEnd: number }
23
38
  | { ok: false; code: ReviewSummaryErrorCode; message: string };
@@ -203,28 +218,46 @@ function finalizeReviewSummaryContent(
203
218
  };
204
219
  }
205
220
 
206
- function parseVerdict(reviewPath: string):
207
- | { ok: true; verdict: 'Approved' | 'Approved-with-issues' | 'Changes Requested' | 'Rejected' }
208
- | { ok: false; verdict: null; message: string } {
221
+ function resolveCanonicalVerdict(summary: ReviewSummary): CanonicalVerdictResult {
222
+ if (summary.counts === null) {
223
+ return {
224
+ ok: false,
225
+ verdict: null,
226
+ code: 'REVIEW_FINDING_COUNTS_NOT_FINALIZED',
227
+ message: 'review summary finding counts are not finalized'
228
+ };
229
+ }
230
+ if (summary.verdict === 'Approved' && !equalCounts(summary.counts, { blocker: 0, major: 0, minor: 0 })) {
231
+ return {
232
+ ok: false,
233
+ verdict: null,
234
+ code: 'REVIEW_VERDICT_FINDING_MISMATCH',
235
+ message: 'Approved verdict requires zero finalized findings'
236
+ };
237
+ }
238
+ return { ok: true, verdict: summary.verdict };
239
+ }
240
+
241
+ function parseVerdict(reviewPath: string): PathVerdictResult {
209
242
  if (!fs.existsSync(reviewPath)) {
210
- return { ok: false, verdict: null, message: `Review artifact not found: ${path.basename(reviewPath)}` };
243
+ return { ok: false, verdict: null, code: 'REVIEW_ARTIFACT_NOT_FOUND', message: `Review artifact not found: ${path.basename(reviewPath)}` };
244
+ }
245
+ let content: string;
246
+ try {
247
+ content = fs.readFileSync(reviewPath, 'utf8');
248
+ } catch (error) {
249
+ return { ok: false, verdict: null, code: 'REVIEW_ARTIFACT_READ_FAILED', message: `Review artifact could not be read: ${String(error)}` };
211
250
  }
212
- const parsed = parseReviewSummary(fs.readFileSync(reviewPath, 'utf8'));
251
+ const parsed = parseReviewSummary(content);
213
252
  if (!parsed.ok) {
214
253
  return {
215
254
  ok: false,
216
255
  verdict: null,
256
+ code: parsed.code === 'REVIEW_SUMMARY_PLACEHOLDER_INVALID' ? parsed.code : 'REVIEW_SUMMARY_NOT_FOUND',
217
257
  message: `cannot parse review summary in ${path.basename(reviewPath)}: ${parsed.message}`
218
258
  };
219
259
  }
220
- const { verdict, counts } = parsed.summary;
221
- if (verdict !== 'Approved') return { ok: true, verdict };
222
- return {
223
- ok: true,
224
- verdict: counts && equalCounts(counts, { blocker: 0, major: 0, minor: 0 })
225
- ? 'Approved'
226
- : 'Approved-with-issues'
227
- };
260
+ return resolveCanonicalVerdict(parsed.summary);
228
261
  }
229
262
 
230
263
  export {
@@ -236,13 +269,18 @@ export {
236
269
  maxRound,
237
270
  normalizeVerdict,
238
271
  parseReviewSummary,
239
- parseVerdict
272
+ parseVerdict,
273
+ resolveCanonicalVerdict
240
274
  };
241
275
  export type {
276
+ CanonicalVerdictResult,
277
+ PathVerdictErrorCode,
278
+ PathVerdictResult,
242
279
  ReviewFindingCounts,
243
280
  ReviewSummary,
244
281
  ReviewSummaryErrorCode,
245
282
  ReviewSummaryFinalizeResult,
246
283
  ReviewSummaryParseResult,
247
- ReviewVerdict
284
+ ReviewVerdict,
285
+ VerdictSemanticErrorCode
248
286
  };