@fitlab-ai/agent-infra 0.9.0 → 0.9.2

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 (327) hide show
  1. package/README.md +22 -5
  2. package/README.zh-CN.md +21 -5
  3. package/bin/cli.ts +12 -0
  4. package/dist/bin/cli.js +14 -0
  5. package/dist/bin/internal-cli.js +20 -0
  6. package/dist/lib/agent-client.js +163 -0
  7. package/dist/lib/agent-clients/adapter.js +294 -0
  8. package/dist/lib/agent-clients/adapters/claude-code.js +62 -0
  9. package/dist/lib/agent-clients/adapters/codex-sandbox.js +210 -0
  10. package/dist/lib/agent-clients/adapters/codex.js +54 -0
  11. package/dist/lib/agent-clients/adapters/gemini-cli.js +64 -0
  12. package/dist/lib/agent-clients/adapters/index.js +20 -0
  13. package/dist/lib/agent-clients/adapters/opencode.js +64 -0
  14. package/dist/lib/agent-clients/config.js +177 -0
  15. package/dist/lib/agent-clients/custom-tuis.js +99 -0
  16. package/dist/lib/agent-clients/invocation.js +9 -0
  17. package/dist/lib/agent-clients/next-steps.js +38 -0
  18. package/dist/lib/agent-clients/project-assets.js +45 -0
  19. package/dist/lib/agent-clients/reconcile.js +235 -0
  20. package/dist/lib/agent-clients/registry.js +72 -0
  21. package/dist/lib/agent-clients/schema.js +57 -0
  22. package/dist/lib/agent-clients/types.js +29 -0
  23. package/dist/lib/builtin-tuis.js +9 -14
  24. package/dist/lib/defaults.json +85 -73
  25. package/dist/lib/git/review-snapshot.js +25 -10
  26. package/dist/lib/git/reviewed-commit-equivalence.js +110 -0
  27. package/dist/lib/init.js +47 -91
  28. package/dist/lib/internal/agent-client.js +117 -0
  29. package/dist/lib/internal/git-workflow.js +1 -1
  30. package/dist/lib/internal/platform-release-notes.js +53 -0
  31. package/dist/lib/internal/release-workflow.js +175 -17
  32. package/dist/lib/internal/task-orchestration.js +151 -0
  33. package/dist/lib/internal/task-review.js +65 -0
  34. package/dist/lib/merge.js +2 -1
  35. package/dist/lib/platform/adapters.js +38 -1
  36. package/dist/lib/platform/change-request-git-evidence.js +113 -0
  37. package/dist/lib/platform/github-release-notes.js +96 -0
  38. package/dist/lib/platform/issues.js +4 -2
  39. package/dist/lib/platform/merged-pr-equivalence.js +84 -0
  40. package/dist/lib/platform/pr-checks.js +43 -15
  41. package/dist/lib/platform/pull-requests.js +113 -7
  42. package/dist/lib/platform/release-notes.js +143 -0
  43. package/dist/lib/platform/releases.js +1 -1
  44. package/dist/lib/platform/verification-required.js +31 -9
  45. package/dist/lib/prompt.js +15 -3
  46. package/dist/lib/release/channels.js +4 -1
  47. package/dist/lib/release/workflow.js +4 -2
  48. package/dist/lib/remove-dir.js +25 -0
  49. package/dist/lib/run/tui.js +8 -2
  50. package/dist/lib/sandbox/agent-client-reconciler.js +234 -0
  51. package/dist/lib/sandbox/build-proxy.js +63 -0
  52. package/dist/lib/sandbox/clipboard/linux.js +2 -1
  53. package/dist/lib/sandbox/commands/create.js +133 -189
  54. package/dist/lib/sandbox/commands/enter.js +17 -1
  55. package/dist/lib/sandbox/commands/prune.js +2 -2
  56. package/dist/lib/sandbox/commands/rebuild.js +24 -7
  57. package/dist/lib/sandbox/commands/rm.js +3 -2
  58. package/dist/lib/sandbox/config.js +21 -4
  59. package/dist/lib/sandbox/constants.js +3 -0
  60. package/dist/lib/sandbox/dockerfile.js +2 -1
  61. package/dist/lib/sandbox/image-build.js +2 -1
  62. package/dist/lib/sandbox/managed-fs.js +10 -5
  63. package/dist/lib/sandbox/recovery.js +52 -36
  64. package/dist/lib/sandbox/runtimes/ai-tools.dockerfile +24 -3
  65. package/dist/lib/sandbox/runtimes/base.dockerfile +8 -1
  66. package/dist/lib/sandbox/runtimes/java17.dockerfile +8 -1
  67. package/dist/lib/sandbox/runtimes/java21.dockerfile +8 -1
  68. package/dist/lib/sandbox/runtimes/node20.dockerfile +8 -1
  69. package/dist/lib/sandbox/runtimes/node22.dockerfile +8 -1
  70. package/dist/lib/sandbox/runtimes/python3.dockerfile +8 -1
  71. package/dist/lib/sandbox/shell.js +3 -2
  72. package/dist/lib/sandbox/tool-types.js +8 -0
  73. package/dist/lib/sandbox/tools.js +2 -126
  74. package/dist/lib/task/artifact-lifecycle.js +1 -49
  75. package/dist/lib/task/commands/decisions.js +50 -20
  76. package/dist/lib/task/commands/issue-body.js +10 -5
  77. package/dist/lib/task/delegation-receipts.js +107 -0
  78. package/dist/lib/task/events.js +101 -2
  79. package/dist/lib/task/issue-form.js +2 -0
  80. package/dist/lib/task/orchestration.js +420 -0
  81. package/dist/lib/task/review-artifacts.js +143 -59
  82. package/dist/lib/task/review-finalization.js +166 -0
  83. package/dist/lib/task/review-fingerprint.js +13 -0
  84. package/dist/lib/task/verification-checks.js +2 -1
  85. package/dist/lib/task/verification-engine.js +168 -23
  86. package/dist/lib/task/verification-review.js +1 -1
  87. package/dist/lib/task/verification.js +4 -2
  88. package/dist/lib/task/workspace-snapshot.js +34 -0
  89. package/dist/lib/update.js +42 -161
  90. package/lib/agent-client.ts +214 -0
  91. package/lib/agent-clients/adapter.ts +461 -0
  92. package/lib/agent-clients/adapters/claude-code.ts +70 -0
  93. package/lib/agent-clients/adapters/codex-sandbox.ts +245 -0
  94. package/lib/agent-clients/adapters/codex.ts +58 -0
  95. package/lib/agent-clients/adapters/gemini-cli.ts +65 -0
  96. package/lib/agent-clients/adapters/index.ts +27 -0
  97. package/lib/agent-clients/adapters/opencode.ts +66 -0
  98. package/lib/agent-clients/config.ts +278 -0
  99. package/lib/agent-clients/custom-tuis.ts +149 -0
  100. package/lib/agent-clients/invocation.ts +19 -0
  101. package/lib/agent-clients/next-steps.ts +72 -0
  102. package/lib/agent-clients/project-assets.ts +96 -0
  103. package/lib/agent-clients/reconcile.ts +324 -0
  104. package/lib/agent-clients/registry.ts +144 -0
  105. package/lib/agent-clients/schema.ts +62 -0
  106. package/lib/agent-clients/types.ts +78 -0
  107. package/lib/benchmark/schemas/v1.0.0/case-manifest.schema.json +120 -0
  108. package/lib/benchmark/schemas/v1.0.0/common.schema.json +109 -0
  109. package/lib/benchmark/schemas/v1.0.0/grader-result.schema.json +160 -0
  110. package/lib/benchmark/schemas/v1.0.0/run-manifest.schema.json +122 -0
  111. package/lib/benchmark/schemas/v1.0.0/subject.schema.json +227 -0
  112. package/lib/builtin-tuis.ts +16 -15
  113. package/lib/defaults.json +24 -12
  114. package/lib/git/review-snapshot.ts +39 -9
  115. package/lib/git/reviewed-commit-equivalence.ts +135 -0
  116. package/lib/init.ts +53 -121
  117. package/lib/internal/agent-client.ts +131 -0
  118. package/lib/internal/git-workflow.ts +1 -1
  119. package/lib/internal/platform-release-notes.ts +55 -0
  120. package/lib/internal/release-workflow.ts +172 -18
  121. package/lib/internal/task-orchestration.ts +132 -0
  122. package/lib/internal/task-review.ts +65 -0
  123. package/lib/merge.ts +2 -1
  124. package/lib/platform/adapters.ts +142 -3
  125. package/lib/platform/change-request-git-evidence.ts +159 -0
  126. package/lib/platform/github-release-notes.ts +132 -0
  127. package/lib/platform/issues.ts +6 -4
  128. package/lib/platform/merged-pr-equivalence.ts +107 -0
  129. package/lib/platform/pr-checks.ts +57 -28
  130. package/lib/platform/pull-requests.ts +140 -25
  131. package/lib/platform/release-notes.ts +167 -0
  132. package/lib/platform/releases.ts +1 -1
  133. package/lib/platform/verification-required.ts +37 -9
  134. package/lib/prompt.ts +16 -3
  135. package/lib/release/channels.ts +4 -1
  136. package/lib/release/workflow.ts +4 -3
  137. package/lib/remove-dir.ts +28 -0
  138. package/lib/run/tui.ts +9 -1
  139. package/lib/sandbox/agent-client-reconciler.ts +378 -0
  140. package/lib/sandbox/build-proxy.ts +94 -0
  141. package/lib/sandbox/clipboard/linux.ts +2 -1
  142. package/lib/sandbox/commands/create.ts +188 -203
  143. package/lib/sandbox/commands/enter.ts +23 -1
  144. package/lib/sandbox/commands/prune.ts +2 -2
  145. package/lib/sandbox/commands/rebuild.ts +29 -7
  146. package/lib/sandbox/commands/rm.ts +3 -2
  147. package/lib/sandbox/config.ts +31 -4
  148. package/lib/sandbox/constants.ts +6 -0
  149. package/lib/sandbox/dockerfile.ts +2 -1
  150. package/lib/sandbox/image-build.ts +4 -1
  151. package/lib/sandbox/managed-fs.ts +18 -5
  152. package/lib/sandbox/recovery.ts +98 -67
  153. package/lib/sandbox/runtimes/ai-tools.dockerfile +24 -3
  154. package/lib/sandbox/runtimes/base.dockerfile +8 -1
  155. package/lib/sandbox/runtimes/java17.dockerfile +8 -1
  156. package/lib/sandbox/runtimes/java21.dockerfile +8 -1
  157. package/lib/sandbox/runtimes/node20.dockerfile +8 -1
  158. package/lib/sandbox/runtimes/node22.dockerfile +8 -1
  159. package/lib/sandbox/runtimes/python3.dockerfile +8 -1
  160. package/lib/sandbox/shell.ts +4 -2
  161. package/lib/sandbox/tool-types.ts +143 -0
  162. package/lib/sandbox/tools.ts +5 -157
  163. package/lib/task/artifact-lifecycle.ts +1 -38
  164. package/lib/task/commands/decisions.ts +53 -20
  165. package/lib/task/commands/issue-body.ts +10 -5
  166. package/lib/task/delegation-receipts.ts +170 -0
  167. package/lib/task/events.ts +111 -3
  168. package/lib/task/issue-form.ts +4 -0
  169. package/lib/task/orchestration.ts +543 -0
  170. package/lib/task/review-artifacts.ts +201 -69
  171. package/lib/task/review-finalization.ts +275 -0
  172. package/lib/task/review-fingerprint.ts +15 -1
  173. package/lib/task/verification-checks.ts +2 -1
  174. package/lib/task/verification-engine.ts +190 -25
  175. package/lib/task/verification-review.ts +2 -0
  176. package/lib/task/verification.ts +6 -3
  177. package/lib/task/workspace-snapshot.ts +36 -0
  178. package/lib/update.ts +48 -184
  179. package/package.json +2 -2
  180. package/templates/.agents/README.en.md +21 -20
  181. package/templates/.agents/README.zh-CN.md +21 -20
  182. package/templates/.agents/hooks/lifecycle-delegation.js +64 -0
  183. package/templates/.agents/rules/README.en.md +2 -0
  184. package/templates/.agents/rules/README.zh-CN.md +2 -0
  185. package/templates/.agents/rules/commit-and-pr.en.md +1 -0
  186. package/templates/.agents/rules/commit-and-pr.zh-CN.md +1 -0
  187. package/templates/.agents/rules/human-decision-context.en.md +14 -12
  188. package/templates/.agents/rules/human-decision-context.zh-CN.md +14 -12
  189. package/templates/.agents/rules/lifecycle-orchestration.en.md +21 -0
  190. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +21 -0
  191. package/templates/.agents/rules/next-step-output.en.md +16 -0
  192. package/templates/.agents/rules/next-step-output.zh-CN.md +16 -0
  193. package/templates/.agents/rules/no-mid-flow-questions.en.md +2 -0
  194. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +2 -0
  195. package/templates/.agents/rules/release-commands.en.md +2 -2
  196. package/templates/.agents/rules/release-commands.github.en.md +15 -39
  197. package/templates/.agents/rules/release-commands.github.zh-CN.md +14 -38
  198. package/templates/.agents/rules/release-commands.zh-CN.md +2 -2
  199. package/templates/.agents/rules/review-handshake.en.md +4 -3
  200. package/templates/.agents/rules/review-handshake.zh-CN.md +4 -3
  201. package/templates/.agents/rules/review-method.en.md +113 -0
  202. package/templates/.agents/rules/review-method.zh-CN.md +98 -0
  203. package/templates/.agents/rules/testing-discipline.en.md +20 -2
  204. package/templates/.agents/rules/testing-discipline.zh-CN.md +20 -2
  205. package/templates/.agents/skills/analyze-task/SKILL.en.md +9 -6
  206. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +9 -6
  207. package/templates/.agents/skills/block-task/SKILL.en.md +4 -4
  208. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +4 -4
  209. package/templates/.agents/skills/cancel-task/SKILL.en.md +4 -4
  210. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +5 -5
  211. package/templates/.agents/skills/check-task/SKILL.en.md +16 -16
  212. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -16
  213. package/templates/.agents/skills/close-codescan/SKILL.en.md +4 -4
  214. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +4 -4
  215. package/templates/.agents/skills/close-dependabot/SKILL.en.md +4 -4
  216. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +4 -4
  217. package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
  218. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -2
  219. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +14 -16
  220. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +14 -16
  221. package/templates/.agents/skills/code-task/reference/output-template.en.md +3 -3
  222. package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +3 -3
  223. package/templates/.agents/skills/commit/SKILL.en.md +11 -1
  224. package/templates/.agents/skills/commit/SKILL.zh-CN.md +11 -1
  225. package/templates/.agents/skills/commit/reference/task-status-update.en.md +24 -28
  226. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +24 -28
  227. package/templates/.agents/skills/complete-task/SKILL.en.md +3 -3
  228. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
  229. package/templates/.agents/skills/complete-task/config/verify.en.json +0 -1
  230. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +0 -1
  231. package/templates/.agents/skills/create-pr/SKILL.en.md +10 -10
  232. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +10 -10
  233. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +3 -3
  234. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +3 -3
  235. package/templates/.agents/skills/create-release-note/SKILL.en.md +17 -38
  236. package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +16 -37
  237. package/templates/.agents/skills/create-task/SKILL.en.md +15 -11
  238. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +15 -11
  239. package/templates/.agents/skills/create-task/reference/context-capture.en.md +29 -0
  240. package/templates/.agents/skills/create-task/reference/context-capture.zh-CN.md +29 -0
  241. package/templates/.agents/skills/import-codescan/SKILL.en.md +5 -5
  242. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +5 -5
  243. package/templates/.agents/skills/import-dependabot/SKILL.en.md +5 -5
  244. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +5 -5
  245. package/templates/.agents/skills/import-issue/SKILL.en.md +5 -5
  246. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -5
  247. package/templates/.agents/skills/init-labels/SKILL.en.md +4 -4
  248. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +4 -4
  249. package/templates/.agents/skills/init-milestones/SKILL.en.md +4 -4
  250. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +4 -4
  251. package/templates/.agents/skills/plan-task/SKILL.en.md +5 -5
  252. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +5 -5
  253. package/templates/.agents/skills/refine-title/SKILL.en.md +1 -1
  254. package/templates/.agents/skills/refine-title/SKILL.zh-CN.md +1 -1
  255. package/templates/.agents/skills/release/SKILL.en.md +1 -1
  256. package/templates/.agents/skills/review-analysis/SKILL.en.md +13 -2
  257. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +14 -3
  258. package/templates/.agents/skills/review-analysis/config/verify.en.json +6 -0
  259. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +6 -0
  260. package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +13 -15
  261. package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +13 -15
  262. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +51 -5
  263. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +52 -6
  264. package/templates/.agents/skills/review-analysis/reference/review-criteria.en.md +25 -22
  265. package/templates/.agents/skills/review-analysis/reference/review-criteria.zh-CN.md +25 -22
  266. package/templates/.agents/skills/review-code/SKILL.en.md +28 -8
  267. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +28 -8
  268. package/templates/.agents/skills/review-code/config/verify.en.json +7 -0
  269. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +7 -0
  270. package/templates/.agents/skills/review-code/reference/concurrency-risks.en.md +25 -0
  271. package/templates/.agents/skills/review-code/reference/concurrency-risks.zh-CN.md +25 -0
  272. package/templates/.agents/skills/review-code/reference/cross-platform-risks.en.md +27 -0
  273. package/templates/.agents/skills/review-code/reference/cross-platform-risks.zh-CN.md +27 -0
  274. package/templates/.agents/skills/review-code/reference/documentation-antipatterns.en.md +13 -0
  275. package/templates/.agents/skills/review-code/reference/documentation-antipatterns.zh-CN.md +13 -0
  276. package/templates/.agents/skills/review-code/reference/migration-risks.en.md +25 -0
  277. package/templates/.agents/skills/review-code/reference/migration-risks.zh-CN.md +25 -0
  278. package/templates/.agents/skills/review-code/reference/output-templates.en.md +22 -24
  279. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +22 -24
  280. package/templates/.agents/skills/review-code/reference/report-template.en.md +54 -7
  281. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +54 -7
  282. package/templates/.agents/skills/review-code/reference/review-criteria.en.md +29 -24
  283. package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +29 -24
  284. package/templates/.agents/skills/review-code/reference/security-risks.en.md +25 -0
  285. package/templates/.agents/skills/review-code/reference/security-risks.zh-CN.md +25 -0
  286. package/templates/.agents/skills/review-plan/SKILL.en.md +13 -2
  287. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +14 -3
  288. package/templates/.agents/skills/review-plan/config/verify.en.json +6 -0
  289. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +6 -0
  290. package/templates/.agents/skills/review-plan/reference/output-templates.en.md +13 -15
  291. package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +13 -15
  292. package/templates/.agents/skills/review-plan/reference/report-template.en.md +61 -5
  293. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +61 -5
  294. package/templates/.agents/skills/review-plan/reference/review-criteria.en.md +21 -24
  295. package/templates/.agents/skills/review-plan/reference/review-criteria.zh-CN.md +21 -24
  296. package/templates/.agents/skills/run-task/SKILL.en.md +22 -0
  297. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +22 -0
  298. package/templates/.agents/skills/run-task/config/verify.en.json +9 -0
  299. package/templates/.agents/skills/run-task/config/verify.zh-CN.json +9 -0
  300. package/templates/.agents/skills/test/SKILL.en.md +4 -4
  301. package/templates/.agents/skills/test/SKILL.zh-CN.md +4 -4
  302. package/templates/.agents/skills/test-integration/SKILL.en.md +4 -4
  303. package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +4 -4
  304. package/templates/.agents/skills/update-agent-infra/SKILL.en.md +5 -5
  305. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +5 -5
  306. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +647 -203
  307. package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +4 -4
  308. package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +4 -4
  309. package/templates/.agents/skills/watch-pr/SKILL.en.md +8 -8
  310. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +8 -8
  311. package/templates/.agents/templates/task.en.md +18 -0
  312. package/templates/.agents/templates/task.zh-CN.md +18 -0
  313. package/templates/.claude/agents/agent-infra-lifecycle-executor.en.md +6 -0
  314. package/templates/.claude/agents/agent-infra-lifecycle-executor.zh-CN.md +6 -0
  315. package/templates/.claude/agents/agent-infra-lifecycle-reviewer.en.md +6 -0
  316. package/templates/.claude/agents/agent-infra-lifecycle-reviewer.zh-CN.md +6 -0
  317. package/templates/.claude/commands/run-task.en.md +8 -0
  318. package/templates/.claude/commands/run-task.zh-CN.md +8 -0
  319. package/templates/.claude/settings.json +24 -0
  320. package/templates/.codex/agents/agent-infra-lifecycle-executor.en.toml +3 -0
  321. package/templates/.codex/agents/agent-infra-lifecycle-executor.zh-CN.toml +3 -0
  322. package/templates/.codex/agents/agent-infra-lifecycle-reviewer.en.toml +3 -0
  323. package/templates/.codex/agents/agent-infra-lifecycle-reviewer.zh-CN.toml +3 -0
  324. package/templates/.gemini/commands/_project_/run-task.en.toml +8 -0
  325. package/templates/.gemini/commands/_project_/run-task.zh-CN.toml +8 -0
  326. package/templates/.opencode/commands/run-task.en.md +11 -0
  327. package/templates/.opencode/commands/run-task.zh-CN.md +11 -0
@@ -0,0 +1,420 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { randomUUID } from 'node:crypto';
4
+ import { parseTypedTaskFrontmatter } from './frontmatter.js';
5
+ import { parseReviewSummary } from './review-artifacts.js';
6
+ import { inspectArtifactDirectory } from './artifact-lifecycle.js';
7
+ import { parseLedger, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
8
+ import { resolveTaskRef } from './resolve-ref.js';
9
+ import { activateDelegation, completeDelegationStage, consumeDelegation, managedDelegationRole, prepareDelegation, sealDelegation } from './delegation-receipts.js';
10
+ import { getAgentClientCapability } from '../agent-clients/registry.js';
11
+ import { captureWorkspaceSnapshot, diffWorkspaceSnapshots } from './workspace-snapshot.js';
12
+ function orchestrationPath(taskDir) {
13
+ return path.join(taskDir, 'orchestration.json');
14
+ }
15
+ function readRun(taskDir) {
16
+ const file = orchestrationPath(taskDir);
17
+ if (!fs.existsSync(file))
18
+ return null;
19
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
20
+ }
21
+ function atomicWrite(file, value) {
22
+ const temp = `${file}.${process.pid}.${randomUUID()}.tmp`;
23
+ fs.writeFileSync(temp, `${JSON.stringify(value, null, 2)}\n`, { encoding: 'utf8', flag: 'wx' });
24
+ fs.renameSync(temp, file);
25
+ }
26
+ function saveRun(taskDir, run) {
27
+ atomicWrite(orchestrationPath(taskDir), run);
28
+ }
29
+ function withUpdatedRun(run, updates) {
30
+ return Object.freeze({ ...run, ...updates, updatedAt: new Date().toISOString() });
31
+ }
32
+ function failed(code, message, taskId = null) {
33
+ return { status: 'failed', changed: false, taskId, run: null, next: null, error: { code, message } };
34
+ }
35
+ function beginOrResumeOrchestration(taskRef, options = {}) {
36
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
37
+ if (!resolved.ok)
38
+ return failed(resolved.code, resolved.message, resolved.taskId);
39
+ const existing = readRun(resolved.taskDir);
40
+ if (existing) {
41
+ if (existing.status === 'paused' && existing.pause?.recoverable && existing.pendingDelegation === null) {
42
+ const resumed = withUpdatedRun(existing, { status: 'running', pause: null });
43
+ saveRun(resolved.taskDir, resumed);
44
+ return { status: 'running', changed: true, taskId: resolved.taskId, run: resumed, next: null, error: null };
45
+ }
46
+ return { status: existing.status, changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
47
+ }
48
+ const now = (options.now ?? (() => new Date().toISOString()))();
49
+ const run = {
50
+ schemaVersion: 1,
51
+ taskId: resolved.taskId,
52
+ runId: (options.id ?? randomUUID)(),
53
+ status: 'running',
54
+ nextStage: null,
55
+ stepCount: 0,
56
+ maxSteps: options.maxSteps ?? 24,
57
+ baseline: '',
58
+ pendingDelegation: null,
59
+ receipts: [],
60
+ pause: null,
61
+ commitAuthorization: { issuedAt: null, consumedAt: null },
62
+ createdAt: now,
63
+ updatedAt: now
64
+ };
65
+ atomicWrite(orchestrationPath(resolved.taskDir), run);
66
+ return { status: 'running', changed: true, taskId: resolved.taskId, run, next: null, error: null };
67
+ }
68
+ function highestRound(taskDir, family) {
69
+ const pattern = new RegExp(`^${family}(?:-r(\\d+))?\\.md$`);
70
+ return fs.readdirSync(taskDir).reduce((max, name) => {
71
+ const match = pattern.exec(name);
72
+ return match ? Math.max(max, match[1] ? Number(match[1]) : 1) : max;
73
+ }, 0);
74
+ }
75
+ function artifactName(family, round) {
76
+ return round === 1 ? `${family}.md` : `${family}-r${round}.md`;
77
+ }
78
+ function hasReviewAfterArtifact(taskDir, family, reviewFamily) {
79
+ const artifact = inspectArtifactDirectory(taskDir, family);
80
+ const review = inspectArtifactDirectory(taskDir, reviewFamily);
81
+ return artifact.status === 'ready'
82
+ && review.status === 'ready'
83
+ && artifact.latest !== null
84
+ && review.reviewedInput?.name === artifact.latest.name;
85
+ }
86
+ function latestReviewApproved(taskDir, family) {
87
+ const round = highestRound(taskDir, family);
88
+ if (round === 0)
89
+ return false;
90
+ const parsed = parseReviewSummary(fs.readFileSync(path.join(taskDir, artifactName(family, round)), 'utf8'));
91
+ return parsed.ok && parsed.summary.verdict === 'Approved' && parsed.summary.counts !== null;
92
+ }
93
+ function routeFromFacts(taskDir) {
94
+ const analysisRound = highestRound(taskDir, 'analysis');
95
+ const analysisReviewRound = highestRound(taskDir, 'review-analysis');
96
+ const planRound = highestRound(taskDir, 'plan');
97
+ const planReviewRound = highestRound(taskDir, 'review-plan');
98
+ const codeRound = highestRound(taskDir, 'code');
99
+ const codeReviewRound = highestRound(taskDir, 'review-code');
100
+ if (analysisRound === 0) {
101
+ const round = analysisRound + 1;
102
+ return { action: 'analyze-task', role: 'executor', stage: 'analysis', round, artifact: artifactName('analysis', round) };
103
+ }
104
+ if (!hasReviewAfterArtifact(taskDir, 'analysis', 'review-analysis')) {
105
+ const round = analysisReviewRound + 1;
106
+ return { action: 'review-analysis', role: 'reviewer', stage: 'review-analysis', round, artifact: artifactName('review-analysis', round) };
107
+ }
108
+ if (!latestReviewApproved(taskDir, 'review-analysis')) {
109
+ const round = analysisRound + 1;
110
+ return { action: 'analyze-task', role: 'executor', stage: 'analysis', round, artifact: artifactName('analysis', round) };
111
+ }
112
+ if (planRound === 0) {
113
+ const round = planRound + 1;
114
+ return { action: 'plan-task', role: 'executor', stage: 'plan', round, artifact: artifactName('plan', round) };
115
+ }
116
+ if (!hasReviewAfterArtifact(taskDir, 'plan', 'review-plan')) {
117
+ const round = planReviewRound + 1;
118
+ return { action: 'review-plan', role: 'reviewer', stage: 'review-plan', round, artifact: artifactName('review-plan', round) };
119
+ }
120
+ if (!latestReviewApproved(taskDir, 'review-plan')) {
121
+ const round = planRound + 1;
122
+ return { action: 'plan-task', role: 'executor', stage: 'plan', round, artifact: artifactName('plan', round) };
123
+ }
124
+ if (codeRound === 0) {
125
+ const round = codeRound + 1;
126
+ return { action: 'code-task', role: 'executor', stage: 'code', round, artifact: artifactName('code', round) };
127
+ }
128
+ if (!hasReviewAfterArtifact(taskDir, 'code', 'review-code')) {
129
+ const round = codeReviewRound + 1;
130
+ return { action: 'review-code', role: 'reviewer', stage: 'review-code', round, artifact: artifactName('review-code', round) };
131
+ }
132
+ if (!latestReviewApproved(taskDir, 'review-code')) {
133
+ const round = codeRound + 1;
134
+ return { action: 'code-task', role: 'executor', stage: 'code', round, artifact: artifactName('code', round) };
135
+ }
136
+ return { action: 'commit', role: 'executor', stage: 'commit', round: 1, artifact: 'commit' };
137
+ }
138
+ function routeOrchestration(taskRef, options = {}) {
139
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
140
+ if (!resolved.ok)
141
+ return failed(resolved.code, resolved.message, resolved.taskId);
142
+ let content;
143
+ try {
144
+ content = fs.readFileSync(resolved.taskMdPath, 'utf8');
145
+ }
146
+ catch (error) {
147
+ return failed('ORCHESTRATION_TASK_READ_FAILED', String(error), resolved.taskId);
148
+ }
149
+ try {
150
+ parseTypedTaskFrontmatter(content);
151
+ }
152
+ catch (error) {
153
+ return failed('ORCHESTRATION_TASK_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
154
+ }
155
+ const next = routeFromFacts(resolved.taskDir);
156
+ if (!next)
157
+ return failed('ORCHESTRATION_ROUTE_UNKNOWN', 'cannot determine a unique lifecycle action', resolved.taskId);
158
+ if (next.stage === 'commit') {
159
+ const reviewRound = highestRound(resolved.taskDir, 'review-code');
160
+ const review = parseReviewSummary(fs.readFileSync(path.join(resolved.taskDir, artifactName('review-code', reviewRound)), 'utf8'));
161
+ if (!review.ok || review.summary.manualValidation === null) {
162
+ return failed('ORCHESTRATION_REVIEW_INVALID', 'latest code review has no numeric manual-validation count', resolved.taskId);
163
+ }
164
+ if (review.summary.manualValidation > 0) {
165
+ return failed('ORCHESTRATION_MANUAL_VALIDATION_PENDING', 'manual validation must be completed before commit', resolved.taskId);
166
+ }
167
+ const rows = parseLedger(content);
168
+ const ledgerError = validateLedgerRows(rows);
169
+ if (ledgerError)
170
+ return failed('ORCHESTRATION_LEDGER_INVALID', ledgerError.message, resolved.taskId);
171
+ if (!summarizeLedgerStage(rows, 'code').canAdvance) {
172
+ return failed('ORCHESTRATION_LEDGER_BLOCKED', 'code review ledger has unresolved findings or human decisions', resolved.taskId);
173
+ }
174
+ }
175
+ return { status: 'running', changed: false, taskId: resolved.taskId, run: readRun(resolved.taskDir), next, error: null };
176
+ }
177
+ function statusOrchestration(taskRef, options = {}) {
178
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
179
+ if (!resolved.ok)
180
+ return failed(resolved.code, resolved.message, resolved.taskId);
181
+ const run = readRun(resolved.taskDir);
182
+ if (!run)
183
+ return failed('ORCHESTRATION_RUN_MISSING', 'no orchestration run exists', resolved.taskId);
184
+ return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
185
+ }
186
+ function prepareOrchestrationDelegation(taskRef, input, options = {}) {
187
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
188
+ if (!resolved.ok)
189
+ return failed(resolved.code, resolved.message, resolved.taskId);
190
+ if (getAgentClientCapability(input.client, 'subagents').level === 'unsupported'
191
+ || getAgentClientCapability(input.client, 'orchestration').level === 'unsupported') {
192
+ return failed('ORCHESTRATION_CLIENT_UNSUPPORTED', `client '${input.client}' does not support lifecycle orchestration`, resolved.taskId);
193
+ }
194
+ const run = readRun(resolved.taskDir);
195
+ if (!run || run.status !== 'running')
196
+ return failed('ORCHESTRATION_RUN_NOT_RUNNING', 'a running orchestration is required', resolved.taskId);
197
+ if (run.pendingDelegation)
198
+ return failed('ORCHESTRATION_DELEGATION_BUSY', 'the run already has a pending delegation', resolved.taskId);
199
+ const repositoryPending = ['claude-code', 'codex']
200
+ .flatMap((client) => matchingDelegations(client, () => true, options));
201
+ if (repositoryPending.length > 0) {
202
+ return failed('ORCHESTRATION_DELEGATION_BUSY', 'the repository already has a pending lifecycle delegation', resolved.taskId);
203
+ }
204
+ if (run.stepCount >= run.maxSteps)
205
+ return pauseOrchestration(taskRef, 'ORCHESTRATION_MAX_STEPS', 'maximum orchestration steps reached', true, options);
206
+ const routed = routeOrchestration(taskRef, options);
207
+ if (!routed.next)
208
+ return routed;
209
+ const next = routed.next;
210
+ let beforeFingerprint;
211
+ try {
212
+ beforeFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)(resolved.repoRoot);
213
+ }
214
+ catch (error) {
215
+ return failed('ORCHESTRATION_SNAPSHOT_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
216
+ }
217
+ const receipt = prepareDelegation({
218
+ taskId: resolved.taskId,
219
+ runId: run.runId,
220
+ role: next.role,
221
+ stage: next.stage,
222
+ round: next.round,
223
+ artifact: next.artifact,
224
+ client: input.client,
225
+ requestedModel: input.requestedModel ?? null,
226
+ beforeFingerprint
227
+ }, { id: options.id, now: options.now });
228
+ const updated = withUpdatedRun(run, {
229
+ nextStage: next.stage,
230
+ pendingDelegation: receipt,
231
+ commitAuthorization: next.stage === 'commit' && run.commitAuthorization.issuedAt === null
232
+ ? { issuedAt: (options.now ?? (() => new Date().toISOString()))(), consumedAt: null }
233
+ : run.commitAuthorization
234
+ });
235
+ saveRun(resolved.taskDir, updated);
236
+ return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next, error: null };
237
+ }
238
+ function matchingDelegations(client, predicate, options) {
239
+ const repoRoot = options.repoRoot ?? process.cwd();
240
+ const activeRoot = path.join(repoRoot, '.agents', 'workspace', 'active');
241
+ if (!fs.existsSync(activeRoot))
242
+ return [];
243
+ return fs.readdirSync(activeRoot, { withFileTypes: true })
244
+ .filter((entry) => entry.isDirectory())
245
+ .flatMap((entry) => {
246
+ const run = readRun(path.join(activeRoot, entry.name));
247
+ if (!run?.pendingDelegation)
248
+ return [];
249
+ const receipt = run.pendingDelegation;
250
+ return receipt.client === client && predicate(receipt)
251
+ ? [{ taskId: entry.name, run }]
252
+ : [];
253
+ });
254
+ }
255
+ function uniqueMatchingDelegation(client, predicate, options) {
256
+ const matches = matchingDelegations(client, predicate, options);
257
+ if (matches.length === 0)
258
+ return failed('ORCHESTRATION_DELEGATION_MISSING', 'no matching lifecycle delegation exists');
259
+ if (matches.length > 1)
260
+ return failed('ORCHESTRATION_DELEGATION_AMBIGUOUS', 'multiple lifecycle delegations match the native hook event');
261
+ return matches[0];
262
+ }
263
+ function activateMatchingOrchestrationDelegation(client, event, options = {}) {
264
+ const role = managedDelegationRole(event.nativeAgent);
265
+ if (!role)
266
+ return failed('DELEGATION_IGNORED', `subagent '${event.nativeAgent}' is not lifecycle-managed`);
267
+ const matched = uniqueMatchingDelegation(client, (receipt) => receipt.status === 'prepared', options);
268
+ if ('status' in matched)
269
+ return matched;
270
+ if (matched.run.receipts.some((receipt) => receipt.childId === event.childId)) {
271
+ return pauseOrchestration(matched.taskId, 'DELEGATION_IDENTITY_REUSED', 'native child identity was already used by this run', true, options);
272
+ }
273
+ return activateOrchestrationDelegation(matched.taskId, event, options);
274
+ }
275
+ function sealMatchingOrchestrationDelegation(client, event, options = {}) {
276
+ const role = managedDelegationRole(event.nativeAgent);
277
+ if (!role)
278
+ return failed('DELEGATION_IGNORED', `subagent '${event.nativeAgent}' is not lifecycle-managed`);
279
+ const matched = uniqueMatchingDelegation(client, (receipt) => receipt.status !== 'prepared', options);
280
+ if ('status' in matched)
281
+ return matched;
282
+ const receipt = matched.run.pendingDelegation;
283
+ if (receipt.role !== role) {
284
+ return pauseOrchestration(matched.taskId, 'DELEGATION_ROLE_MISMATCH', `managed role ${role} does not match ${receipt.role}`, true, options);
285
+ }
286
+ const repoRoot = options.repoRoot ?? process.cwd();
287
+ try {
288
+ const afterFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)(repoRoot);
289
+ const changedPaths = (options.diffWorkspace ?? diffWorkspaceSnapshots)(repoRoot, receipt.beforeFingerprint, afterFingerprint);
290
+ return sealOrchestrationDelegation(matched.taskId, {
291
+ childId: event.childId,
292
+ exitCode: 0,
293
+ afterFingerprint,
294
+ changedPaths
295
+ }, options);
296
+ }
297
+ catch (error) {
298
+ return pauseOrchestration(matched.taskId, 'ORCHESTRATION_SNAPSHOT_FAILED', error instanceof Error ? error.message : String(error), true, options);
299
+ }
300
+ }
301
+ function activateOrchestrationDelegation(taskRef, event, options = {}) {
302
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
303
+ if (!resolved.ok)
304
+ return failed(resolved.code, resolved.message, resolved.taskId);
305
+ const run = readRun(resolved.taskDir);
306
+ if (!run?.pendingDelegation)
307
+ return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
308
+ const result = activateDelegation(run.pendingDelegation, event, { now: options.now });
309
+ if (!result.ok) {
310
+ if (result.code === 'DELEGATION_IGNORED')
311
+ return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
312
+ return pauseOrchestration(taskRef, result.code, result.message, true, options);
313
+ }
314
+ const updated = withUpdatedRun(run, { pendingDelegation: result.receipt });
315
+ saveRun(resolved.taskDir, updated);
316
+ return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
317
+ }
318
+ function completeOrchestrationStage(taskRef, event, options = {}) {
319
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
320
+ if (!resolved.ok)
321
+ return failed(resolved.code, resolved.message, resolved.taskId);
322
+ const run = readRun(resolved.taskDir);
323
+ if (!run)
324
+ return { status: 'running', changed: false, taskId: resolved.taskId, run: null, next: null, error: null };
325
+ if (!run.pendingDelegation)
326
+ return failed('ORCHESTRATION_DELEGATION_MISSING', 'active run has no pending delegation', resolved.taskId);
327
+ const result = completeDelegationStage(run.pendingDelegation, event);
328
+ if (!result.ok)
329
+ return pauseOrchestration(taskRef, result.code, result.message, true, options);
330
+ const updated = withUpdatedRun(run, { pendingDelegation: result.receipt });
331
+ saveRun(resolved.taskDir, updated);
332
+ return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
333
+ }
334
+ function completeCommitOrchestrationStage(taskRef, agent, options = {}) {
335
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
336
+ if (!resolved.ok)
337
+ return failed(resolved.code, resolved.message, resolved.taskId);
338
+ const run = readRun(resolved.taskDir);
339
+ if (!run)
340
+ return { status: 'running', changed: false, taskId: resolved.taskId, run: null, next: null, error: null };
341
+ const receipt = run.pendingDelegation;
342
+ if (!receipt || receipt.stage !== 'commit') {
343
+ return failed('ORCHESTRATION_DELEGATION_MISSING', 'active run has no pending commit delegation', resolved.taskId);
344
+ }
345
+ return completeOrchestrationStage(taskRef, {
346
+ stage: receipt.stage,
347
+ round: receipt.round,
348
+ artifact: receipt.artifact,
349
+ agent
350
+ }, options);
351
+ }
352
+ function validateOrchestrationStage(taskRef, event, options = {}) {
353
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
354
+ if (!resolved.ok)
355
+ return failed(resolved.code, resolved.message, resolved.taskId);
356
+ const run = readRun(resolved.taskDir);
357
+ if (!run)
358
+ return { status: 'running', changed: false, taskId: resolved.taskId, run: null, next: null, error: null };
359
+ const receipt = run.pendingDelegation;
360
+ if (!receipt
361
+ || receipt.status !== 'activated'
362
+ || receipt.stage !== event.stage
363
+ || receipt.round !== event.round
364
+ || receipt.artifact !== event.artifact
365
+ || receipt.role !== event.role) {
366
+ return pauseOrchestration(taskRef, 'ORCHESTRATION_PROVENANCE_MISMATCH', 'active run does not have one matching activated delegation', true, options);
367
+ }
368
+ return { status: 'running', changed: false, taskId: resolved.taskId, run, next: null, error: null };
369
+ }
370
+ function sealOrchestrationDelegation(taskRef, event, options = {}) {
371
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
372
+ if (!resolved.ok)
373
+ return failed(resolved.code, resolved.message, resolved.taskId);
374
+ const run = readRun(resolved.taskDir);
375
+ if (!run?.pendingDelegation)
376
+ return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
377
+ const result = sealDelegation(run.pendingDelegation, event, { now: options.now });
378
+ if (!result.ok)
379
+ return pauseOrchestration(taskRef, result.code, result.message, true, options);
380
+ const updated = withUpdatedRun(run, { pendingDelegation: result.receipt });
381
+ saveRun(resolved.taskDir, updated);
382
+ return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
383
+ }
384
+ function advanceOrchestration(taskRef, options = {}) {
385
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
386
+ if (!resolved.ok)
387
+ return failed(resolved.code, resolved.message, resolved.taskId);
388
+ const run = readRun(resolved.taskDir);
389
+ if (!run?.pendingDelegation)
390
+ return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
391
+ const result = consumeDelegation(run.pendingDelegation, { now: options.now });
392
+ if (!result.ok)
393
+ return pauseOrchestration(taskRef, result.code, result.message, true, options);
394
+ const completed = result.receipt.stage === 'commit';
395
+ const updated = withUpdatedRun(run, {
396
+ status: completed ? 'completed' : 'running',
397
+ nextStage: null,
398
+ stepCount: run.stepCount + 1,
399
+ pendingDelegation: null,
400
+ receipts: Object.freeze([...run.receipts, result.receipt]),
401
+ commitAuthorization: completed
402
+ ? { ...run.commitAuthorization, consumedAt: (options.now ?? (() => new Date().toISOString()))() }
403
+ : run.commitAuthorization
404
+ });
405
+ saveRun(resolved.taskDir, updated);
406
+ return { status: updated.status, changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
407
+ }
408
+ function pauseOrchestration(taskRef, code, message, recoverable, options = {}) {
409
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
410
+ if (!resolved.ok)
411
+ return failed(resolved.code, resolved.message, resolved.taskId);
412
+ const run = readRun(resolved.taskDir);
413
+ if (!run)
414
+ return failed('ORCHESTRATION_RUN_MISSING', 'no orchestration run exists', resolved.taskId);
415
+ const updated = withUpdatedRun(run, { status: 'paused', pause: { code, message, recoverable } });
416
+ saveRun(resolved.taskDir, updated);
417
+ return { status: 'paused', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
418
+ }
419
+ export { activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, advanceOrchestration, beginOrResumeOrchestration, completeCommitOrchestrationStage, completeOrchestrationStage, orchestrationPath, pauseOrchestration, prepareOrchestrationDelegation, readRun, routeOrchestration, sealMatchingOrchestrationDelegation, sealOrchestrationDelegation, statusOrchestration, validateOrchestrationStage };
420
+ //# sourceMappingURL=orchestration.js.map
@@ -1,13 +1,15 @@
1
- // Shared helpers for review-artifact parsing.
2
- // Imported by typed artifact lifecycle and verification so the round/verdict vocabulary stays
3
- // in a single source of truth (prevents the cross-file drift this lifecycle
4
- // is designed to eliminate).
5
- import fs from "node:fs";
6
- import path from "node:path";
7
- export function escapeRegExp(value) {
8
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ const SUMMARY_HEADINGS = new Set(['审查摘要', 'Review Summary']);
4
+ const PLACEHOLDERS = [
5
+ '{unresolved-blockers}',
6
+ '{unresolved-major}',
7
+ '{unresolved-minor}'
8
+ ];
9
+ function escapeRegExp(value) {
10
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
9
11
  }
10
- export function maxRound(entries, stem) {
12
+ function maxRound(entries, stem) {
11
13
  let max = 0;
12
14
  for (const entry of entries) {
13
15
  if (entry === `${stem}.md`) {
@@ -15,87 +17,169 @@ export function maxRound(entries, stem) {
15
17
  continue;
16
18
  }
17
19
  const match = entry.match(new RegExp(`^${escapeRegExp(stem)}-r(\\d+)\\.md$`));
18
- if (match) {
20
+ if (match)
19
21
  max = Math.max(max, Number(match[1]));
20
- }
21
22
  }
22
23
  return max;
23
24
  }
24
- export function artifactName(stem, round) {
25
+ function artifactName(stem, round) {
25
26
  return round === 1 ? `${stem}.md` : `${stem}-r${round}.md`;
26
27
  }
27
- export function normalizeVerdict(raw) {
28
+ function normalizeVerdict(raw) {
28
29
  const value = String(raw).trim().toLowerCase();
29
- if (value === "通过" || value === "approved") {
30
- return "Approved";
31
- }
32
- if (value === "需要修改" || value === "changes requested") {
33
- return "Changes Requested";
34
- }
35
- if (value === "拒绝" || value === "rejected") {
36
- return "Rejected";
37
- }
38
- return "";
30
+ if (value === '通过' || value === 'approved')
31
+ return 'Approved';
32
+ if (value === '需要修改' || value === 'changes requested')
33
+ return 'Changes Requested';
34
+ if (value === '拒绝' || value === 'rejected')
35
+ return 'Rejected';
36
+ return '';
39
37
  }
40
- export function extractSection(content, names) {
38
+ function extractSection(content, names) {
41
39
  const lines = content.split(/\r?\n/);
42
40
  const nameSet = new Set(names);
43
41
  const start = lines.findIndex((line) => {
44
42
  const match = line.trim().match(/^##\s+(.+?)\s*$/);
45
43
  return match ? nameSet.has(match[1]) : false;
46
44
  });
47
- if (start === -1) {
48
- return "";
49
- }
45
+ if (start === -1)
46
+ return '';
50
47
  const sectionLines = [];
51
48
  for (let index = start + 1; index < lines.length; index += 1) {
52
- if (/^##\s+/.test(lines[index])) {
49
+ if (/^##\s+/.test(lines[index]))
53
50
  break;
54
- }
55
51
  sectionLines.push(lines[index]);
56
52
  }
57
- return sectionLines.join("\n");
53
+ return sectionLines.join('\n');
58
54
  }
59
- // Parse the canonical verdict out of a review-* artifact.
60
- // Returns { ok, verdict, message }. Verdict collapses Approved into
61
- // "Approved-with-issues" when the findings counts are non-zero.
62
- export function parseVerdict(reviewPath) {
63
- if (!fs.existsSync(reviewPath)) {
64
- return { ok: false, verdict: null, message: `Review artifact not found: ${path.basename(reviewPath)}` };
55
+ function summaryBounds(content) {
56
+ const headings = [...content.matchAll(/^##\s+(.+?)\s*$/gm)]
57
+ .map((match) => ({ index: match.index, end: match.index + match[0].length, name: match[1] }));
58
+ const summaries = headings.filter((heading) => SUMMARY_HEADINGS.has(heading.name));
59
+ if (summaries.length !== 1)
60
+ return null;
61
+ const summary = summaries[0];
62
+ const next = headings.find((heading) => heading.index > summary.index);
63
+ return { start: summary.end, end: next?.index ?? content.length };
64
+ }
65
+ function uniqueLine(content, bounds, pattern) {
66
+ const section = content.slice(bounds.start, bounds.end);
67
+ const matches = [...section.matchAll(pattern)];
68
+ if (matches.length !== 1)
69
+ return null;
70
+ const match = matches[0];
71
+ return {
72
+ value: match[1],
73
+ start: bounds.start + match.index,
74
+ end: bounds.start + match.index + match[0].length
75
+ };
76
+ }
77
+ function parseCounts(value) {
78
+ const zh = /^(\d+)\s*阻塞项,\s*(\d+)\s*主要,\s*(\d+)\s*次要(?:\s*\/.*)?$/.exec(value);
79
+ const en = /^(\d+)\s*blockers?,\s*(\d+)\s*majors?,\s*(\d+)\s*minors?(?:\s*\/.*)?$/i.exec(value);
80
+ const match = zh ?? en;
81
+ if (!match)
82
+ return null;
83
+ const [blocker, major, minor] = match.slice(1, 4).map(Number);
84
+ if (![blocker, major, minor].every(Number.isSafeInteger))
85
+ return null;
86
+ return { blocker: blocker, major: major, minor: minor };
87
+ }
88
+ function parseManualValidation(value) {
89
+ const match = /\*\*(?:人工校验|Manual[- ]validation)\*\*[::]\s*(\d+)/i.exec(value);
90
+ if (!match)
91
+ return null;
92
+ const count = Number(match[1]);
93
+ return Number.isSafeInteger(count) ? count : null;
94
+ }
95
+ function hasCanonicalPlaceholders(value) {
96
+ const zh = /^\{unresolved-blockers\}\s*阻塞项,\s*\{unresolved-major\}\s*主要,\s*\{unresolved-minor\}\s*次要(?:\s*\/.*)?$/.test(value);
97
+ const en = /^\{unresolved-blockers\}\s*blockers?,\s*\{unresolved-major\}\s*majors?,\s*\{unresolved-minor\}\s*minors?(?:\s*\/.*)?$/i.test(value);
98
+ return zh || en;
99
+ }
100
+ function parseReviewSummary(content) {
101
+ const bounds = summaryBounds(content);
102
+ if (!bounds) {
103
+ return { ok: false, code: 'REVIEW_SUMMARY_NOT_FOUND', message: 'review artifact must contain one review summary section' };
65
104
  }
66
- const content = fs.readFileSync(reviewPath, "utf8");
67
- const summary = extractSection(content, ["审查摘要", "Review Summary"]);
68
- const fileName = path.basename(reviewPath);
69
- if (!summary) {
70
- return { ok: false, verdict: null, message: `cannot locate review summary section in ${fileName}` };
105
+ const verdictLine = uniqueLine(content, bounds, /^[-*]?\s*\*\*(?:总体结论|Overall Verdict)\*\*[::]\s*(.+?)\s*$/gim);
106
+ const findingsLine = uniqueLine(content, bounds, /^[-*]?\s*\*\*(?:发现(AI 可处理)|Findings \(AI-actionable\))\*\*[::]\s*(.+?)\s*$/gim);
107
+ const verdict = verdictLine ? normalizeVerdict(verdictLine.value) : '';
108
+ if (!verdict || !findingsLine) {
109
+ return { ok: false, code: 'REVIEW_SUMMARY_NOT_FOUND', message: 'review summary verdict or finding count line is missing or repeated' };
71
110
  }
72
- const verdictMatch = summary.match(/^[-*]?\s*\*\*(?:总体结论|Overall Verdict)\*\*[::]\s*(.+?)\s*$/im);
73
- if (!verdictMatch) {
74
- return { ok: false, verdict: null, message: `cannot parse verdict in ${fileName}` };
111
+ const counts = parseCounts(findingsLine.value);
112
+ if (counts) {
113
+ return {
114
+ ok: true,
115
+ summary: { verdict, counts, manualValidation: parseManualValidation(findingsLine.value), countState: 'numeric' },
116
+ findingsStart: findingsLine.start,
117
+ findingsEnd: findingsLine.end
118
+ };
75
119
  }
76
- const verdict = normalizeVerdict(verdictMatch[1]);
77
- if (!verdict) {
120
+ const placeholderOccurrences = PLACEHOLDERS.map((token) => ([...findingsLine.value.matchAll(new RegExp(escapeRegExp(token), 'g'))].length));
121
+ if (hasCanonicalPlaceholders(findingsLine.value) && placeholderOccurrences.every((count) => count === 1)) {
78
122
  return {
79
- ok: false,
80
- verdict: null,
81
- message: `unrecognized verdict '${verdictMatch[1].trim()}' in ${fileName}`
123
+ ok: true,
124
+ summary: { verdict, counts: null, manualValidation: parseManualValidation(findingsLine.value), countState: 'placeholders' },
125
+ findingsStart: findingsLine.start,
126
+ findingsEnd: findingsLine.end
82
127
  };
83
128
  }
84
- if (verdict !== "Approved") {
85
- return { ok: true, verdict };
129
+ return {
130
+ ok: false,
131
+ code: 'REVIEW_SUMMARY_PLACEHOLDER_INVALID',
132
+ message: 'review summary finding counts must be three canonical placeholders or non-negative integers'
133
+ };
134
+ }
135
+ function equalCounts(left, right) {
136
+ return left.blocker === right.blocker && left.major === right.major && left.minor === right.minor;
137
+ }
138
+ function finalizeReviewSummaryContent(content, counts) {
139
+ const parsed = parseReviewSummary(content);
140
+ if (!parsed.ok)
141
+ return parsed;
142
+ if (parsed.summary.countState === 'numeric') {
143
+ if (equalCounts(parsed.summary.counts, counts))
144
+ return { ok: true, changed: false, content };
145
+ return {
146
+ ok: false,
147
+ code: 'REVIEW_SUMMARY_COUNT_MISMATCH',
148
+ message: 'review summary finding counts do not match the ledger snapshot'
149
+ };
86
150
  }
87
- const findingsMatch = summary.match(/^[-*]?\s*\*\*(?:发现(AI 可处理)|Findings \(AI-actionable\))\*\*[::]\s*(.+?)\s*$/im);
88
- if (!findingsMatch) {
89
- return { ok: false, verdict, message: `cannot parse findings count in ${fileName}` };
151
+ let line = content.slice(parsed.findingsStart, parsed.findingsEnd);
152
+ line = line
153
+ .replace(PLACEHOLDERS[0], String(counts.blocker))
154
+ .replace(PLACEHOLDERS[1], String(counts.major))
155
+ .replace(PLACEHOLDERS[2], String(counts.minor));
156
+ return {
157
+ ok: true,
158
+ changed: true,
159
+ content: content.slice(0, parsed.findingsStart) + line + content.slice(parsed.findingsEnd)
160
+ };
161
+ }
162
+ function parseVerdict(reviewPath) {
163
+ if (!fs.existsSync(reviewPath)) {
164
+ return { ok: false, verdict: null, message: `Review artifact not found: ${path.basename(reviewPath)}` };
90
165
  }
91
- const counts = findingsMatch[1].match(/(\d+)\s*(?:阻塞项|blockers?).*?(\d+)\s*(?:主要|majors?).*?(\d+)\s*(?:次要|minors?)/i);
92
- if (!counts) {
93
- return { ok: false, verdict, message: `cannot parse findings count in ${fileName}` };
166
+ const parsed = parseReviewSummary(fs.readFileSync(reviewPath, 'utf8'));
167
+ if (!parsed.ok) {
168
+ return {
169
+ ok: false,
170
+ verdict: null,
171
+ message: `cannot parse review summary in ${path.basename(reviewPath)}: ${parsed.message}`
172
+ };
94
173
  }
95
- const [, blockers, majors, minors] = counts.map(Number);
174
+ const { verdict, counts } = parsed.summary;
175
+ if (verdict !== 'Approved')
176
+ return { ok: true, verdict };
96
177
  return {
97
178
  ok: true,
98
- verdict: blockers === 0 && majors === 0 && minors === 0 ? "Approved" : "Approved-with-issues"
179
+ verdict: counts && equalCounts(counts, { blocker: 0, major: 0, minor: 0 })
180
+ ? 'Approved'
181
+ : 'Approved-with-issues'
99
182
  };
100
183
  }
184
+ export { artifactName, equalCounts, escapeRegExp, extractSection, finalizeReviewSummaryContent, maxRound, normalizeVerdict, parseReviewSummary, parseVerdict };
101
185
  //# sourceMappingURL=review-artifacts.js.map