@fitlab-ai/agent-infra 0.9.2 → 0.9.4

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 (428) hide show
  1. package/README.md +12 -9
  2. package/README.zh-CN.md +12 -9
  3. package/bin/cli.ts +12 -0
  4. package/dist/bin/cli.js +14 -0
  5. package/dist/bin/internal-cli.js +134 -108
  6. package/dist/lib/agent-clients/adapter.js +137 -4
  7. package/dist/lib/agent-clients/adapters/antigravity-cli.js +64 -0
  8. package/dist/lib/{sandbox/credentials.js → agent-clients/adapters/claude-code-credentials.js} +4 -14
  9. package/dist/lib/agent-clients/adapters/claude-code-refresh.js +85 -0
  10. package/dist/lib/agent-clients/adapters/claude-code-sandbox.js +247 -0
  11. package/dist/lib/agent-clients/adapters/claude-code.js +27 -4
  12. package/dist/lib/agent-clients/adapters/codex.js +9 -0
  13. package/dist/lib/agent-clients/adapters/index.js +2 -2
  14. package/dist/lib/agent-clients/adapters/opencode-sandbox.js +108 -0
  15. package/dist/lib/agent-clients/adapters/opencode.js +24 -3
  16. package/dist/lib/agent-clients/adapters/runtimes/claude-code.dockerfile +65 -0
  17. package/dist/lib/agent-clients/config.js +42 -4
  18. package/dist/lib/agent-clients/next-steps.js +11 -2
  19. package/dist/lib/agent-clients/reconcile.js +2 -1
  20. package/dist/lib/agent-clients/registry.js +16 -2
  21. package/dist/lib/agent-clients/schema.js +18 -0
  22. package/dist/lib/agent-clients/tokens.js +49 -0
  23. package/dist/lib/agent-clients/types.js +1 -1
  24. package/dist/lib/defaults.json +1 -1
  25. package/dist/lib/git/workflow.js +54 -1
  26. package/dist/lib/internal/agent-client.js +61 -4
  27. package/dist/lib/internal/git-workflow.js +6 -1
  28. package/dist/lib/internal/platform-checks.js +3 -3
  29. package/dist/lib/internal/platform-comment.js +29 -2
  30. package/dist/lib/internal/platform-issue.js +7 -0
  31. package/dist/lib/internal/platform-pr-review.js +132 -0
  32. package/dist/lib/internal/platform-pr.js +20 -2
  33. package/dist/lib/internal/platform-release-notes.js +52 -16
  34. package/dist/lib/internal/pr-review-grade.js +140 -0
  35. package/dist/lib/internal/release-workflow.js +13 -6
  36. package/dist/lib/internal/sandbox-control.js +24 -0
  37. package/dist/lib/internal/task-activity.js +105 -0
  38. package/dist/lib/internal/task-event.js +17 -1
  39. package/dist/lib/internal/task-ledger.js +12 -3
  40. package/dist/lib/internal/task-lifecycle.js +26 -1
  41. package/dist/lib/internal/task-orchestration.js +167 -18
  42. package/dist/lib/internal/task-review.js +8 -3
  43. package/dist/lib/platform/adapters.js +8 -1
  44. package/dist/lib/platform/completion-backfill.js +122 -0
  45. package/dist/lib/platform/github-client.js +19 -2
  46. package/dist/lib/platform/issue-comments.js +3 -2
  47. package/dist/lib/platform/merged-pr-equivalence.js +115 -5
  48. package/dist/lib/platform/pr-checks.js +65 -42
  49. package/dist/lib/platform/pr-review.js +166 -0
  50. package/dist/lib/platform/pull-requests.js +371 -4
  51. package/dist/lib/platform/release-notes.js +85 -3
  52. package/dist/lib/platform/releases.js +13 -5
  53. package/dist/lib/pr-review/evidence-grading.js +229 -0
  54. package/dist/lib/process-data/index.js +239 -0
  55. package/dist/lib/process-data/normalize.js +198 -0
  56. package/dist/lib/process-data/privacy.js +33 -0
  57. package/dist/lib/process-data/reconcile.js +95 -0
  58. package/dist/lib/process-data/repair.js +58 -0
  59. package/dist/lib/process-data/sources.js +345 -0
  60. package/dist/lib/process-data/store.js +282 -0
  61. package/dist/lib/process-data/types.js +2 -0
  62. package/dist/lib/run/index.js +4 -3
  63. package/dist/lib/run/tui.js +10 -13
  64. package/dist/lib/sandbox/agent-client-reconciler.js +26 -2
  65. package/dist/lib/sandbox/capture.js +7 -2
  66. package/dist/lib/sandbox/commands/create.js +109 -248
  67. package/dist/lib/sandbox/commands/enter.js +15 -47
  68. package/dist/lib/sandbox/commands/list-running.js +16 -6
  69. package/dist/lib/sandbox/commands/ls.js +10 -6
  70. package/dist/lib/sandbox/commands/rebuild.js +4 -3
  71. package/dist/lib/sandbox/commands/refresh.js +4 -84
  72. package/dist/lib/sandbox/commands/rm.js +24 -3
  73. package/dist/lib/sandbox/commands/show.js +13 -4
  74. package/dist/lib/sandbox/commands/start.js +7 -4
  75. package/dist/lib/sandbox/config.js +2 -0
  76. package/dist/lib/sandbox/constants.js +6 -0
  77. package/dist/lib/sandbox/control/client.js +44 -0
  78. package/dist/lib/sandbox/control/protocol.js +35 -0
  79. package/dist/lib/sandbox/control/server.js +109 -0
  80. package/dist/lib/sandbox/dockerfile.js +36 -13
  81. package/dist/lib/sandbox/mounts.js +17 -4
  82. package/dist/lib/sandbox/recovery.js +128 -5
  83. package/dist/lib/sandbox/redaction.js +12 -0
  84. package/dist/lib/sandbox/runtimes/base.dockerfile +24 -74
  85. package/dist/lib/sandbox/workspace-identity.js +95 -0
  86. package/dist/lib/sandbox/workspace-view.js +103 -0
  87. package/dist/lib/task/activity-intent.js +307 -0
  88. package/dist/lib/task/artifact-lifecycle.js +2 -1
  89. package/dist/lib/task/commands/log.js +15 -14
  90. package/dist/lib/task/commit-finalization.js +267 -0
  91. package/dist/lib/task/commit-intent.js +165 -0
  92. package/dist/lib/task/decision-intents.js +31 -17
  93. package/dist/lib/task/delegation-receipts.js +25 -4
  94. package/dist/lib/task/events.js +67 -28
  95. package/dist/lib/task/finalization-artifacts.js +35 -0
  96. package/dist/lib/task/implementation-inputs.js +54 -7
  97. package/dist/lib/task/ledger-intents.js +35 -1
  98. package/dist/lib/task/lifecycle-execution.js +46 -0
  99. package/dist/lib/task/orchestration.js +753 -54
  100. package/dist/lib/task/resolve-ref.js +41 -1
  101. package/dist/lib/task/review-finalization.js +29 -9
  102. package/dist/lib/task/task-execution-lock.js +231 -0
  103. package/dist/lib/task/verification-checks.js +1 -1
  104. package/dist/lib/task/verification-engine.js +218 -2
  105. package/dist/lib/task/verification.js +4 -3
  106. package/dist/lib/task/workspace-snapshot.js +17 -5
  107. package/lib/agent-clients/adapter.ts +201 -4
  108. package/lib/agent-clients/adapters/antigravity-cli.ts +65 -0
  109. package/lib/{sandbox/credentials.ts → agent-clients/adapters/claude-code-credentials.ts} +3 -18
  110. package/lib/agent-clients/adapters/claude-code-refresh.ts +128 -0
  111. package/lib/agent-clients/adapters/claude-code-sandbox.ts +295 -0
  112. package/lib/agent-clients/adapters/claude-code.ts +31 -4
  113. package/lib/agent-clients/adapters/codex.ts +9 -0
  114. package/lib/agent-clients/adapters/index.ts +2 -2
  115. package/lib/agent-clients/adapters/opencode-sandbox.ts +136 -0
  116. package/lib/agent-clients/adapters/opencode.ts +27 -3
  117. package/lib/agent-clients/adapters/runtimes/claude-code.dockerfile +65 -0
  118. package/lib/agent-clients/config.ts +49 -4
  119. package/lib/agent-clients/next-steps.ts +15 -2
  120. package/lib/agent-clients/reconcile.ts +2 -1
  121. package/lib/agent-clients/registry.ts +19 -1
  122. package/lib/agent-clients/schema.ts +18 -0
  123. package/lib/agent-clients/tokens.ts +53 -0
  124. package/lib/agent-clients/types.ts +15 -1
  125. package/lib/defaults.json +1 -1
  126. package/lib/git/workflow.ts +66 -2
  127. package/lib/internal/agent-client.ts +59 -4
  128. package/lib/internal/git-workflow.ts +5 -1
  129. package/lib/internal/platform-checks.ts +4 -4
  130. package/lib/internal/platform-comment.ts +17 -2
  131. package/lib/internal/platform-issue.ts +4 -0
  132. package/lib/internal/platform-pr-review.ts +104 -0
  133. package/lib/internal/platform-pr.ts +14 -1
  134. package/lib/internal/platform-release-notes.ts +59 -16
  135. package/lib/internal/pr-review-grade.ts +130 -0
  136. package/lib/internal/release-workflow.ts +13 -6
  137. package/lib/internal/sandbox-control.ts +24 -0
  138. package/lib/internal/task-activity.ts +91 -0
  139. package/lib/internal/task-event.ts +9 -1
  140. package/lib/internal/task-ledger.ts +12 -3
  141. package/lib/internal/task-lifecycle.ts +26 -1
  142. package/lib/internal/task-orchestration.ts +123 -14
  143. package/lib/internal/task-review.ts +8 -3
  144. package/lib/platform/adapters.ts +29 -2
  145. package/lib/platform/completion-backfill.ts +134 -0
  146. package/lib/platform/github-client.ts +20 -2
  147. package/lib/platform/issue-comments.ts +3 -2
  148. package/lib/platform/merged-pr-equivalence.ts +127 -5
  149. package/lib/platform/pr-checks.ts +73 -46
  150. package/lib/platform/pr-review.ts +201 -0
  151. package/lib/platform/pull-requests.ts +444 -3
  152. package/lib/platform/release-notes.ts +100 -4
  153. package/lib/platform/releases.ts +16 -4
  154. package/lib/pr-review/evidence-grading.ts +318 -0
  155. package/lib/process-data/index.ts +224 -0
  156. package/lib/process-data/normalize.ts +202 -0
  157. package/lib/process-data/privacy.ts +41 -0
  158. package/lib/process-data/reconcile.ts +110 -0
  159. package/lib/process-data/repair.ts +64 -0
  160. package/lib/process-data/sources.ts +354 -0
  161. package/lib/process-data/store.ts +315 -0
  162. package/lib/process-data/types.ts +135 -0
  163. package/lib/run/index.ts +5 -3
  164. package/lib/run/tui.ts +14 -12
  165. package/lib/sandbox/agent-client-reconciler.ts +44 -2
  166. package/lib/sandbox/capture.ts +9 -2
  167. package/lib/sandbox/commands/create.ts +126 -280
  168. package/lib/sandbox/commands/enter.ts +13 -59
  169. package/lib/sandbox/commands/list-running.ts +23 -6
  170. package/lib/sandbox/commands/ls.ts +22 -6
  171. package/lib/sandbox/commands/rebuild.ts +4 -3
  172. package/lib/sandbox/commands/refresh.ts +4 -126
  173. package/lib/sandbox/commands/rm.ts +24 -3
  174. package/lib/sandbox/commands/show.ts +30 -4
  175. package/lib/sandbox/commands/start.ts +6 -4
  176. package/lib/sandbox/config.ts +4 -0
  177. package/lib/sandbox/constants.ts +8 -0
  178. package/lib/sandbox/control/client.ts +55 -0
  179. package/lib/sandbox/control/protocol.ts +75 -0
  180. package/lib/sandbox/control/server.ts +117 -0
  181. package/lib/sandbox/dockerfile.ts +66 -13
  182. package/lib/sandbox/mounts.ts +24 -4
  183. package/lib/sandbox/recovery.ts +163 -6
  184. package/lib/sandbox/redaction.ts +14 -0
  185. package/lib/sandbox/runtimes/base.dockerfile +24 -74
  186. package/lib/sandbox/tool-types.ts +16 -0
  187. package/lib/sandbox/workspace-identity.ts +123 -0
  188. package/lib/sandbox/workspace-view.ts +143 -0
  189. package/lib/server/server.schema.json +2 -2
  190. package/lib/task/activity-intent.ts +417 -0
  191. package/lib/task/artifact-lifecycle.ts +2 -1
  192. package/lib/task/commands/log.ts +15 -14
  193. package/lib/task/commit-finalization.ts +356 -0
  194. package/lib/task/commit-intent.ts +230 -0
  195. package/lib/task/decision-intents.ts +32 -12
  196. package/lib/task/delegation-receipts.ts +34 -5
  197. package/lib/task/events.ts +75 -30
  198. package/lib/task/finalization-artifacts.ts +58 -0
  199. package/lib/task/implementation-inputs.ts +62 -8
  200. package/lib/task/ledger-intents.ts +43 -3
  201. package/lib/task/lifecycle-execution.ts +106 -0
  202. package/lib/task/orchestration.ts +1019 -72
  203. package/lib/task/resolve-ref.ts +46 -1
  204. package/lib/task/review-finalization.ts +40 -9
  205. package/lib/task/task-execution-lock.ts +296 -0
  206. package/lib/task/verification-checks.ts +1 -1
  207. package/lib/task/verification-engine.ts +249 -2
  208. package/lib/task/verification.ts +5 -3
  209. package/lib/task/workspace-snapshot.ts +26 -5
  210. package/package.json +3 -3
  211. package/templates/.agents/QUICKSTART.en.md +25 -8
  212. package/templates/.agents/QUICKSTART.zh-CN.md +25 -8
  213. package/templates/.agents/README.en.md +10 -7
  214. package/templates/.agents/README.zh-CN.md +10 -7
  215. package/templates/.agents/hooks/lifecycle-delegation.js +15 -4
  216. package/templates/.agents/rules/README.en.md +1 -1
  217. package/templates/.agents/rules/README.zh-CN.md +1 -1
  218. package/templates/.agents/rules/create-issue.github.en.md +4 -2
  219. package/templates/.agents/rules/create-issue.github.zh-CN.md +4 -2
  220. package/templates/.agents/rules/human-decision-context.en.md +11 -2
  221. package/templates/.agents/rules/human-decision-context.zh-CN.md +11 -2
  222. package/templates/.agents/rules/issue-fields.github.en.md +3 -1
  223. package/templates/.agents/rules/issue-fields.github.zh-CN.md +3 -1
  224. package/templates/.agents/rules/issue-pr-commands.en.md +5 -3
  225. package/templates/.agents/rules/issue-pr-commands.github.en.md +5 -3
  226. package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +5 -3
  227. package/templates/.agents/rules/issue-pr-commands.zh-CN.md +5 -3
  228. package/templates/.agents/rules/issue-sync.github.en.md +7 -3
  229. package/templates/.agents/rules/issue-sync.github.zh-CN.md +8 -4
  230. package/templates/.agents/rules/lifecycle-orchestration.en.md +5 -2
  231. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +5 -2
  232. package/templates/.agents/rules/milestone-inference.github.en.md +4 -2
  233. package/templates/.agents/rules/milestone-inference.github.zh-CN.md +4 -2
  234. package/templates/.agents/rules/next-step-output.en.md +3 -1
  235. package/templates/.agents/rules/next-step-output.zh-CN.md +3 -1
  236. package/templates/.agents/rules/no-mid-flow-questions.en.md +17 -0
  237. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +17 -0
  238. package/templates/.agents/rules/pr-checks-commands.en.md +3 -3
  239. package/templates/.agents/rules/pr-checks-commands.github.en.md +2 -2
  240. package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +3 -3
  241. package/templates/.agents/rules/pr-checks-commands.zh-CN.md +3 -3
  242. package/templates/.agents/rules/pr-sync.en.md +3 -1
  243. package/templates/.agents/rules/pr-sync.github.en.md +11 -1
  244. package/templates/.agents/rules/pr-sync.github.zh-CN.md +11 -1
  245. package/templates/.agents/rules/pr-sync.zh-CN.md +3 -1
  246. package/templates/.agents/rules/release-commands.en.md +1 -1
  247. package/templates/.agents/rules/release-commands.github.en.md +12 -2
  248. package/templates/.agents/rules/release-commands.github.zh-CN.md +12 -2
  249. package/templates/.agents/rules/release-commands.zh-CN.md +1 -1
  250. package/templates/.agents/rules/review-handshake.zh-CN.md +1 -1
  251. package/templates/.agents/rules/task-management.en.md +13 -0
  252. package/templates/.agents/rules/task-management.zh-CN.md +13 -0
  253. package/templates/.agents/skills/analyze-task/SKILL.en.md +10 -7
  254. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +10 -7
  255. package/templates/.agents/skills/block-task/SKILL.en.md +7 -5
  256. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +7 -5
  257. package/templates/.agents/skills/cancel-task/SKILL.en.md +6 -4
  258. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +6 -4
  259. package/templates/.agents/skills/close-codescan/SKILL.en.md +3 -1
  260. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +3 -1
  261. package/templates/.agents/skills/close-dependabot/SKILL.en.md +3 -1
  262. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +3 -1
  263. package/templates/.agents/skills/code-task/SKILL.en.md +10 -7
  264. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +12 -9
  265. package/templates/.agents/skills/commit/SKILL.en.md +23 -11
  266. package/templates/.agents/skills/commit/SKILL.zh-CN.md +29 -11
  267. package/templates/.agents/skills/commit/reference/commit-message.en.md +1 -1
  268. package/templates/.agents/skills/commit/reference/commit-message.zh-CN.md +1 -1
  269. package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +49 -0
  270. package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +55 -0
  271. package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
  272. package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
  273. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
  274. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
  275. package/templates/.agents/skills/commit/reference/task-status-update.en.md +4 -4
  276. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +4 -4
  277. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +6 -4
  278. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +6 -4
  279. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +1 -1
  280. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +1 -1
  281. package/templates/.agents/skills/complete-task/SKILL.en.md +22 -7
  282. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +22 -7
  283. package/templates/.agents/skills/complete-task/config/verify.en.json +1 -0
  284. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -0
  285. package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +17 -0
  286. package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +17 -0
  287. package/templates/.agents/skills/create-pr/SKILL.en.md +7 -5
  288. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +7 -5
  289. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +1 -1
  290. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +1 -1
  291. package/templates/.agents/skills/create-release-note/SKILL.en.md +17 -16
  292. package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +17 -16
  293. package/templates/.agents/skills/create-task/SKILL.en.md +5 -3
  294. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -3
  295. package/templates/.agents/skills/create-task/reference/context-capture.en.md +34 -0
  296. package/templates/.agents/skills/create-task/reference/context-capture.zh-CN.md +34 -0
  297. package/templates/.agents/skills/import-issue/SKILL.en.md +5 -3
  298. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -3
  299. package/templates/.agents/skills/plan-task/SKILL.en.md +8 -5
  300. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +8 -5
  301. package/templates/.agents/skills/post-release/SKILL.en.md +2 -0
  302. package/templates/.agents/skills/post-release/SKILL.zh-CN.md +2 -0
  303. package/templates/.agents/skills/release/SKILL.en.md +16 -10
  304. package/templates/.agents/skills/release/SKILL.zh-CN.md +16 -10
  305. package/templates/.agents/skills/restore-task/SKILL.en.md +4 -1
  306. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +4 -1
  307. package/templates/.agents/skills/review-analysis/SKILL.en.md +7 -4
  308. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +7 -4
  309. package/templates/.agents/skills/review-code/SKILL.en.md +11 -8
  310. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +11 -8
  311. package/templates/.agents/skills/review-code/reference/output-templates.en.md +1 -1
  312. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +1 -1
  313. package/templates/.agents/skills/review-plan/SKILL.en.md +7 -4
  314. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +7 -4
  315. package/templates/.agents/skills/review-pr/SKILL.en.md +186 -0
  316. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +186 -0
  317. package/templates/.agents/skills/review-pr/config/verify.en.json +37 -0
  318. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +37 -0
  319. package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +65 -0
  320. package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +65 -0
  321. package/templates/.agents/skills/review-pr/reference/output-guidance.en.md +18 -0
  322. package/templates/.agents/skills/review-pr/reference/output-guidance.zh-CN.md +18 -0
  323. package/templates/.agents/skills/review-pr/reference/report-template.en.md +85 -0
  324. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +85 -0
  325. package/templates/.agents/skills/run-task/SKILL.en.md +8 -8
  326. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +8 -8
  327. package/templates/.agents/skills/run-task/config/verify.en.json +2 -1
  328. package/templates/.agents/skills/run-task/config/verify.zh-CN.json +2 -1
  329. package/templates/.agents/skills/run-task/reference/host-validation.en.md +20 -0
  330. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +20 -0
  331. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +98 -107
  332. package/templates/.agents/skills/watch-pr/SKILL.en.md +13 -13
  333. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +13 -13
  334. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +27 -6
  335. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +26 -5
  336. package/templates/.agents/templates/task.en.md +2 -2
  337. package/templates/.agents/templates/task.zh-CN.md +2 -2
  338. package/templates/.agents/workflows/bug-fix.en.yaml +3 -3
  339. package/templates/.agents/workflows/bug-fix.zh-CN.yaml +3 -3
  340. package/templates/.agents/workflows/code-review.en.yaml +1 -1
  341. package/templates/.agents/workflows/code-review.zh-CN.yaml +1 -1
  342. package/templates/.agents/workflows/feature-development.en.yaml +4 -4
  343. package/templates/.agents/workflows/feature-development.zh-CN.yaml +4 -4
  344. package/templates/.agents/workflows/refactoring.en.yaml +3 -3
  345. package/templates/.agents/workflows/refactoring.zh-CN.yaml +3 -3
  346. package/templates/.claude/commands/post-release.en.md +1 -0
  347. package/templates/.claude/commands/post-release.zh-CN.md +1 -0
  348. package/templates/.claude/commands/review-pr.en.md +8 -0
  349. package/templates/.claude/commands/review-pr.zh-CN.md +8 -0
  350. package/templates/.claude/commands/watch-pr.en.md +1 -1
  351. package/templates/.claude/commands/watch-pr.zh-CN.md +1 -1
  352. package/templates/.claude/rules/tool-preferences.en.md +20 -0
  353. package/templates/.claude/rules/tool-preferences.zh-CN.md +20 -0
  354. package/templates/.opencode/commands/post-release.en.md +2 -0
  355. package/templates/.opencode/commands/post-release.zh-CN.md +2 -0
  356. package/templates/.opencode/commands/review-pr.en.md +11 -0
  357. package/templates/.opencode/commands/review-pr.zh-CN.md +11 -0
  358. package/templates/.opencode/commands/watch-pr.en.md +1 -1
  359. package/templates/.opencode/commands/watch-pr.zh-CN.md +1 -1
  360. package/dist/lib/agent-clients/adapters/gemini-cli.js +0 -64
  361. package/lib/agent-clients/adapters/gemini-cli.ts +0 -65
  362. package/templates/.gemini/commands/_project_/analyze-task.en.toml +0 -10
  363. package/templates/.gemini/commands/_project_/analyze-task.zh-CN.toml +0 -10
  364. package/templates/.gemini/commands/_project_/archive-tasks.en.toml +0 -10
  365. package/templates/.gemini/commands/_project_/archive-tasks.zh-CN.toml +0 -10
  366. package/templates/.gemini/commands/_project_/block-task.en.toml +0 -10
  367. package/templates/.gemini/commands/_project_/block-task.zh-CN.toml +0 -10
  368. package/templates/.gemini/commands/_project_/cancel-task.en.toml +0 -10
  369. package/templates/.gemini/commands/_project_/cancel-task.zh-CN.toml +0 -10
  370. package/templates/.gemini/commands/_project_/check-task.en.toml +0 -10
  371. package/templates/.gemini/commands/_project_/check-task.zh-CN.toml +0 -10
  372. package/templates/.gemini/commands/_project_/close-codescan.en.toml +0 -8
  373. package/templates/.gemini/commands/_project_/close-codescan.zh-CN.toml +0 -8
  374. package/templates/.gemini/commands/_project_/close-dependabot.en.toml +0 -8
  375. package/templates/.gemini/commands/_project_/close-dependabot.zh-CN.toml +0 -8
  376. package/templates/.gemini/commands/_project_/code-task.en.toml +0 -10
  377. package/templates/.gemini/commands/_project_/code-task.zh-CN.toml +0 -10
  378. package/templates/.gemini/commands/_project_/commit.en.toml +0 -10
  379. package/templates/.gemini/commands/_project_/commit.zh-CN.toml +0 -10
  380. package/templates/.gemini/commands/_project_/complete-manual-validation.en.toml +0 -10
  381. package/templates/.gemini/commands/_project_/complete-manual-validation.zh-CN.toml +0 -10
  382. package/templates/.gemini/commands/_project_/complete-task.en.toml +0 -10
  383. package/templates/.gemini/commands/_project_/complete-task.zh-CN.toml +0 -10
  384. package/templates/.gemini/commands/_project_/create-pr.en.toml +0 -10
  385. package/templates/.gemini/commands/_project_/create-pr.zh-CN.toml +0 -10
  386. package/templates/.gemini/commands/_project_/create-release-note.en.toml +0 -8
  387. package/templates/.gemini/commands/_project_/create-release-note.zh-CN.toml +0 -8
  388. package/templates/.gemini/commands/_project_/create-task.en.toml +0 -8
  389. package/templates/.gemini/commands/_project_/create-task.zh-CN.toml +0 -8
  390. package/templates/.gemini/commands/_project_/import-codescan.en.toml +0 -8
  391. package/templates/.gemini/commands/_project_/import-codescan.zh-CN.toml +0 -8
  392. package/templates/.gemini/commands/_project_/import-dependabot.en.toml +0 -8
  393. package/templates/.gemini/commands/_project_/import-dependabot.zh-CN.toml +0 -8
  394. package/templates/.gemini/commands/_project_/import-issue.en.toml +0 -8
  395. package/templates/.gemini/commands/_project_/import-issue.zh-CN.toml +0 -8
  396. package/templates/.gemini/commands/_project_/init-labels.en.toml +0 -8
  397. package/templates/.gemini/commands/_project_/init-labels.zh-CN.toml +0 -8
  398. package/templates/.gemini/commands/_project_/init-milestones.en.toml +0 -10
  399. package/templates/.gemini/commands/_project_/init-milestones.zh-CN.toml +0 -10
  400. package/templates/.gemini/commands/_project_/plan-task.en.toml +0 -10
  401. package/templates/.gemini/commands/_project_/plan-task.zh-CN.toml +0 -10
  402. package/templates/.gemini/commands/_project_/post-release.en.toml +0 -6
  403. package/templates/.gemini/commands/_project_/post-release.zh-CN.toml +0 -6
  404. package/templates/.gemini/commands/_project_/refine-title.en.toml +0 -8
  405. package/templates/.gemini/commands/_project_/refine-title.zh-CN.toml +0 -8
  406. package/templates/.gemini/commands/_project_/release.en.toml +0 -8
  407. package/templates/.gemini/commands/_project_/release.zh-CN.toml +0 -8
  408. package/templates/.gemini/commands/_project_/restore-task.en.toml +0 -8
  409. package/templates/.gemini/commands/_project_/restore-task.zh-CN.toml +0 -8
  410. package/templates/.gemini/commands/_project_/review-analysis.en.toml +0 -10
  411. package/templates/.gemini/commands/_project_/review-analysis.zh-CN.toml +0 -10
  412. package/templates/.gemini/commands/_project_/review-code.en.toml +0 -10
  413. package/templates/.gemini/commands/_project_/review-code.zh-CN.toml +0 -10
  414. package/templates/.gemini/commands/_project_/review-plan.en.toml +0 -10
  415. package/templates/.gemini/commands/_project_/review-plan.zh-CN.toml +0 -10
  416. package/templates/.gemini/commands/_project_/run-task.en.toml +0 -8
  417. package/templates/.gemini/commands/_project_/run-task.zh-CN.toml +0 -8
  418. package/templates/.gemini/commands/_project_/test-integration.en.toml +0 -6
  419. package/templates/.gemini/commands/_project_/test-integration.zh-CN.toml +0 -6
  420. package/templates/.gemini/commands/_project_/test.en.toml +0 -6
  421. package/templates/.gemini/commands/_project_/test.zh-CN.toml +0 -6
  422. package/templates/.gemini/commands/_project_/update-agent-infra.en.toml +0 -6
  423. package/templates/.gemini/commands/_project_/update-agent-infra.zh-CN.toml +0 -6
  424. package/templates/.gemini/commands/_project_/upgrade-dependency.en.toml +0 -8
  425. package/templates/.gemini/commands/_project_/upgrade-dependency.zh-CN.toml +0 -8
  426. package/templates/.gemini/commands/_project_/watch-pr.en.toml +0 -10
  427. package/templates/.gemini/commands/_project_/watch-pr.zh-CN.toml +0 -10
  428. package/templates/.gemini/settings.json +0 -3
@@ -0,0 +1,356 @@
1
+ import { execFileSync, spawnSync } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ import { locateActivityLog, appendActivityEntry, pairEntries } from './activity-log.ts';
6
+ import { commitIntentPath, digest, readCommitIntent } from './commit-intent.ts';
7
+ import type { CommitIntent } from './commit-intent.ts';
8
+ import { parseTypedTaskFrontmatter } from './frontmatter.ts';
9
+ import { parseReviewSummary } from './review-artifacts.ts';
10
+ import {
11
+ extractReviewBaseline,
12
+ extractReviewedSnapshotTree,
13
+ findAuthoritativeReviewCodeArtifact
14
+ } from './review-fingerprint.ts';
15
+ import type { TaskMutation } from './write.ts';
16
+
17
+ type CommitFinalizationDisposition =
18
+ | 'idle'
19
+ | 'prepared'
20
+ | 'recoverable'
21
+ | 'invalid'
22
+ | 'conflict'
23
+ | 'orphaned-start';
24
+
25
+ type CommitFinalizationCode =
26
+ | 'COMMIT_FINALIZATION_PENDING'
27
+ | 'COMMIT_FINALIZATION_EVIDENCE_MISSING'
28
+ | 'COMMIT_FINALIZATION_EVIDENCE_INVALID'
29
+ | 'COMMIT_FINALIZATION_CONFLICT';
30
+
31
+ type CommitFinalizationInspection = Readonly<{
32
+ disposition: CommitFinalizationDisposition;
33
+ code: CommitFinalizationCode | null;
34
+ message: string;
35
+ currentHead: string;
36
+ committedHead: string | null;
37
+ commitNote: string | null;
38
+ needsAnchor: boolean;
39
+ needsLog: boolean;
40
+ intent: CommitIntent | null;
41
+ intentDigest: string | null;
42
+ }>;
43
+
44
+ type CreatePrCommitGate = Readonly<{
45
+ allowed: boolean;
46
+ code: CommitFinalizationCode | null;
47
+ message: string;
48
+ disposition: CommitFinalizationDisposition;
49
+ action: 'continue' | 'rerun-commit' | 'rerun-review-code';
50
+ }>;
51
+
52
+ type CommitTaskFinalizationPlan = Readonly<{
53
+ mutations: readonly TaskMutation[];
54
+ commitNote: string;
55
+ }>;
56
+
57
+ const SHA_RE = /^[a-f0-9]{40,64}$/;
58
+
59
+ function git(repoRoot: string, args: string[]): string {
60
+ return execFileSync('git', args, {
61
+ cwd: repoRoot,
62
+ encoding: 'utf8',
63
+ stdio: ['ignore', 'pipe', 'pipe']
64
+ }).trim();
65
+ }
66
+
67
+ function commitExists(repoRoot: string, value: string): boolean {
68
+ return SHA_RE.test(value) && spawnSync('git', ['cat-file', '-e', `${value}^{commit}`], {
69
+ cwd: repoRoot,
70
+ stdio: 'ignore'
71
+ }).status === 0;
72
+ }
73
+
74
+ function isAncestor(repoRoot: string, ancestor: string, descendant: string): boolean {
75
+ return spawnSync('git', ['merge-base', '--is-ancestor', ancestor, descendant], {
76
+ cwd: repoRoot,
77
+ stdio: 'ignore'
78
+ }).status === 0;
79
+ }
80
+
81
+ function outcome(
82
+ disposition: CommitFinalizationDisposition,
83
+ overrides: Partial<CommitFinalizationInspection> = {}
84
+ ): CommitFinalizationInspection {
85
+ return {
86
+ disposition,
87
+ code: null,
88
+ message: disposition,
89
+ currentHead: '',
90
+ committedHead: null,
91
+ commitNote: null,
92
+ needsAnchor: false,
93
+ needsLog: false,
94
+ intent: null,
95
+ intentDigest: null,
96
+ ...overrides
97
+ };
98
+ }
99
+
100
+ function invalid(
101
+ message: string,
102
+ currentHead = '',
103
+ code: CommitFinalizationCode = 'COMMIT_FINALIZATION_EVIDENCE_INVALID',
104
+ overrides: Partial<CommitFinalizationInspection> = {}
105
+ ): CommitFinalizationInspection {
106
+ return outcome('invalid', { code, message, currentHead, ...overrides });
107
+ }
108
+
109
+ function inspectCommitFinalization(
110
+ taskDir: string,
111
+ repoRoot: string,
112
+ taskId: string
113
+ ): CommitFinalizationInspection {
114
+ let currentHead: string;
115
+ let taskContent: string;
116
+ let metadata: ReturnType<typeof parseTypedTaskFrontmatter>;
117
+ try {
118
+ currentHead = git(repoRoot, ['rev-parse', 'HEAD']);
119
+ taskContent = fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8');
120
+ metadata = parseTypedTaskFrontmatter(taskContent);
121
+ } catch (error) {
122
+ return invalid(error instanceof Error ? error.message : String(error));
123
+ }
124
+
125
+ const activity = locateActivityLog(taskContent);
126
+ if (!activity) return invalid('task activity log is missing or ambiguous', currentHead);
127
+ const commitRows = pairEntries(activity.entries).filter((row) => row.step === 'Commit');
128
+ const openRows = commitRows.filter((row) => row.started !== '' && row.done === '');
129
+ const file = commitIntentPath(taskDir);
130
+ if (!fs.existsSync(file)) {
131
+ if (openRows.length > 0) {
132
+ return outcome('orphaned-start', {
133
+ code: 'COMMIT_FINALIZATION_EVIDENCE_MISSING',
134
+ message: 'an open Commit activity entry exists without a commit intent',
135
+ currentHead
136
+ });
137
+ }
138
+ return outcome('idle', { message: 'no active commit finalization exists', currentHead });
139
+ }
140
+
141
+ let raw: string;
142
+ let intent: CommitIntent;
143
+ try {
144
+ raw = fs.readFileSync(file, 'utf8');
145
+ intent = readCommitIntent(taskDir, taskId);
146
+ } catch (error) {
147
+ return invalid(error instanceof Error ? error.message : String(error), currentHead);
148
+ }
149
+ const intentDigest = digest(raw);
150
+ const shared = { currentHead, intent, intentDigest, committedHead: intent.committedHead };
151
+
152
+ if (!commitExists(repoRoot, intent.baselineHead)) {
153
+ return invalid('commit intent baseline is not a valid Git commit', currentHead, 'COMMIT_FINALIZATION_EVIDENCE_INVALID', shared);
154
+ }
155
+
156
+ if (intent.phase === 'prepared') {
157
+ if (intent.committedHead !== null || intent.pushEvidence !== null) {
158
+ return invalid('prepared commit intent contains commit or push evidence', currentHead, 'COMMIT_FINALIZATION_EVIDENCE_INVALID', shared);
159
+ }
160
+ if (currentHead !== intent.baselineHead || openRows.length !== 1) {
161
+ return outcome('conflict', {
162
+ ...shared,
163
+ code: 'COMMIT_FINALIZATION_CONFLICT',
164
+ message: 'prepared commit intent does not match HEAD and exactly one open Commit entry'
165
+ });
166
+ }
167
+ return outcome('prepared', { ...shared, message: 'prepared commit intent can be safely retried' });
168
+ }
169
+
170
+ if (!intent.committedHead || !commitExists(repoRoot, intent.committedHead)) {
171
+ return invalid('committed intent head is missing or invalid', currentHead, 'COMMIT_FINALIZATION_EVIDENCE_INVALID', shared);
172
+ }
173
+ if (intent.phase === 'committed' && intent.pushEvidence !== null) {
174
+ return invalid('committed intent contains premature push evidence', currentHead, 'COMMIT_FINALIZATION_EVIDENCE_INVALID', shared);
175
+ }
176
+ if (
177
+ intent.phase === 'pushed'
178
+ && (
179
+ intent.pushEvidence === null
180
+ || intent.pushEvidence.head !== intent.committedHead
181
+ || !intent.pushEvidence.remote
182
+ || !intent.pushEvidence.ref
183
+ )
184
+ ) {
185
+ return invalid('pushed intent evidence is missing or inconsistent', currentHead, 'COMMIT_FINALIZATION_EVIDENCE_INVALID', shared);
186
+ }
187
+ if (currentHead !== intent.committedHead || !isAncestor(repoRoot, intent.baselineHead, intent.committedHead)) {
188
+ return outcome('conflict', {
189
+ ...shared,
190
+ code: 'COMMIT_FINALIZATION_CONFLICT',
191
+ message: 'repository HEAD or ancestry conflicts with the commit intent'
192
+ });
193
+ }
194
+
195
+ const review = findAuthoritativeReviewCodeArtifact(taskDir);
196
+ let needsAnchor = false;
197
+ if (review.ok && review.path) {
198
+ let reviewContent: string;
199
+ try {
200
+ reviewContent = fs.readFileSync(review.path, 'utf8');
201
+ } catch (error) {
202
+ return invalid(error instanceof Error ? error.message : String(error), currentHead, 'COMMIT_FINALIZATION_EVIDENCE_MISSING', shared);
203
+ }
204
+ const summary = parseReviewSummary(reviewContent);
205
+ const reviewBaseline = extractReviewBaseline(reviewContent);
206
+ const reviewTree = extractReviewedSnapshotTree(reviewContent);
207
+ if (!summary.ok || summary.summary.verdict !== 'Approved' || !commitExists(repoRoot, reviewBaseline) || !SHA_RE.test(reviewTree)) {
208
+ return invalid('authoritative review-code evidence is not a valid approved snapshot', currentHead, 'COMMIT_FINALIZATION_EVIDENCE_INVALID', shared);
209
+ }
210
+ const committedTree = git(repoRoot, ['rev-parse', `${intent.committedHead}^{tree}`]);
211
+ if (reviewBaseline !== intent.baselineHead || reviewTree !== committedTree) {
212
+ return outcome('conflict', {
213
+ ...shared,
214
+ code: 'COMMIT_FINALIZATION_CONFLICT',
215
+ message: 'review baseline or snapshot tree conflicts with the committed intent'
216
+ });
217
+ }
218
+
219
+ const anchor = String(metadata.last_reviewed_commit ?? '').trim();
220
+ if (anchor && anchor !== intent.committedHead) {
221
+ return outcome('conflict', {
222
+ ...shared,
223
+ code: 'COMMIT_FINALIZATION_CONFLICT',
224
+ message: 'task review anchor conflicts with the committed intent'
225
+ });
226
+ }
227
+ needsAnchor = anchor === '';
228
+ }
229
+ const commitNote = git(repoRoot, ['show', '-s', '--format=%h%x20%s', intent.committedHead]);
230
+ const matchingDone = commitRows.filter((row) => row.done !== '' && row.note === commitNote);
231
+ const needsLog = matchingDone.length === 0 && openRows.length === 1;
232
+ if (!(needsLog || (matchingDone.length === 1 && openRows.length === 0))) {
233
+ return outcome('conflict', {
234
+ ...shared,
235
+ code: 'COMMIT_FINALIZATION_CONFLICT',
236
+ message: 'Commit activity identity is missing, duplicated, or ambiguous',
237
+ commitNote
238
+ });
239
+ }
240
+ return outcome('recoverable', {
241
+ ...shared,
242
+ message: 'commit finalization can be recovered',
243
+ commitNote,
244
+ needsAnchor,
245
+ needsLog
246
+ });
247
+ }
248
+
249
+ function planCommitTaskFinalization(
250
+ taskDir: string,
251
+ inspection: CommitFinalizationInspection,
252
+ agent: string,
253
+ timestamp: string
254
+ ): CommitTaskFinalizationPlan {
255
+ if (inspection.disposition !== 'recoverable' || !inspection.committedHead || !inspection.commitNote) {
256
+ throw new Error('commit finalization task mutations require a recoverable inspection');
257
+ }
258
+ const taskContent = fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8');
259
+ const frontmatter: Record<string, string> = { assigned_to: agent };
260
+ if (inspection.needsAnchor) frontmatter.last_reviewed_commit = inspection.committedHead;
261
+ const mutations: TaskMutation[] = [{
262
+ kind: 'frontmatter',
263
+ set: frontmatter
264
+ }];
265
+ if (inspection.needsLog) {
266
+ const activity = locateActivityLog(taskContent);
267
+ if (!activity) throw new Error('task activity log is missing or ambiguous');
268
+ mutations.push({
269
+ kind: 'section',
270
+ aliases: ['活动日志', 'Activity Log'],
271
+ heading: activity.heading,
272
+ body: appendActivityEntry(activity, {
273
+ time: timestamp,
274
+ step: 'Commit',
275
+ agent,
276
+ note: inspection.commitNote
277
+ })
278
+ });
279
+ }
280
+ return { mutations, commitNote: inspection.commitNote };
281
+ }
282
+
283
+ function inspectCreatePrCommitGate(taskDir: string, repoRoot: string, taskId: string): CreatePrCommitGate {
284
+ const lifecycle = inspectCommitFinalization(taskDir, repoRoot, taskId);
285
+ if (lifecycle.disposition !== 'idle') {
286
+ const pending = lifecycle.disposition === 'prepared' || lifecycle.disposition === 'recoverable';
287
+ return {
288
+ allowed: false,
289
+ code: pending ? 'COMMIT_FINALIZATION_PENDING' : lifecycle.code ?? 'COMMIT_FINALIZATION_CONFLICT',
290
+ message: pending ? 'commit finalization must complete before creating a pull request' : lifecycle.message,
291
+ disposition: lifecycle.disposition,
292
+ action: 'rerun-commit'
293
+ };
294
+ }
295
+
296
+ const review = findAuthoritativeReviewCodeArtifact(taskDir);
297
+ if (!review.ok || !review.path) {
298
+ return { allowed: true, code: null, message: 'no review-code history requires an anchor', disposition: 'idle', action: 'continue' };
299
+ }
300
+ try {
301
+ const content = fs.readFileSync(review.path, 'utf8');
302
+ const summary = parseReviewSummary(content);
303
+ if (!summary.ok || summary.summary.verdict !== 'Approved') {
304
+ return {
305
+ allowed: false,
306
+ code: 'COMMIT_FINALIZATION_EVIDENCE_INVALID',
307
+ message: 'latest authoritative review-code is not approved',
308
+ disposition: 'idle',
309
+ action: 'rerun-review-code'
310
+ };
311
+ }
312
+ const task = parseTypedTaskFrontmatter(fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8'));
313
+ const currentHead = git(repoRoot, ['rev-parse', 'HEAD']);
314
+ const anchor = String(task.last_reviewed_commit ?? '').trim();
315
+ if (!anchor || !commitExists(repoRoot, anchor)) {
316
+ return {
317
+ allowed: false,
318
+ code: 'COMMIT_FINALIZATION_EVIDENCE_MISSING',
319
+ message: 'approved review snapshot is not anchored to a Git commit',
320
+ disposition: 'idle',
321
+ action: 'rerun-commit'
322
+ };
323
+ }
324
+ if (anchor !== currentHead || extractReviewedSnapshotTree(content) !== git(repoRoot, ['rev-parse', 'HEAD^{tree}'])) {
325
+ return {
326
+ allowed: false,
327
+ code: 'COMMIT_FINALIZATION_CONFLICT',
328
+ message: 'approved review anchor or tree does not match repository HEAD',
329
+ disposition: 'idle',
330
+ action: 'rerun-review-code'
331
+ };
332
+ }
333
+ return { allowed: true, code: null, message: 'commit finalization evidence is complete', disposition: 'idle', action: 'continue' };
334
+ } catch (error) {
335
+ return {
336
+ allowed: false,
337
+ code: 'COMMIT_FINALIZATION_EVIDENCE_INVALID',
338
+ message: error instanceof Error ? error.message : String(error),
339
+ disposition: 'idle',
340
+ action: 'rerun-review-code'
341
+ };
342
+ }
343
+ }
344
+
345
+ export {
346
+ inspectCommitFinalization,
347
+ inspectCreatePrCommitGate,
348
+ planCommitTaskFinalization
349
+ };
350
+ export type {
351
+ CommitFinalizationCode,
352
+ CommitFinalizationDisposition,
353
+ CommitFinalizationInspection,
354
+ CommitTaskFinalizationPlan,
355
+ CreatePrCommitGate
356
+ };
@@ -0,0 +1,230 @@
1
+ import { createHash, randomBytes, randomUUID, timingSafeEqual } from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ import type { DelegationReceipt } from './delegation-receipts.ts';
6
+
7
+ type CommitIntentPhase = 'prepared' | 'committed' | 'pushed';
8
+ type PushEvidence = Readonly<{ remote: string; ref: string; head: string }>;
9
+ type CommitIntentOrchestration = Readonly<{
10
+ runId: string;
11
+ receiptId: string;
12
+ authorizationIssuedAt: string;
13
+ sourceRunDigest: string;
14
+ plannedRunDigest: string;
15
+ completionUpdatedAt: string;
16
+ plannedReceipt: DelegationReceipt;
17
+ }>;
18
+ type CommitIntent = Readonly<{
19
+ schemaVersion: 1;
20
+ taskId: string;
21
+ mode: 'standalone' | 'orchestrated';
22
+ phase: CommitIntentPhase;
23
+ tokenDigest: string;
24
+ baselineHead: string;
25
+ committedHead: string | null;
26
+ pushEvidence: PushEvidence | null;
27
+ orchestration: CommitIntentOrchestration | null;
28
+ createdAt: string;
29
+ updatedAt: string;
30
+ }>;
31
+ type CommitIntentInput = Omit<CommitIntent, 'schemaVersion' | 'tokenDigest'>;
32
+
33
+ class CommitIntentError extends Error {
34
+ readonly code: string;
35
+
36
+ constructor(code: string, message: string) {
37
+ super(message);
38
+ this.name = 'CommitIntentError';
39
+ this.code = code;
40
+ }
41
+ }
42
+
43
+ function commitIntentPath(taskDir: string): string {
44
+ return path.join(taskDir, 'commit-intent.json');
45
+ }
46
+
47
+ function digest(value: string | Buffer): string {
48
+ return createHash('sha256').update(value).digest('hex');
49
+ }
50
+
51
+ function serialize(value: unknown): string {
52
+ return `${JSON.stringify(value, null, 2)}\n`;
53
+ }
54
+
55
+ function atomicWrite(file: string, value: CommitIntent): void {
56
+ const temp = `${file}.${process.pid}.${randomUUID()}.tmp`;
57
+ try {
58
+ fs.writeFileSync(temp, serialize(value), { encoding: 'utf8', flag: 'wx', mode: 0o600 });
59
+ const handle = fs.openSync(temp, 'r+');
60
+ try {
61
+ fs.fsyncSync(handle);
62
+ } finally {
63
+ fs.closeSync(handle);
64
+ }
65
+ fs.renameSync(temp, file);
66
+ fs.chmodSync(file, 0o600);
67
+ try {
68
+ const directory = fs.openSync(path.dirname(file), 'r');
69
+ try {
70
+ fs.fsyncSync(directory);
71
+ } finally {
72
+ fs.closeSync(directory);
73
+ }
74
+ } catch (error) {
75
+ const code = (error as NodeJS.ErrnoException).code;
76
+ if (!['EACCES', 'EISDIR', 'EINVAL', 'EPERM'].includes(code ?? '')) throw error;
77
+ }
78
+ } finally {
79
+ if (fs.existsSync(temp)) fs.unlinkSync(temp);
80
+ }
81
+ }
82
+
83
+ function isRecord(value: unknown): value is Record<string, unknown> {
84
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
85
+ }
86
+
87
+ function keysEqual(value: Record<string, unknown>, expected: readonly string[]): boolean {
88
+ return Object.keys(value).sort().join('\0') === [...expected].sort().join('\0');
89
+ }
90
+
91
+ function validPushEvidence(value: unknown): value is PushEvidence | null {
92
+ return value === null || (
93
+ isRecord(value)
94
+ && keysEqual(value, ['remote', 'ref', 'head'])
95
+ && ['remote', 'ref', 'head'].every((key) => typeof value[key] === 'string' && value[key] !== '')
96
+ );
97
+ }
98
+
99
+ function validOrchestration(value: unknown): value is CommitIntentOrchestration | null {
100
+ if (value === null) return true;
101
+ if (!isRecord(value) || !keysEqual(value, [
102
+ 'runId', 'receiptId', 'authorizationIssuedAt', 'sourceRunDigest', 'plannedRunDigest',
103
+ 'completionUpdatedAt', 'plannedReceipt'
104
+ ])) return false;
105
+ return [
106
+ 'runId', 'receiptId', 'authorizationIssuedAt', 'sourceRunDigest', 'plannedRunDigest', 'completionUpdatedAt'
107
+ ].every((key) => typeof value[key] === 'string' && value[key] !== '') && isRecord(value.plannedReceipt);
108
+ }
109
+
110
+ function parseIntent(raw: string, taskId: string): CommitIntent {
111
+ let value: unknown;
112
+ try {
113
+ value = JSON.parse(raw);
114
+ } catch {
115
+ throw new CommitIntentError('ORCHESTRATION_COMMIT_INTENT_INVALID', 'commit intent is not valid JSON');
116
+ }
117
+ if (!isRecord(value) || !keysEqual(value, [
118
+ 'schemaVersion', 'taskId', 'mode', 'phase', 'tokenDigest', 'baselineHead', 'committedHead',
119
+ 'pushEvidence', 'orchestration', 'createdAt', 'updatedAt'
120
+ ])) {
121
+ throw new CommitIntentError('ORCHESTRATION_COMMIT_INTENT_INVALID', 'commit intent has an invalid schema');
122
+ }
123
+ if (
124
+ value.schemaVersion !== 1
125
+ || value.taskId !== taskId
126
+ || !['standalone', 'orchestrated'].includes(String(value.mode))
127
+ || !['prepared', 'committed', 'pushed'].includes(String(value.phase))
128
+ || typeof value.tokenDigest !== 'string'
129
+ || !/^[a-f0-9]{64}$/.test(value.tokenDigest)
130
+ || typeof value.baselineHead !== 'string'
131
+ || (value.committedHead !== null && typeof value.committedHead !== 'string')
132
+ || !validPushEvidence(value.pushEvidence)
133
+ || !validOrchestration(value.orchestration)
134
+ || typeof value.createdAt !== 'string'
135
+ || typeof value.updatedAt !== 'string'
136
+ || (value.mode === 'standalone') !== (value.orchestration === null)
137
+ ) {
138
+ throw new CommitIntentError('ORCHESTRATION_COMMIT_INTENT_INVALID', 'commit intent has invalid field values');
139
+ }
140
+ return value as CommitIntent;
141
+ }
142
+
143
+ function tokenMatches(actualDigest: string, token: string): boolean {
144
+ const actual = Buffer.from(actualDigest, 'hex');
145
+ const supplied = Buffer.from(digest(token), 'hex');
146
+ return actual.length === supplied.length && timingSafeEqual(actual, supplied);
147
+ }
148
+
149
+ function readCommitIntent(taskDir: string, taskId: string, token?: string): CommitIntent {
150
+ const file = commitIntentPath(taskDir);
151
+ const intent = parseIntent(fs.readFileSync(file, 'utf8'), taskId);
152
+ if (token !== undefined && !tokenMatches(intent.tokenDigest, token)) {
153
+ throw new CommitIntentError('ORCHESTRATION_COMMIT_INTENT_TOKEN_MISMATCH', 'commit intent token does not match');
154
+ }
155
+ return intent;
156
+ }
157
+
158
+ function createCommitIntent(
159
+ taskDir: string,
160
+ input: CommitIntentInput,
161
+ options: Readonly<{ token?: () => string }> = {}
162
+ ): Readonly<{ intent: CommitIntent; token: string }> {
163
+ const file = commitIntentPath(taskDir);
164
+ if (fs.existsSync(file)) {
165
+ throw new CommitIntentError('ORCHESTRATION_COMMIT_INTENT_BUSY', 'an active commit intent already exists');
166
+ }
167
+ const token = (options.token ?? (() => randomBytes(32).toString('base64url')))();
168
+ const intent: CommitIntent = Object.freeze({ schemaVersion: 1, ...input, tokenDigest: digest(token) });
169
+ atomicWrite(file, intent);
170
+ return { intent, token };
171
+ }
172
+
173
+ function updateCommitIntent(
174
+ taskDir: string,
175
+ taskId: string,
176
+ token: string,
177
+ updates: Partial<Pick<CommitIntent, 'phase' | 'committedHead' | 'pushEvidence' | 'updatedAt'>>
178
+ ): CommitIntent {
179
+ const current = readCommitIntent(taskDir, taskId, token);
180
+ const nextPhase = updates.phase ?? current.phase;
181
+ const transitions: Record<CommitIntentPhase, readonly CommitIntentPhase[]> = {
182
+ prepared: ['prepared', 'committed', 'pushed'],
183
+ committed: ['committed', 'pushed'],
184
+ pushed: ['pushed']
185
+ };
186
+ if (!transitions[current.phase].includes(nextPhase)) {
187
+ throw new CommitIntentError('ORCHESTRATION_COMMIT_INTENT_STATE_INVALID', `cannot move commit intent from ${current.phase} to ${nextPhase}`);
188
+ }
189
+ const updated = Object.freeze({ ...current, ...updates, phase: nextPhase });
190
+ atomicWrite(commitIntentPath(taskDir), updated);
191
+ return updated;
192
+ }
193
+
194
+ function removeCommitIntent(taskDir: string, taskId: string, token: string): void {
195
+ readCommitIntent(taskDir, taskId, token);
196
+ fs.unlinkSync(commitIntentPath(taskDir));
197
+ }
198
+
199
+ function removeCommitIntentByDigest(taskDir: string, taskId: string, expectedDigest: string): void {
200
+ const file = commitIntentPath(taskDir);
201
+ const raw = fs.readFileSync(file);
202
+ parseIntent(raw.toString('utf8'), taskId);
203
+ const actual = digest(raw);
204
+ const expected = Buffer.from(expectedDigest, 'hex');
205
+ const observed = Buffer.from(actual, 'hex');
206
+ if (
207
+ !/^[a-f0-9]{64}$/.test(expectedDigest)
208
+ || expected.length !== observed.length
209
+ || !timingSafeEqual(expected, observed)
210
+ ) {
211
+ throw new CommitIntentError(
212
+ 'ORCHESTRATION_COMMIT_RECOVERY_REQUIRED',
213
+ 'commit intent changed after recovery inspection'
214
+ );
215
+ }
216
+ fs.unlinkSync(file);
217
+ }
218
+
219
+ export {
220
+ CommitIntentError,
221
+ commitIntentPath,
222
+ createCommitIntent,
223
+ digest,
224
+ readCommitIntent,
225
+ removeCommitIntent,
226
+ removeCommitIntentByDigest,
227
+ serialize,
228
+ updateCommitIntent
229
+ };
230
+ export type { CommitIntent, CommitIntentInput, CommitIntentOrchestration, CommitIntentPhase, PushEvidence };
@@ -4,9 +4,11 @@ import { appendActivityEntry, locateActivityLog } from './activity-log.ts';
4
4
  import { isDecisionItem, listDecisionItems, selectDecisionItem } from './decision-items.ts';
5
5
  import {
6
6
  createImplementationInput,
7
+ finalizeImplementationInput,
7
8
  IMPLEMENTATION_INPUT_ALIASES,
8
9
  parseImplementationInputs,
9
- renderImplementationInputs
10
+ renderImplementationInputs,
11
+ selectDeclaredImplementationInput
10
12
  } from './implementation-inputs.ts';
11
13
  import { LEDGER_COLUMNS, LEDGER_HEADINGS, parseLedger } from './ledger.ts';
12
14
  import type { LedgerRow } from './ledger.ts';
@@ -73,7 +75,7 @@ function existingDecision(
73
75
  ? { recordId, implementationInputId: null }
74
76
  : { conflict: '--needs-implementation is only valid for code-stage decisions' };
75
77
  const inputs = parseImplementationInputs(content).rows.filter((input) => input.ledgerId === row.id && input.decisionEvidence === row.evidence);
76
- if (inputs.length !== 1 || inputs[0]!.needsImplementation !== needsImplementation) {
78
+ if (inputs.length !== 1 || (needsImplementation !== undefined && inputs[0]!.needsImplementation !== needsImplementation)) {
77
79
  return { conflict: `implementation intent for '${row.id}' conflicts with the existing decision` };
78
80
  }
79
81
  return { recordId, implementationInputId: inputs[0]!.id };
@@ -106,9 +108,28 @@ function applyHumanDecision(request: HumanDecisionRequest, options: TaskWriteOpt
106
108
  return failed('DECISION_TARGET_INVALID', message, resolved.taskId);
107
109
  }
108
110
  const row = selected.row;
109
- if (row.stage === 'code' && request.needsImplementation === undefined) return failed('DECISION_PAYLOAD_INVALID', 'code-stage decisions require --needs-implementation true|false', resolved.taskId, row.id);
110
111
  if (row.stage !== 'code' && request.needsImplementation !== undefined) return failed('DECISION_PAYLOAD_INVALID', '--needs-implementation is only valid for code-stage decisions', resolved.taskId, row.id);
111
112
 
113
+ let parsedInputs: ReturnType<typeof parseImplementationInputs> | null = null;
114
+ let declaredInput: ReturnType<typeof selectDeclaredImplementationInput> = null;
115
+ let effectiveNeedsImplementation = request.needsImplementation;
116
+ if (row.stage === 'code') {
117
+ try {
118
+ parsedInputs = parseImplementationInputs(content);
119
+ declaredInput = selectDeclaredImplementationInput(parsedInputs.rows, row.id, row.evidence);
120
+ } catch (error) {
121
+ return failed('DECISION_DOCUMENT_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId, row.id);
122
+ }
123
+ if (declaredInput) {
124
+ if (request.needsImplementation !== undefined && request.needsImplementation !== declaredInput.needsImplementation) {
125
+ return failed('DECISION_CONFLICT', `implementation intent for '${row.id}' conflicts with its declaration`, resolved.taskId, row.id);
126
+ }
127
+ effectiveNeedsImplementation = declaredInput.needsImplementation;
128
+ } else if (effectiveNeedsImplementation === undefined) {
129
+ return failed('DECISION_PAYLOAD_INVALID', 'code-stage decisions require --needs-implementation true|false when no implementation intent was declared', resolved.taskId, row.id);
130
+ }
131
+ }
132
+
112
133
  let metadata;
113
134
  try { metadata = (options.metadataProvider ?? captureTaskWriteMetadata)(); }
114
135
  catch (error) { return failed('METADATA_CAPTURE_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId, row.id); }
@@ -132,18 +153,17 @@ function applyHumanDecision(request: HumanDecisionRequest, options: TaskWriteOpt
132
153
  ];
133
154
  let implementationInputId: string | null = null;
134
155
  if (row.stage === 'code') {
135
- let parsed;
136
- try { parsed = parseImplementationInputs(content); }
137
- catch (error) { return failed('DECISION_DOCUMENT_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId, row.id); }
138
- const input = createImplementationInput(parsed.rows, {
139
- ledgerId: row.id, decisionEvidence: evidence,
140
- needsImplementation: request.needsImplementation!, decidedAt: metadata.timestamp
141
- });
142
- implementationInputId = input.id;
156
+ const nextInputs = declaredInput
157
+ ? finalizeImplementationInput(parsedInputs!.rows, declaredInput.id, evidence, metadata.timestamp)
158
+ : [...parsedInputs!.rows, createImplementationInput(parsedInputs!.rows, {
159
+ ledgerId: row.id, decisionEvidence: evidence,
160
+ needsImplementation: effectiveNeedsImplementation!, decidedAt: metadata.timestamp
161
+ })];
162
+ implementationInputId = declaredInput?.id ?? nextInputs[nextInputs.length - 1]!.id;
143
163
  mutations.push({
144
164
  kind: 'section', aliases: IMPLEMENTATION_INPUT_ALIASES,
145
165
  heading: findSectionHeading(content, [...IMPLEMENTATION_INPUT_ALIASES]),
146
- body: renderImplementationInputs([...parsed.rows, input])
166
+ body: renderImplementationInputs(nextInputs)
147
167
  });
148
168
  }
149
169
  mutations.push({