@fitlab-ai/agent-infra 0.9.10 → 0.9.12

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 (375) hide show
  1. package/README.md +19 -9
  2. package/README.zh-CN.md +19 -9
  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 +71 -10
  8. package/dist/lib/agent-clients/adapters/opencode-sandbox.js +0 -4
  9. package/dist/lib/agent-clients/adapters/traecli.js +7 -8
  10. package/dist/lib/agent-clients/config.js +14 -99
  11. package/dist/lib/agent-clients/reconcile.js +1 -11
  12. package/dist/lib/agent-clients/schema.js +5 -4
  13. package/dist/lib/agent-clients/tokens.js +5 -15
  14. package/dist/lib/decide.js +14 -30
  15. package/dist/lib/defaults.json +8 -0
  16. package/dist/lib/git/workflow.js +43 -1
  17. package/dist/lib/init.js +3 -2
  18. package/dist/lib/internal/git-workflow.js +8 -1
  19. package/dist/lib/internal/platform-pr.js +8 -2
  20. package/dist/lib/internal/release-workflow.js +0 -1
  21. package/dist/lib/internal/task-delivery.js +59 -0
  22. package/dist/lib/internal/task-finalization.js +19 -9
  23. package/dist/lib/internal/task-ledger.js +8 -2
  24. package/dist/lib/internal/task-lifecycle.js +17 -93
  25. package/dist/lib/internal/task-orchestration.js +30 -216
  26. package/dist/lib/internal/task-validate.js +2 -1
  27. package/dist/lib/merge.js +205 -103
  28. package/dist/lib/platform/github-client.js +7 -1
  29. package/dist/lib/platform/pr-checks.js +8 -5
  30. package/dist/lib/platform/pr-summary.js +12 -8
  31. package/dist/lib/platform/pull-requests.js +393 -91
  32. package/dist/lib/platform/verification-required.js +13 -2
  33. package/dist/lib/platform/verification-sync.js +9 -4
  34. package/dist/lib/pr-review/evidence-grading.js +12 -4
  35. package/dist/lib/process-data/normalize.js +2 -9
  36. package/dist/lib/run/host.js +46 -16
  37. package/dist/lib/sandbox/agent-client-reconciler.js +4 -12
  38. package/dist/lib/sandbox/commands/create.js +3 -3
  39. package/dist/lib/sandbox/commands/enter.js +18 -4
  40. package/dist/lib/sandbox/config.js +8 -12
  41. package/dist/lib/sandbox/control/executor.js +131 -39
  42. package/dist/lib/sandbox/control/lifecycle.js +55 -24
  43. package/dist/lib/sandbox/control/server.js +18 -16
  44. package/dist/lib/sandbox/mounts.js +3 -2
  45. package/dist/lib/sandbox/recovery.js +123 -22
  46. package/dist/lib/sandbox/workspace-identity.js +14 -0
  47. package/dist/lib/sandbox/workspace-view.js +8 -9
  48. package/dist/lib/self-update.js +278 -0
  49. package/dist/lib/server/process-state.js +2 -2
  50. package/dist/lib/server/protocol.js +5 -5
  51. package/dist/lib/task/activity-log.js +4 -1
  52. package/dist/lib/task/artifact-lifecycle.js +6 -4
  53. package/dist/lib/task/codex-orchestration.js +1 -1
  54. package/dist/lib/task/command-options.js +1 -3
  55. package/dist/lib/task/commands/cat.js +0 -5
  56. package/dist/lib/task/commands/decisions.js +76 -92
  57. package/dist/lib/task/commands/files.js +6 -6
  58. package/dist/lib/task/commands/grep.js +25 -61
  59. package/dist/lib/task/commands/issue-body.js +6 -6
  60. package/dist/lib/task/commands/log.js +25 -10
  61. package/dist/lib/task/commands/show.js +5 -6
  62. package/dist/lib/task/commands/status.js +8 -8
  63. package/dist/lib/task/commit-intent.js +100 -0
  64. package/dist/lib/task/commit-operation.js +181 -91
  65. package/dist/lib/task/commit-policy.js +1 -1
  66. package/dist/lib/task/control-authority.js +431 -0
  67. package/dist/lib/task/create.js +39 -8
  68. package/dist/lib/task/current-contract.js +35 -0
  69. package/dist/lib/task/decision-details.js +218 -0
  70. package/dist/lib/task/decision-intents.js +5 -2
  71. package/dist/lib/task/delivery-target.js +97 -0
  72. package/dist/lib/task/delivery.js +153 -0
  73. package/dist/lib/task/events.js +11 -5
  74. package/dist/lib/task/human-override.js +5 -2
  75. package/dist/lib/task/index.js +19 -20
  76. package/dist/lib/task/ledger-intents.js +8 -9
  77. package/dist/lib/task/ledger.js +13 -6
  78. package/dist/lib/task/lifecycle.js +122 -9
  79. package/dist/lib/task/orchestration.js +105 -75
  80. package/dist/lib/task/pr-delivery-fact.js +182 -0
  81. package/dist/lib/task/review-artifacts.js +31 -12
  82. package/dist/lib/task/review-finalization.js +13 -4
  83. package/dist/lib/task/review-fingerprint.js +8 -0
  84. package/dist/lib/task/verification-engine.js +86 -39
  85. package/dist/lib/task/verification-review.js +2 -2
  86. package/dist/lib/task/workspace-snapshot.js +5 -11
  87. package/dist/lib/task/write.js +11 -1
  88. package/dist/lib/update.js +13 -35
  89. package/dist/lib/version.js +5 -1
  90. package/dist/lifecycle-build-manifest.json +3 -3
  91. package/lib/agent-client.ts +0 -1
  92. package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +105 -10
  93. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +12 -1
  94. package/lib/agent-clients/adapters/opencode-sandbox.ts +0 -5
  95. package/lib/agent-clients/adapters/traecli.ts +7 -8
  96. package/lib/agent-clients/config.ts +14 -144
  97. package/lib/agent-clients/reconcile.ts +1 -12
  98. package/lib/agent-clients/schema.ts +5 -4
  99. package/lib/agent-clients/tokens.ts +5 -18
  100. package/lib/decide.ts +12 -22
  101. package/lib/defaults.json +8 -0
  102. package/lib/git/workflow.ts +38 -1
  103. package/lib/init.ts +5 -2
  104. package/lib/internal/git-workflow.ts +7 -1
  105. package/lib/internal/platform-pr.ts +8 -1
  106. package/lib/internal/release-workflow.ts +0 -1
  107. package/lib/internal/task-delivery.ts +53 -0
  108. package/lib/internal/task-finalization.ts +27 -13
  109. package/lib/internal/task-ledger.ts +8 -2
  110. package/lib/internal/task-lifecycle.ts +33 -76
  111. package/lib/internal/task-orchestration.ts +33 -190
  112. package/lib/internal/task-validate.ts +3 -2
  113. package/lib/merge.ts +236 -120
  114. package/lib/platform/github-client.ts +8 -1
  115. package/lib/platform/pr-checks.ts +7 -5
  116. package/lib/platform/pr-summary.ts +10 -7
  117. package/lib/platform/pull-requests.ts +425 -87
  118. package/lib/platform/verification-required.ts +12 -2
  119. package/lib/platform/verification-sync.ts +9 -4
  120. package/lib/pr-review/evidence-grading.ts +7 -4
  121. package/lib/process-data/normalize.ts +2 -8
  122. package/lib/run/host.ts +63 -14
  123. package/lib/sandbox/agent-client-reconciler.ts +5 -15
  124. package/lib/sandbox/commands/create.ts +4 -4
  125. package/lib/sandbox/commands/enter.ts +24 -4
  126. package/lib/sandbox/config.ts +9 -14
  127. package/lib/sandbox/control/container-identity.ts +2 -2
  128. package/lib/sandbox/control/executor.ts +148 -51
  129. package/lib/sandbox/control/lifecycle.ts +59 -31
  130. package/lib/sandbox/control/protocol.ts +1 -3
  131. package/lib/sandbox/control/server.ts +15 -12
  132. package/lib/sandbox/control/state.ts +7 -7
  133. package/lib/sandbox/mounts.ts +4 -3
  134. package/lib/sandbox/recovery.ts +162 -34
  135. package/lib/sandbox/tools.ts +1 -1
  136. package/lib/sandbox/workspace-identity.ts +28 -0
  137. package/lib/sandbox/workspace-view.ts +14 -10
  138. package/lib/self-update.ts +341 -0
  139. package/lib/server/process-state.ts +2 -2
  140. package/lib/server/protocol.ts +5 -5
  141. package/lib/task/activity-log.ts +6 -1
  142. package/lib/task/artifact-lifecycle.ts +7 -5
  143. package/lib/task/codex-orchestration.ts +2 -1
  144. package/lib/task/command-options.ts +1 -2
  145. package/lib/task/commands/cat.ts +0 -4
  146. package/lib/task/commands/decisions.ts +80 -75
  147. package/lib/task/commands/files.ts +6 -6
  148. package/lib/task/commands/grep.ts +23 -51
  149. package/lib/task/commands/issue-body.ts +6 -6
  150. package/lib/task/commands/log.ts +22 -10
  151. package/lib/task/commands/show.ts +5 -6
  152. package/lib/task/commands/status.ts +8 -8
  153. package/lib/task/commit-intent.ts +137 -0
  154. package/lib/task/commit-operation.ts +161 -79
  155. package/lib/task/commit-policy.ts +1 -2
  156. package/lib/task/control-authority.ts +563 -0
  157. package/lib/task/create.ts +40 -10
  158. package/lib/task/current-contract.ts +49 -0
  159. package/lib/task/decision-details.ts +280 -0
  160. package/lib/task/decision-intents.ts +7 -2
  161. package/lib/task/delivery-target.ts +130 -0
  162. package/lib/task/delivery.ts +160 -0
  163. package/lib/task/events.ts +10 -5
  164. package/lib/task/human-override.ts +5 -2
  165. package/lib/task/index.ts +19 -20
  166. package/lib/task/ledger-intents.ts +6 -8
  167. package/lib/task/ledger.ts +17 -7
  168. package/lib/task/lifecycle.ts +113 -4
  169. package/lib/task/orchestration.ts +102 -80
  170. package/lib/task/pr-delivery-fact.ts +254 -0
  171. package/lib/task/review-artifacts.ts +53 -15
  172. package/lib/task/review-finalization.ts +21 -5
  173. package/lib/task/review-fingerprint.ts +10 -0
  174. package/lib/task/verification-engine.ts +78 -46
  175. package/lib/task/verification-review.ts +3 -1
  176. package/lib/task/workspace-snapshot.ts +4 -13
  177. package/lib/task/write.ts +20 -1
  178. package/lib/update.ts +14 -38
  179. package/lib/version.ts +6 -1
  180. package/package.json +1 -1
  181. package/templates/.agents/README.en.md +5 -3
  182. package/templates/.agents/README.zh-CN.md +5 -3
  183. package/templates/.agents/rules/README.en.md +4 -2
  184. package/templates/.agents/rules/README.zh-CN.md +4 -2
  185. package/templates/.agents/rules/compatibility-policy.en.md +5 -4
  186. package/templates/.agents/rules/compatibility-policy.zh-CN.md +5 -4
  187. package/templates/.agents/rules/create-issue.en.md +11 -2
  188. package/templates/.agents/rules/create-issue.zh-CN.md +24 -2
  189. package/templates/.agents/rules/human-decision-context.en.md +9 -7
  190. package/templates/.agents/rules/human-decision-context.zh-CN.md +9 -7
  191. package/templates/.agents/rules/{issue-fields.github.en.md → issue-fields.en.md} +1 -1
  192. package/templates/.agents/rules/{issue-fields.github.zh-CN.md → issue-fields.zh-CN.md} +1 -1
  193. package/templates/.agents/rules/issue-pr-commands.en.md +5 -18
  194. package/templates/.agents/rules/issue-pr-commands.zh-CN.md +1 -1
  195. package/templates/.agents/rules/issue-sync.en.md +26 -5
  196. package/templates/.agents/rules/issue-sync.zh-CN.md +62 -5
  197. package/templates/.agents/rules/label-milestone-setup.en.md +15 -2
  198. package/templates/.agents/rules/label-milestone-setup.zh-CN.md +15 -2
  199. package/templates/.agents/rules/local-artifact-repair.en.md +46 -0
  200. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +46 -0
  201. package/templates/.agents/rules/milestone-inference.en.md +12 -2
  202. package/templates/.agents/rules/milestone-inference.zh-CN.md +25 -3
  203. package/templates/.agents/rules/next-step-output.en.md +3 -3
  204. package/templates/.agents/rules/next-step-output.zh-CN.md +3 -3
  205. package/templates/.agents/rules/no-mid-flow-questions.en.md +1 -1
  206. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +1 -1
  207. package/templates/.agents/rules/pr-sync.en.md +23 -14
  208. package/templates/.agents/rules/pr-sync.zh-CN.md +23 -2
  209. package/templates/.agents/rules/release-commands.en.md +42 -3
  210. package/templates/.agents/rules/release-commands.zh-CN.md +42 -3
  211. package/templates/.agents/rules/review-handshake.en.md +1 -1
  212. package/templates/.agents/rules/review-handshake.zh-CN.md +1 -1
  213. package/templates/.agents/rules/security-alerts.en.md +17 -2
  214. package/templates/.agents/rules/security-alerts.zh-CN.md +17 -2
  215. package/templates/.agents/rules/sync-content-generation.en.md +8 -0
  216. package/templates/.agents/rules/sync-content-generation.zh-CN.md +8 -0
  217. package/templates/.agents/rules/task-management.en.md +2 -3
  218. package/templates/.agents/rules/task-management.zh-CN.md +2 -3
  219. package/templates/.agents/scripts/security-alerts.github.sh +151 -0
  220. package/templates/.agents/scripts/security-alerts.sh +31 -0
  221. package/templates/.agents/skills/analyze-task/SKILL.en.md +4 -3
  222. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +4 -3
  223. package/templates/.agents/skills/block-task/SKILL.en.md +2 -2
  224. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
  225. package/templates/.agents/skills/cancel-task/SKILL.en.md +2 -2
  226. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +2 -2
  227. package/templates/.agents/skills/check-task/SKILL.en.md +2 -2
  228. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +2 -2
  229. package/templates/.agents/skills/close-codescan/SKILL.en.md +3 -3
  230. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +3 -3
  231. package/templates/.agents/skills/close-dependabot/SKILL.en.md +3 -3
  232. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +3 -3
  233. package/templates/.agents/skills/code-task/SKILL.en.md +8 -5
  234. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +9 -6
  235. package/templates/.agents/skills/commit/SKILL.en.md +30 -10
  236. package/templates/.agents/skills/commit/SKILL.zh-CN.md +6 -7
  237. package/templates/.agents/skills/commit/config/verify.json +1 -1
  238. package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +3 -3
  239. package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +3 -3
  240. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
  241. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
  242. package/templates/.agents/skills/commit/reference/task-status-update.en.md +2 -2
  243. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +2 -2
  244. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +7 -6
  245. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +7 -6
  246. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
  247. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
  248. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +1 -1
  249. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +1 -1
  250. package/templates/.agents/skills/complete-task/SKILL.en.md +13 -13
  251. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +13 -13
  252. package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +2 -2
  253. package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +2 -2
  254. package/templates/.agents/skills/create-pr/SKILL.en.md +9 -11
  255. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +9 -11
  256. package/templates/.agents/skills/create-pr/reference/change-report.en.md +9 -4
  257. package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +9 -4
  258. package/templates/.agents/skills/create-pr/scripts/change-report.mjs +203 -0
  259. package/templates/.agents/skills/create-release-note/SKILL.en.md +5 -6
  260. package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +5 -6
  261. package/templates/.agents/skills/create-task/SKILL.en.md +2 -1
  262. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +2 -1
  263. package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
  264. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
  265. package/templates/.agents/skills/import-dependabot/SKILL.en.md +1 -1
  266. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
  267. package/templates/.agents/skills/import-issue/SKILL.en.md +1 -1
  268. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +1 -1
  269. package/templates/.agents/skills/init-labels/SKILL.en.md +9 -10
  270. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +9 -10
  271. package/templates/.agents/skills/init-labels/scripts/init-labels.github.sh +57 -1
  272. package/templates/.agents/skills/init-labels/scripts/init-labels.sh +27 -1
  273. package/templates/.agents/skills/init-milestones/SKILL.en.md +3 -5
  274. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +3 -5
  275. package/templates/.agents/skills/plan-task/SKILL.en.md +4 -3
  276. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -3
  277. package/templates/.agents/skills/restore-task/SKILL.en.md +1 -1
  278. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +1 -1
  279. package/templates/.agents/skills/review-analysis/SKILL.en.md +8 -5
  280. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +8 -5
  281. package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -0
  282. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -0
  283. package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +18 -0
  284. package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +18 -0
  285. package/templates/.agents/skills/review-code/SKILL.en.md +11 -8
  286. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +11 -8
  287. package/templates/.agents/skills/review-code/config/verify.en.json +1 -0
  288. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -0
  289. package/templates/.agents/skills/review-code/reference/migration-risks.en.md +2 -1
  290. package/templates/.agents/skills/review-code/reference/migration-risks.zh-CN.md +2 -1
  291. package/templates/.agents/skills/review-code/reference/output-templates.en.md +19 -1
  292. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +19 -1
  293. package/templates/.agents/skills/review-code/reference/report-template.en.md +4 -2
  294. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +4 -2
  295. package/templates/.agents/skills/review-plan/SKILL.en.md +8 -5
  296. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +8 -5
  297. package/templates/.agents/skills/review-plan/config/verify.en.json +1 -0
  298. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -0
  299. package/templates/.agents/skills/review-plan/reference/output-templates.en.md +18 -0
  300. package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +18 -0
  301. package/templates/.agents/skills/review-pr/SKILL.en.md +1 -0
  302. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +1 -0
  303. package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +2 -2
  304. package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +2 -2
  305. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +5 -0
  306. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +5 -0
  307. package/templates/.agents/skills/run-task/SKILL.en.md +5 -1
  308. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +5 -1
  309. package/templates/.agents/skills/update-agent-infra/SKILL.en.md +2 -2
  310. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +2 -2
  311. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +144 -109
  312. package/templates/.agents/skills/watch-pr/SKILL.en.md +5 -5
  313. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +5 -5
  314. package/templates/.agents/templates/task.en.md +6 -2
  315. package/templates/.agents/templates/task.zh-CN.md +6 -2
  316. package/templates/.agents/workflows/bug-fix.en.yaml +9 -8
  317. package/templates/.agents/workflows/bug-fix.zh-CN.yaml +9 -8
  318. package/templates/.agents/workflows/feature-development.en.yaml +9 -8
  319. package/templates/.agents/workflows/feature-development.zh-CN.yaml +9 -8
  320. package/templates/.agents/workflows/refactoring.en.yaml +9 -8
  321. package/templates/.agents/workflows/refactoring.zh-CN.yaml +9 -8
  322. package/templates/.claude/commands/analyze-task.en.md +1 -1
  323. package/templates/.claude/commands/analyze-task.zh-CN.md +1 -1
  324. package/templates/.claude/commands/block-task.en.md +1 -1
  325. package/templates/.claude/commands/block-task.zh-CN.md +1 -1
  326. package/templates/.claude/commands/cancel-task.en.md +1 -1
  327. package/templates/.claude/commands/cancel-task.zh-CN.md +1 -1
  328. package/templates/.claude/commands/check-task.en.md +1 -1
  329. package/templates/.claude/commands/check-task.zh-CN.md +1 -1
  330. package/templates/.claude/commands/code-task.en.md +1 -1
  331. package/templates/.claude/commands/code-task.zh-CN.md +1 -1
  332. package/templates/.claude/commands/commit.en.md +1 -1
  333. package/templates/.claude/commands/commit.zh-CN.md +1 -1
  334. package/templates/.claude/commands/complete-manual-validation.en.md +1 -1
  335. package/templates/.claude/commands/complete-manual-validation.zh-CN.md +1 -1
  336. package/templates/.claude/commands/complete-task.en.md +1 -1
  337. package/templates/.claude/commands/complete-task.zh-CN.md +1 -1
  338. package/templates/.claude/commands/create-pr.en.md +1 -1
  339. package/templates/.claude/commands/create-pr.zh-CN.md +1 -1
  340. package/templates/.claude/commands/plan-task.en.md +1 -1
  341. package/templates/.claude/commands/plan-task.zh-CN.md +1 -1
  342. package/templates/.claude/commands/review-analysis.en.md +1 -1
  343. package/templates/.claude/commands/review-analysis.zh-CN.md +1 -1
  344. package/templates/.claude/commands/review-code.en.md +1 -1
  345. package/templates/.claude/commands/review-code.zh-CN.md +1 -1
  346. package/templates/.claude/commands/review-plan.en.md +1 -1
  347. package/templates/.claude/commands/review-plan.zh-CN.md +1 -1
  348. package/templates/.claude/commands/run-manual-validation.en.md +1 -1
  349. package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
  350. package/templates/.claude/commands/run-task.en.md +1 -1
  351. package/templates/.claude/commands/run-task.zh-CN.md +1 -1
  352. package/templates/.claude/commands/watch-pr.en.md +1 -1
  353. package/templates/.claude/commands/watch-pr.zh-CN.md +1 -1
  354. package/templates/{.trae/skills → .traecli/commands}/update-agent-infra.en.md +0 -1
  355. package/templates/{.trae/skills → .traecli/commands}/update-agent-infra.zh-CN.md +0 -1
  356. package/dist/lib/builtin-tuis.js +0 -40
  357. package/lib/builtin-tuis.ts +0 -56
  358. package/templates/.agents/rules/create-issue.github.en.md +0 -14
  359. package/templates/.agents/rules/create-issue.github.zh-CN.md +0 -27
  360. package/templates/.agents/rules/issue-pr-commands.github.en.md +0 -34
  361. package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +0 -49
  362. package/templates/.agents/rules/issue-sync.github.en.md +0 -40
  363. package/templates/.agents/rules/issue-sync.github.zh-CN.md +0 -76
  364. package/templates/.agents/rules/label-milestone-setup.github.en.md +0 -60
  365. package/templates/.agents/rules/label-milestone-setup.github.zh-CN.md +0 -60
  366. package/templates/.agents/rules/milestone-inference.github.en.md +0 -15
  367. package/templates/.agents/rules/milestone-inference.github.zh-CN.md +0 -27
  368. package/templates/.agents/rules/pr-checks-commands.github.en.md +0 -18
  369. package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +0 -34
  370. package/templates/.agents/rules/pr-sync.github.en.md +0 -43
  371. package/templates/.agents/rules/pr-sync.github.zh-CN.md +0 -69
  372. package/templates/.agents/rules/release-commands.github.en.md +0 -38
  373. package/templates/.agents/rules/release-commands.github.zh-CN.md +0 -38
  374. package/templates/.agents/rules/security-alerts.github.en.md +0 -43
  375. package/templates/.agents/rules/security-alerts.github.zh-CN.md +0 -43
@@ -0,0 +1,160 @@
1
+ import fs from 'node:fs';
2
+ import { spawnSync } from 'node:child_process';
3
+ import path from 'node:path';
4
+
5
+ import { parseTaskFrontmatter } from './frontmatter.ts';
6
+ import { resolveTaskRef } from './resolve-ref.ts';
7
+ import { resolveDeliveryTarget, resolveDiffBase, resolveTargetHead, validateBaseRef, validateRemote } from './delivery-target.ts';
8
+ import { extractReviewBaseline, extractReviewDiffBase, extractReviewTargetHead, extractReviewedHead, findAuthoritativeReviewCodeArtifact } from './review-fingerprint.ts';
9
+ import { captureTaskWriteMetadata, writeTask } from './write.ts';
10
+ import { withRepositoryMutationLock, withTaskExecutionLock } from './task-execution-lock.ts';
11
+
12
+ export type DeliveryState = 'absent' | 'same' | 'known-old' | 'unknown-drift';
13
+
14
+ export type DeliveryClassification = Readonly<{
15
+ state: DeliveryState;
16
+ shouldPush: boolean;
17
+ }>;
18
+
19
+ export type TaskBranchDeliveryResult = Readonly<{
20
+ status: 'applied' | 'no-op' | 'failed' | 'blocked' | 'planned';
21
+ changed: boolean;
22
+ taskId: string | null;
23
+ remoteHead: string | null;
24
+ localHead: string | null;
25
+ state: DeliveryState | null;
26
+ error: { code: string; message: string; retryable: boolean } | null;
27
+ }>;
28
+
29
+ function classifyDeliveryState(localHead: string, remoteHead: string | null, lastDelivered: string | null): DeliveryClassification {
30
+ if (remoteHead === null) return { state: 'absent', shouldPush: true };
31
+ if (remoteHead === localHead) return { state: 'same', shouldPush: false };
32
+ if (lastDelivered && remoteHead === lastDelivered) return { state: 'known-old', shouldPush: true };
33
+ return { state: 'unknown-drift', shouldPush: false };
34
+ }
35
+
36
+ function git(cwd: string, args: readonly string[]): { status: number | null; stdout: string; stderr: string } {
37
+ const result = spawnSync('git', [...args], { cwd, encoding: 'utf8' });
38
+ return { status: result.status, stdout: result.stdout ?? '', stderr: result.stderr ?? '' };
39
+ }
40
+
41
+ function gitValue(cwd: string, args: readonly string[]): string | null {
42
+ const result = git(cwd, args);
43
+ return result.status === 0 ? result.stdout.trim() : null;
44
+ }
45
+
46
+ function validateBranch(cwd: string, branch: string): boolean {
47
+ return git(cwd, ['check-ref-format', `refs/heads/${branch}`]).status === 0;
48
+ }
49
+
50
+ function validateReviewTargetCompatibility(
51
+ repoRoot: string,
52
+ taskDir: string,
53
+ target: { remote: string; baseRef: string },
54
+ reviewedHead: string
55
+ ): { ok: true } | { ok: false; code: string; message: string } {
56
+ const artifact = findAuthoritativeReviewCodeArtifact(taskDir);
57
+ if (!artifact.ok || !artifact.path) return { ok: false, code: 'DELIVERY_REVIEW_REQUIRED', message: 'A current review-code artifact is required before delivery' };
58
+ let content: string;
59
+ try { content = fs.readFileSync(artifact.path, 'utf8'); }
60
+ catch (error) { return { ok: false, code: 'DELIVERY_REVIEW_REQUIRED', message: error instanceof Error ? error.message : String(error) }; }
61
+ const savedReviewedHead = extractReviewedHead(content) || extractReviewBaseline(content);
62
+ const savedTargetHead = extractReviewTargetHead(content);
63
+ const savedDiffBase = extractReviewDiffBase(content);
64
+ if (!savedReviewedHead || !savedTargetHead || !savedDiffBase) {
65
+ return { ok: false, code: 'DELIVERY_REVIEW_TARGET_INVALID', message: 'review-code must save reviewed head, target head, and diff base before delivery' };
66
+ }
67
+ if (savedReviewedHead !== reviewedHead) {
68
+ return { ok: false, code: 'DELIVERY_REVIEW_REQUIRED', message: 'review-code reviewed head does not match last_reviewed_commit' };
69
+ }
70
+ const saved = resolveDiffBase(repoRoot, reviewedHead, savedTargetHead);
71
+ if (!saved.ok || saved.diffBase !== savedDiffBase) {
72
+ return { ok: false, code: 'DELIVERY_REVIEW_TARGET_INVALID', message: saved.ok ? 'saved review diff base does not match saved target head' : saved.message };
73
+ }
74
+ const currentTarget = resolveTargetHead(repoRoot, target);
75
+ if (!currentTarget.ok) return { ok: false, code: currentTarget.code, message: currentTarget.message };
76
+ const current = resolveDiffBase(repoRoot, reviewedHead, currentTarget.head!);
77
+ if (!current.ok) return { ok: false, code: current.code, message: current.message };
78
+ if (current.diffBase !== savedDiffBase) {
79
+ return { ok: false, code: 'DELIVERY_REVIEW_TARGET_CHANGED', message: 'current delivery target changed the reviewed diff base; re-run review-code' };
80
+ }
81
+ return { ok: true };
82
+ }
83
+
84
+ function failure(taskId: string | null, code: string, message: string, retryable = false, status: 'failed' | 'blocked' = retryable ? 'blocked' : 'failed'): TaskBranchDeliveryResult {
85
+ return { status, changed: false, taskId, remoteHead: null, localHead: null, state: null, error: { code, message, retryable } };
86
+ }
87
+
88
+ function deliverUnlocked(
89
+ taskId: string,
90
+ taskDir: string,
91
+ repoRoot: string,
92
+ options: { agent: string; remote?: string; baseRef?: string; dryRun?: boolean }
93
+ ): TaskBranchDeliveryResult {
94
+ let frontmatter: ReturnType<typeof parseTaskFrontmatter>;
95
+ try { frontmatter = parseTaskFrontmatter(fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8')); }
96
+ catch (error) { return failure(taskId, 'TASK_CONTEXT_UNREADABLE', error instanceof Error ? error.message : String(error)); }
97
+ if (frontmatter.status !== 'active') return failure(taskId, 'TASK_STATE_INVALID', 'task branch delivery requires an active task');
98
+ const branch = frontmatter.branch ?? '';
99
+ if (!branch) return failure(taskId, 'DELIVERY_BRANCH_MISSING', 'task branch is missing');
100
+ if (!validateBranch(repoRoot, branch)) return failure(taskId, 'DELIVERY_BRANCH_INVALID', 'task branch is invalid');
101
+ if (!frontmatter.delivery_remote || !frontmatter.delivery_base_ref) {
102
+ return failure(taskId, 'DELIVERY_TARGET_MISSING', 'task delivery target is not bound; recreate or repair task metadata before delivery');
103
+ }
104
+ const target = resolveDeliveryTarget(repoRoot, {
105
+ remote: frontmatter.delivery_remote,
106
+ baseRef: frontmatter.delivery_base_ref
107
+ }, {
108
+ ...(options.remote === undefined ? {} : { remote: options.remote }),
109
+ ...(options.baseRef === undefined ? {} : { baseRef: options.baseRef })
110
+ });
111
+ if (!target.ok) return failure(taskId, target.code, target.message);
112
+ if (!validateRemote(target.value.remote) || !validateBaseRef(target.value.baseRef)) return failure(taskId, 'DELIVERY_TARGET_INVALID', 'task delivery target is invalid');
113
+ const localHead = gitValue(repoRoot, ['rev-parse', 'HEAD']);
114
+ if (!localHead) return failure(taskId, 'DELIVERY_LOCAL_HEAD_UNAVAILABLE', 'Unable to resolve local HEAD');
115
+ const status = git(repoRoot, ['status', '--porcelain=v1']);
116
+ if (status.status !== 0) return failure(taskId, 'DELIVERY_GIT_INSPECT_FAILED', status.stderr.trim() || 'Unable to inspect Git state');
117
+ if (status.stdout.trim()) return failure(taskId, 'DELIVERY_WORKTREE_DIRTY', 'Working tree must be clean before branch delivery');
118
+ const reviewed = frontmatter.last_reviewed_commit ?? '';
119
+ if (reviewed !== localHead) return failure(taskId, 'DELIVERY_REVIEW_REQUIRED', 'Local HEAD must equal last_reviewed_commit before delivery');
120
+ const reviewTarget = validateReviewTargetCompatibility(repoRoot, taskDir, target.value, reviewed);
121
+ if (!reviewTarget.ok) return failure(taskId, reviewTarget.code, reviewTarget.message, reviewTarget.code.includes('UNAVAILABLE'), reviewTarget.code.includes('UNAVAILABLE') ? 'blocked' : 'failed');
122
+ const ref = `refs/heads/${branch}`;
123
+ const remoteOutput = git(repoRoot, ['ls-remote', '--refs', target.value.remote, ref]);
124
+ if (remoteOutput.status !== 0) return failure(taskId, 'DELIVERY_REMOTE_UNAVAILABLE', remoteOutput.stderr.trim() || 'Unable to inspect the delivery remote', true);
125
+ const remoteHead = remoteOutput.stdout.trim().split(/\s+/)[0] || null;
126
+ const classification = classifyDeliveryState(localHead, remoteHead, frontmatter.delivery_remote_head ?? null);
127
+ if (classification.state === 'unknown-drift') return failure(taskId, 'DELIVERY_REMOTE_DRIFT', `Remote branch ${branch} has an unexpected head; refusing to overwrite it`);
128
+ if (options.dryRun) return { status: 'planned', changed: classification.shouldPush, taskId, remoteHead, localHead, state: classification.state, error: null };
129
+ if (classification.shouldPush) {
130
+ const args = classification.state === 'known-old'
131
+ ? ['push', `--force-with-lease=${ref}:${frontmatter.delivery_remote_head}`, target.value.remote, `HEAD:${ref}`]
132
+ : ['push', `--force-with-lease=${ref}:`, target.value.remote, `HEAD:${ref}`];
133
+ const pushed = git(repoRoot, args);
134
+ if (pushed.status !== 0) return failure(taskId, 'DELIVERY_PUSH_FAILED', pushed.stderr.trim() || 'Task branch delivery was rejected', true, 'blocked');
135
+ }
136
+ const verified = gitValue(repoRoot, ['ls-remote', '--refs', target.value.remote, ref])?.split(/\s+/)[0] ?? null;
137
+ if (verified !== localHead) return failure(taskId, 'DELIVERY_REMOTE_VERIFY_FAILED', `Expected remote head ${localHead}, received ${verified ?? 'unavailable'}`, true, 'blocked');
138
+ const written = writeTask({
139
+ taskRef: taskId,
140
+ expectedState: 'active',
141
+ mutations: [{ kind: 'frontmatter', set: { delivery_remote: target.value.remote, delivery_base_ref: target.value.baseRef, delivery_remote_head: localHead, assigned_to: options.agent } }]
142
+ }, { repoRoot, metadataProvider: captureTaskWriteMetadata });
143
+ if (written.status === 'failed') return failure(taskId, 'DELIVERY_TASK_SYNC_FAILED', written.error.message, true, 'blocked');
144
+ return { status: classification.shouldPush ? 'applied' : 'no-op', changed: classification.shouldPush || written.changed, taskId, remoteHead: localHead, localHead, state: classification.state, error: null };
145
+ }
146
+
147
+ function deliverTaskBranch(taskRef: string, options: { repoRoot?: string; agent: string; remote?: string; baseRef?: string; dryRun?: boolean }): TaskBranchDeliveryResult {
148
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
149
+ if (!resolved.ok) return failure(resolved.taskId, resolved.code, resolved.message);
150
+ try {
151
+ return withRepositoryMutationLock(resolved.repoRoot, () => withTaskExecutionLock(
152
+ resolved.repoRoot, resolved.taskId, 'task-delivery',
153
+ () => deliverUnlocked(resolved.taskId, resolved.taskDir, resolved.repoRoot, options)
154
+ ));
155
+ } catch (error) {
156
+ return failure(resolved.taskId, 'DELIVERY_LOCK_FAILED', error instanceof Error ? error.message : String(error), true, 'blocked');
157
+ }
158
+ }
159
+
160
+ export { classifyDeliveryState, deliverTaskBranch };
@@ -1,6 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
 
3
- import { appendActivityEntry, locateActivityLog, pairEntries } from './activity-log.ts';
3
+ import { appendActivityEntry, locateActivityLog, pairEntries, startedBackedRows } from './activity-log.ts';
4
4
  import {
5
5
  buildArtifactLinkSection,
6
6
  artifactName,
@@ -21,9 +21,9 @@ import {
21
21
  parseImplementationInputs,
22
22
  renderImplementationInputs
23
23
  } from './implementation-inputs.ts';
24
- import { parseLedger, summarizeLedgerStage, validateLedgerRows } from './ledger.ts';
24
+ import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.ts';
25
25
  import type { ReviewStage } from './ledger.ts';
26
- import { parseReviewSummary } from './review-artifacts.ts';
26
+ import { parseReviewSummary, resolveCanonicalVerdict } from './review-artifacts.ts';
27
27
  import { resolveTaskRef } from './resolve-ref.ts';
28
28
  import { findSectionHeading } from './sections.ts';
29
29
  import { validateLifecycleExecution } from './lifecycle-execution.ts';
@@ -50,6 +50,7 @@ type TaskEventErrorCode =
50
50
  | 'EVENT_UNKNOWN' | 'EVENT_PAYLOAD_INVALID' | 'EVENT_TRANSITION_INVALID'
51
51
  | 'EVENT_LOG_MISSING' | 'EVENT_START_MISSING' | 'EVENT_ALREADY_COMPLETED'
52
52
  | 'EVENT_LOG_CONFLICT' | 'EVENT_ARTIFACT_CONFLICT' | 'EVENT_FINDING_COUNT_MISMATCH'
53
+ | 'EVENT_VERDICT_INVALID'
53
54
  | 'EVENT_ORCHESTRATION_COMMIT_FAILED'
54
55
  | ArtifactErrorCode | TaskWriteErrorCode;
55
56
  type TaskEventRequest = {
@@ -151,7 +152,9 @@ function validateReviewFindingCounts(
151
152
  ): TaskEventError | null {
152
153
  const stage = REVIEW_LEDGER_STAGES[family];
153
154
  if (!stage || !artifactPath) return null;
154
- const rows = parseLedger(content);
155
+ const ledger = parseLedgerDocument(content);
156
+ if (!ledger.present) return { code: 'EVENT_FINDING_COUNT_MISMATCH', message: `${LEDGER_SECTION_MISSING_CODE}: ${LEDGER_SECTION_MISSING_MESSAGE}` };
157
+ const rows = ledger.rows;
155
158
  const invalid = validateLedgerRows(rows);
156
159
  if (invalid) return { code: 'TASK_DOCUMENT_INVALID', message: `${invalid.code}: ${invalid.message}` };
157
160
  const expected = summarizeLedgerStage(rows, stage).unresolvedFindingCounts;
@@ -173,6 +176,8 @@ function validateReviewFindingCounts(
173
176
  message: parsed.ok ? 'review summary finding counts are not finalized' : parsed.message
174
177
  };
175
178
  }
179
+ const canonical = resolveCanonicalVerdict(parsed.summary);
180
+ if (!canonical.ok) return { code: 'EVENT_VERDICT_INVALID', message: `${canonical.code}: ${canonical.message}` };
176
181
  const report = parsed.summary.counts;
177
182
  const reportVerdict = parsed.summary.verdict === 'Approved'
178
183
  ? 'approved'
@@ -370,7 +375,7 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
370
375
  catch (error) { return failed(request, { code: 'TASK_DOCUMENT_INVALID', message: error instanceof Error ? error.message : String(error) }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath }); }
371
376
  const section = locateActivityLog(content);
372
377
  if (!section) return failed(request, { code: 'EVENT_LOG_MISSING', message: 'task has no unique Activity Log section' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
373
- const rows = pairEntries(section.entries);
378
+ const rows = startedBackedRows(pairEntries(section.entries));
374
379
  let normalized = request;
375
380
  let artifactContext: ArtifactContextResult | null = null;
376
381
  const initialParts = eventParts(request.event);
@@ -24,6 +24,7 @@ import { verifyTaskEvent } from './verification.ts';
24
24
  import { verifyInProcess } from './verification-engine.ts';
25
25
  import { canonicalTimestamp, writeTask } from './write.ts';
26
26
  import type { TaskFileSystem, TaskWriteMetadata, TaskWriteResult } from './write.ts';
27
+ import { readPrDeliveryFact } from './pr-delivery-fact.ts';
27
28
 
28
29
  type Eligibility = 'eligible' | 'repair-only' | 'runtime-recovery-only' | 'never-overridable';
29
30
  type OutcomeEffect = 'apply-target' | 'retry-same-intent' | 'record-only' | 'no-write';
@@ -614,7 +615,8 @@ function resourceForSnapshot(
614
615
  return number === null ? null : { kind: 'issue', number };
615
616
  }
616
617
  if (policy.producerId === 'platform.pull-request') {
617
- const number = options.probePullRequestNumber ?? toPositiveInteger(frontmatter?.pr_number);
618
+ const fact = frontmatter ? readPrDeliveryFact(frontmatter) : null;
619
+ const number = options.probePullRequestNumber ?? (fact?.status === 'valid' && fact.fact.state === 'bound' ? fact.fact.identity.number : null);
618
620
  return number === null ? null : { kind: 'pull-request', number };
619
621
  }
620
622
  return null;
@@ -705,7 +707,8 @@ function probePlatformFailure(
705
707
  return error('OVERRIDE_DOCUMENT_INVALID', cause instanceof Error ? cause.message : String(cause));
706
708
  }
707
709
  const issueNumber = options.probeIssueNumber ?? toPositiveInteger(frontmatter.issue_number);
708
- const pullRequestNumber = options.probePullRequestNumber ?? toPositiveInteger(frontmatter.pr_number);
710
+ const fact = readPrDeliveryFact(frontmatter);
711
+ const pullRequestNumber = options.probePullRequestNumber ?? (fact.status === 'valid' && fact.fact.state === 'bound' ? fact.fact.identity.number : null);
709
712
  const operation = policy.producerId === 'platform.issue'
710
713
  ? issueNumber === null
711
714
  ? null
package/lib/task/index.ts CHANGED
@@ -1,34 +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)
11
+ show [--task <ref> | -t <ref>] Print a task.md
12
+ status [--task <ref> | -t <ref>] Aggregated status view (metadata / artifacts / git / platform)
13
13
 
14
14
  Examples:
15
- ai task cat 11 analysis
16
15
  ai task cat analysis
17
- ai task cat 11 3
18
- ai task decisions 11
16
+ ai task cat --task 11 analysis
17
+ ai task decisions --task 11
19
18
  ai task decisions --item 1
20
- ai task d 11 PL-3 --format markdown
21
- ai task files 11
19
+ ai task d --task 11 --item PL-3 --format markdown
20
+ ai task files --task 11
22
21
  ai task grep resolveArtifact
23
22
  ai task grep resolveArtifact --current
24
- ai task grep resolveArtifact 11
25
- ai task issue-body 11
26
- ai task issue-body 11 --template .github/ISSUE_TEMPLATE/05_other.yml
27
- 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
28
27
  ai task ls
29
- ai task show 11
30
- ai task show TASK-20260612-162737
31
- ai task status 11
28
+ ai task show --task 11
29
+ ai task show --task TASK-20260612-162737
30
+ ai task status --task 11
32
31
 
33
32
  Run 'ai task <command> --help' for details.`;
34
33
 
@@ -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,12 +12,15 @@ 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'
18
20
  ] as const;
19
21
  const lifecycleFailureCatalog = [
20
22
  'LIFECYCLE_DIRECTORY_RENAME_FAILED',
23
+ 'LIFECYCLE_DIRECTORY_COPY_FAILED',
21
24
  'LIFECYCLE_DOCUMENT_INVALID',
22
25
  'LIFECYCLE_FINAL_STATE_INVALID',
23
26
  'LIFECYCLE_IDENTITY_CONFLICT',
@@ -126,6 +129,14 @@ function failed(request: Pick<TaskLifecycleRequest, 'taskRef' | 'intent'>, error
126
129
  };
127
130
  }
128
131
 
132
+ export function taskLifecycleFailure(
133
+ request: Pick<TaskLifecycleRequest, 'taskRef' | 'intent'>,
134
+ error: LifecycleError,
135
+ taskId: string | null = null
136
+ ): TaskLifecycleResult {
137
+ return failed(request, error, { taskId });
138
+ }
139
+
129
140
  function normalizedRequest(request: TaskLifecycleRequest): TaskLifecycleRequest | LifecycleError {
130
141
  if (!request || typeof request !== 'object' || !lifecycleIntentCatalog.includes(request.intent as TaskLifecycleIntent)) {
131
142
  return { code: 'LIFECYCLE_PAYLOAD_INVALID', message: 'intent is not in the lifecycle catalog' };
@@ -326,6 +337,62 @@ function parseJournal(content: string): LifecycleJournal {
326
337
  return value as LifecycleJournal;
327
338
  }
328
339
 
340
+ type DirectoryEntry = Readonly<{ relativePath: string; kind: 'file' | 'directory'; size: number; digest: string }>;
341
+
342
+ function directoryManifest(root: string): DirectoryEntry[] {
343
+ const entries: DirectoryEntry[] = [];
344
+ const visit = (current: string, relative = '') => {
345
+ for (const entry of fs.readdirSync(current, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name))) {
346
+ const relativePath = relative ? path.join(relative, entry.name) : entry.name;
347
+ const absolute = path.join(current, entry.name);
348
+ const stat = fs.lstatSync(absolute);
349
+ if (stat.isSymbolicLink()) throw new Error(`symbolic link is not allowed in lifecycle directory: ${relativePath}`);
350
+ if (entry.isDirectory()) {
351
+ entries.push({ relativePath, kind: 'directory', size: 0, digest: '' });
352
+ visit(absolute, relativePath);
353
+ } else if (entry.isFile()) {
354
+ const digest = createHash('sha256').update(fs.readFileSync(absolute)).digest('hex');
355
+ entries.push({ relativePath, kind: 'file', size: stat.size, digest });
356
+ } else {
357
+ throw new Error(`unsupported lifecycle directory entry: ${relativePath}`);
358
+ }
359
+ }
360
+ };
361
+ visit(root);
362
+ return entries;
363
+ }
364
+
365
+ function copyDirectoryVerified(source: string, target: string): void {
366
+ if (fs.existsSync(target)) throw new Error(`target directory already exists: ${target}`);
367
+ const copy = (currentSource: string, currentTarget: string) => {
368
+ fs.mkdirSync(currentTarget);
369
+ for (const entry of fs.readdirSync(currentSource, { withFileTypes: true })) {
370
+ const sourcePath = path.join(currentSource, entry.name);
371
+ const targetPath = path.join(currentTarget, entry.name);
372
+ const stat = fs.lstatSync(sourcePath);
373
+ if (stat.isSymbolicLink()) throw new Error(`symbolic link is not allowed in lifecycle directory: ${entry.name}`);
374
+ if (entry.isDirectory()) copy(sourcePath, targetPath);
375
+ else if (entry.isFile()) fs.copyFileSync(sourcePath, targetPath);
376
+ else throw new Error(`unsupported lifecycle directory entry: ${entry.name}`);
377
+ }
378
+ };
379
+ copy(source, target);
380
+ const sourceManifest = directoryManifest(source);
381
+ const targetManifest = directoryManifest(target);
382
+ if (JSON.stringify(sourceManifest) !== JSON.stringify(targetManifest)) {
383
+ throw new Error('copied lifecycle directory failed manifest verification');
384
+ }
385
+ }
386
+
387
+ function removeCopiedSource(source: string, target: string): void {
388
+ const sourceManifest = directoryManifest(source);
389
+ const targetManifest = directoryManifest(target);
390
+ if (JSON.stringify(sourceManifest) !== JSON.stringify(targetManifest)) {
391
+ throw new Error('lifecycle source and target diverged before source cleanup');
392
+ }
393
+ fs.rmSync(source, { recursive: true, force: false });
394
+ }
395
+
329
396
  function applyTaskLifecycle(requestInput: TaskLifecycleRequest, options: TaskLifecycleOptions = {}): TaskLifecycleResult {
330
397
  const normalized = normalizedRequest(requestInput);
331
398
  if ('code' in normalized) return failed(requestInput, normalized);
@@ -376,7 +443,7 @@ function applyTaskLifecycle(requestInput: TaskLifecycleRequest, options: TaskLif
376
443
  if (!spec.sources.includes(sourceState) && !existingJournal && !allowsManualOverride(options, 'LIFECYCLE_SOURCE_INVALID')) {
377
444
  return failed(request, { code: 'LIFECYCLE_SOURCE_INVALID', message: `${request.intent} is not allowed from ${sourceState}` }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
378
445
  }
379
- if (sourcePath !== targetPath && fs.existsSync(targetPath)) {
446
+ if (sourcePath !== targetPath && fs.existsSync(targetPath) && !existingJournal) {
380
447
  return failed(request, { code: 'LIFECYCLE_TARGET_CONFLICT', message: `target directory already exists: ${targetPath}` }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
381
448
  }
382
449
  const content = io.readFileSync(currentTaskFile);
@@ -385,6 +452,10 @@ function applyTaskLifecycle(requestInput: TaskLifecycleRequest, options: TaskLif
385
452
  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
453
  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
454
  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 });
455
+ if (spec.target === 'active') {
456
+ const contract = validateCurrentTaskContract(content);
457
+ if (!contract.ok) return failed(request, { code: 'LIFECYCLE_DOCUMENT_INVALID', message: contract.message }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
458
+ }
388
459
  const registryRequest = executeShortIdCommand({
389
460
  operation: 'list', activeDir: path.join(repoRoot, '.agents', 'workspace', 'active'),
390
461
  shortIdLength: configuredShortIdLength(repoRoot)
@@ -408,6 +479,9 @@ function applyTaskLifecycle(requestInput: TaskLifecycleRequest, options: TaskLif
408
479
  try { journal = parseJournal(io.readFileSync(initialJournalPath)); }
409
480
  catch (error) { return failed(request, { code: 'LIFECYCLE_JOURNAL_INVALID', message: String(error) }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath: initialJournalPath }); }
410
481
  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 });
482
+ if (spec.target === 'active' && !isValidAgentInfraVersion(journal.metadata.agentInfraVersion)) {
483
+ 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 });
484
+ }
411
485
  if (request.dryRun) {
412
486
  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
487
  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 +492,9 @@ function applyTaskLifecycle(requestInput: TaskLifecycleRequest, options: TaskLif
418
492
  let metadata: TaskWriteMetadata;
419
493
  try { metadata = (options.metadataProvider ?? captureTaskWriteMetadata)(); }
420
494
  catch (error) { return failed(request, { code: 'LIFECYCLE_METADATA_FAILED', message: String(error) }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath }); }
495
+ if (spec.target === 'active' && !isValidAgentInfraVersion(metadata.agentInfraVersion)) {
496
+ 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 });
497
+ }
421
498
  journal = { version: 1, taskId, intent: request.intent, intentDigest: digest, sourceState, targetState: spec.target, metadata, completedSteps: [] };
422
499
  if (request.dryRun) {
423
500
  const plannedMutations = mutationsFor(request, content, metadata, restoredFiles, allowsManualOverride(options, 'LIFECYCLE_LOG_MISSING'));
@@ -478,13 +555,45 @@ function applyTaskLifecycle(requestInput: TaskLifecycleRequest, options: TaskLif
478
555
  if (fs.existsSync(targetPath)) return failed(request, { code: 'LIFECYCLE_TARGET_CONFLICT', message: `target directory already exists: ${targetPath}` }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath, task: { operations: taskOperations }, changed: true, completedSteps: [...completed], timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion });
479
556
  try { (options.directoryRenameSync ?? io.renameSync)(sourcePath, targetPath); }
480
557
  catch (error) {
558
+ if ((error as { code?: string }).code === 'EXDEV') {
559
+ try {
560
+ copyDirectoryVerified(sourcePath, targetPath);
561
+ completed.add('directory-moved');
562
+ journal.completedSteps = [...completed];
563
+ const copiedJournalPath = path.join(targetPath, '.task-lifecycle.json');
564
+ writeJournal(initialJournalPath, journal, io);
565
+ writeJournal(copiedJournalPath, journal, io);
566
+ removeCopiedSource(sourcePath, targetPath);
567
+ journalPath = copiedJournalPath;
568
+ } catch (copyError) {
569
+ if (fs.existsSync(targetPath)) fs.rmSync(targetPath, { recursive: true, force: false });
570
+ const failure = { code: 'LIFECYCLE_DIRECTORY_COPY_FAILED', message: String(copyError) } as const;
571
+ rememberJournalFailure(initialJournalPath, journal, io, failure);
572
+ return failed(request, failure, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath: initialJournalPath, task: { operations: taskOperations }, changed: true, completedSteps: [...completed], timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion });
573
+ }
574
+ } else {
481
575
  const failure = { code: 'LIFECYCLE_DIRECTORY_RENAME_FAILED', message: String(error) } as const;
482
576
  rememberJournalFailure(journalPath, journal, io, failure);
483
577
  return failed(request, failure, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath, task: { operations: taskOperations }, changed: true, completedSteps: [...completed], timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion });
578
+ }
579
+ }
580
+ if (completed.has('directory-moved') && !fs.existsSync(sourcePath)) {
581
+ journalPath = path.join(targetPath, '.task-lifecycle.json');
582
+ } else if (!completed.has('directory-moved')) {
583
+ completed.add('directory-moved'); journal.completedSteps = [...completed]; journalPath = path.join(targetPath, '.task-lifecycle.json');
584
+ try { writeJournal(journalPath, journal, io); }
585
+ catch (error) { return failed(request, { code: 'LIFECYCLE_JOURNAL_WRITE_FAILED', message: String(error) }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath, task: { operations: taskOperations }, directory: { effect: 'move', changed: true }, changed: true, completedSteps: [...completed], timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion }); }
586
+ }
587
+ }
588
+ if (completed.has('directory-moved') && sourcePath !== targetPath && fs.existsSync(sourcePath) && fs.existsSync(targetPath)) {
589
+ try {
590
+ removeCopiedSource(sourcePath, targetPath);
591
+ journalPath = path.join(targetPath, '.task-lifecycle.json');
592
+ } catch (error) {
593
+ const failure = { code: 'LIFECYCLE_DIRECTORY_COPY_FAILED', message: String(error) } as const;
594
+ rememberJournalFailure(journalPath, journal, io, failure);
595
+ return failed(request, failure, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath, task: { operations: taskOperations }, changed: true, completedSteps: [...completed], timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion });
484
596
  }
485
- completed.add('directory-moved'); journal.completedSteps = [...completed]; journalPath = path.join(targetPath, '.task-lifecycle.json');
486
- try { writeJournal(journalPath, journal, io); }
487
- catch (error) { return failed(request, { code: 'LIFECYCLE_JOURNAL_WRITE_FAILED', message: String(error) }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath, task: { operations: taskOperations }, directory: { effect: 'move', changed: true }, changed: true, completedSteps: [...completed], timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion }); }
488
597
  }
489
598
  let shortId: TaskLifecycleResult['shortId'] = { effect: 'unchanged', shortId: null, changed: false };
490
599
  if (!completed.has('registry-committed')) {