@fitlab-ai/agent-infra 0.9.1 → 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 (283) hide show
  1. package/README.md +16 -4
  2. package/README.zh-CN.md +16 -4
  3. package/bin/cli.ts +12 -0
  4. package/dist/bin/cli.js +14 -0
  5. package/dist/bin/internal-cli.js +15 -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/release-workflow.js +71 -16
  31. package/dist/lib/internal/task-orchestration.js +151 -0
  32. package/dist/lib/internal/task-review.js +65 -0
  33. package/dist/lib/merge.js +2 -1
  34. package/dist/lib/platform/adapters.js +38 -1
  35. package/dist/lib/platform/change-request-git-evidence.js +113 -0
  36. package/dist/lib/platform/merged-pr-equivalence.js +41 -11
  37. package/dist/lib/platform/pr-checks.js +38 -10
  38. package/dist/lib/platform/pull-requests.js +110 -6
  39. package/dist/lib/platform/releases.js +1 -1
  40. package/dist/lib/platform/verification-required.js +8 -10
  41. package/dist/lib/prompt.js +15 -3
  42. package/dist/lib/release/channels.js +4 -1
  43. package/dist/lib/release/workflow.js +4 -2
  44. package/dist/lib/remove-dir.js +25 -0
  45. package/dist/lib/run/tui.js +8 -2
  46. package/dist/lib/sandbox/agent-client-reconciler.js +234 -0
  47. package/dist/lib/sandbox/clipboard/linux.js +2 -1
  48. package/dist/lib/sandbox/commands/create.js +76 -180
  49. package/dist/lib/sandbox/commands/enter.js +17 -1
  50. package/dist/lib/sandbox/commands/prune.js +2 -2
  51. package/dist/lib/sandbox/commands/rm.js +3 -2
  52. package/dist/lib/sandbox/config.js +21 -4
  53. package/dist/lib/sandbox/constants.js +3 -0
  54. package/dist/lib/sandbox/dockerfile.js +2 -1
  55. package/dist/lib/sandbox/managed-fs.js +10 -5
  56. package/dist/lib/sandbox/recovery.js +52 -36
  57. package/dist/lib/sandbox/shell.js +1 -1
  58. package/dist/lib/sandbox/tool-types.js +8 -0
  59. package/dist/lib/sandbox/tools.js +2 -126
  60. package/dist/lib/task/artifact-lifecycle.js +1 -49
  61. package/dist/lib/task/commands/decisions.js +50 -20
  62. package/dist/lib/task/delegation-receipts.js +107 -0
  63. package/dist/lib/task/events.js +100 -1
  64. package/dist/lib/task/orchestration.js +420 -0
  65. package/dist/lib/task/review-artifacts.js +143 -59
  66. package/dist/lib/task/review-finalization.js +166 -0
  67. package/dist/lib/task/review-fingerprint.js +4 -0
  68. package/dist/lib/task/verification-checks.js +2 -1
  69. package/dist/lib/task/verification-engine.js +156 -28
  70. package/dist/lib/task/verification-review.js +1 -1
  71. package/dist/lib/task/verification.js +3 -1
  72. package/dist/lib/task/workspace-snapshot.js +34 -0
  73. package/dist/lib/update.js +42 -161
  74. package/lib/agent-client.ts +214 -0
  75. package/lib/agent-clients/adapter.ts +461 -0
  76. package/lib/agent-clients/adapters/claude-code.ts +70 -0
  77. package/lib/agent-clients/adapters/codex-sandbox.ts +245 -0
  78. package/lib/agent-clients/adapters/codex.ts +58 -0
  79. package/lib/agent-clients/adapters/gemini-cli.ts +65 -0
  80. package/lib/agent-clients/adapters/index.ts +27 -0
  81. package/lib/agent-clients/adapters/opencode.ts +66 -0
  82. package/lib/agent-clients/config.ts +278 -0
  83. package/lib/agent-clients/custom-tuis.ts +149 -0
  84. package/lib/agent-clients/invocation.ts +19 -0
  85. package/lib/agent-clients/next-steps.ts +72 -0
  86. package/lib/agent-clients/project-assets.ts +96 -0
  87. package/lib/agent-clients/reconcile.ts +324 -0
  88. package/lib/agent-clients/registry.ts +144 -0
  89. package/lib/agent-clients/schema.ts +62 -0
  90. package/lib/agent-clients/types.ts +78 -0
  91. package/lib/benchmark/schemas/v1.0.0/case-manifest.schema.json +120 -0
  92. package/lib/benchmark/schemas/v1.0.0/common.schema.json +109 -0
  93. package/lib/benchmark/schemas/v1.0.0/grader-result.schema.json +160 -0
  94. package/lib/benchmark/schemas/v1.0.0/run-manifest.schema.json +122 -0
  95. package/lib/benchmark/schemas/v1.0.0/subject.schema.json +227 -0
  96. package/lib/builtin-tuis.ts +16 -15
  97. package/lib/defaults.json +24 -12
  98. package/lib/git/review-snapshot.ts +39 -9
  99. package/lib/git/reviewed-commit-equivalence.ts +135 -0
  100. package/lib/init.ts +53 -121
  101. package/lib/internal/agent-client.ts +131 -0
  102. package/lib/internal/git-workflow.ts +1 -1
  103. package/lib/internal/release-workflow.ts +68 -16
  104. package/lib/internal/task-orchestration.ts +132 -0
  105. package/lib/internal/task-review.ts +65 -0
  106. package/lib/merge.ts +2 -1
  107. package/lib/platform/adapters.ts +142 -3
  108. package/lib/platform/change-request-git-evidence.ts +159 -0
  109. package/lib/platform/merged-pr-equivalence.ts +51 -14
  110. package/lib/platform/pr-checks.ts +50 -22
  111. package/lib/platform/pull-requests.ts +134 -25
  112. package/lib/platform/releases.ts +1 -1
  113. package/lib/platform/verification-required.ts +12 -10
  114. package/lib/prompt.ts +16 -3
  115. package/lib/release/channels.ts +4 -1
  116. package/lib/release/workflow.ts +4 -3
  117. package/lib/remove-dir.ts +28 -0
  118. package/lib/run/tui.ts +9 -1
  119. package/lib/sandbox/agent-client-reconciler.ts +378 -0
  120. package/lib/sandbox/clipboard/linux.ts +2 -1
  121. package/lib/sandbox/commands/create.ts +111 -192
  122. package/lib/sandbox/commands/enter.ts +23 -1
  123. package/lib/sandbox/commands/prune.ts +2 -2
  124. package/lib/sandbox/commands/rm.ts +3 -2
  125. package/lib/sandbox/config.ts +31 -4
  126. package/lib/sandbox/constants.ts +6 -0
  127. package/lib/sandbox/dockerfile.ts +2 -1
  128. package/lib/sandbox/managed-fs.ts +18 -5
  129. package/lib/sandbox/recovery.ts +98 -67
  130. package/lib/sandbox/shell.ts +1 -1
  131. package/lib/sandbox/tool-types.ts +143 -0
  132. package/lib/sandbox/tools.ts +5 -157
  133. package/lib/task/artifact-lifecycle.ts +1 -38
  134. package/lib/task/commands/decisions.ts +53 -20
  135. package/lib/task/delegation-receipts.ts +170 -0
  136. package/lib/task/events.ts +110 -2
  137. package/lib/task/orchestration.ts +543 -0
  138. package/lib/task/review-artifacts.ts +201 -69
  139. package/lib/task/review-finalization.ts +275 -0
  140. package/lib/task/review-fingerprint.ts +5 -0
  141. package/lib/task/verification-checks.ts +2 -1
  142. package/lib/task/verification-engine.ts +177 -28
  143. package/lib/task/verification-review.ts +1 -0
  144. package/lib/task/verification.ts +5 -2
  145. package/lib/task/workspace-snapshot.ts +36 -0
  146. package/lib/update.ts +48 -184
  147. package/package.json +2 -2
  148. package/templates/.agents/README.en.md +21 -20
  149. package/templates/.agents/README.zh-CN.md +21 -20
  150. package/templates/.agents/hooks/lifecycle-delegation.js +64 -0
  151. package/templates/.agents/rules/README.en.md +2 -0
  152. package/templates/.agents/rules/README.zh-CN.md +2 -0
  153. package/templates/.agents/rules/commit-and-pr.en.md +1 -0
  154. package/templates/.agents/rules/commit-and-pr.zh-CN.md +1 -0
  155. package/templates/.agents/rules/human-decision-context.en.md +14 -12
  156. package/templates/.agents/rules/human-decision-context.zh-CN.md +14 -12
  157. package/templates/.agents/rules/lifecycle-orchestration.en.md +21 -0
  158. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +21 -0
  159. package/templates/.agents/rules/next-step-output.en.md +16 -0
  160. package/templates/.agents/rules/next-step-output.zh-CN.md +16 -0
  161. package/templates/.agents/rules/no-mid-flow-questions.en.md +2 -0
  162. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +2 -0
  163. package/templates/.agents/rules/release-commands.github.en.md +11 -5
  164. package/templates/.agents/rules/release-commands.github.zh-CN.md +11 -5
  165. package/templates/.agents/rules/review-handshake.en.md +4 -4
  166. package/templates/.agents/rules/review-handshake.zh-CN.md +4 -4
  167. package/templates/.agents/rules/review-method.en.md +113 -0
  168. package/templates/.agents/rules/review-method.zh-CN.md +98 -0
  169. package/templates/.agents/skills/analyze-task/SKILL.en.md +5 -5
  170. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +5 -5
  171. package/templates/.agents/skills/block-task/SKILL.en.md +4 -4
  172. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +4 -4
  173. package/templates/.agents/skills/cancel-task/SKILL.en.md +4 -4
  174. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +5 -5
  175. package/templates/.agents/skills/check-task/SKILL.en.md +16 -16
  176. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -16
  177. package/templates/.agents/skills/close-codescan/SKILL.en.md +4 -4
  178. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +4 -4
  179. package/templates/.agents/skills/close-dependabot/SKILL.en.md +4 -4
  180. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +4 -4
  181. package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
  182. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -2
  183. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +14 -16
  184. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +14 -16
  185. package/templates/.agents/skills/code-task/reference/output-template.en.md +3 -3
  186. package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +3 -3
  187. package/templates/.agents/skills/commit/SKILL.en.md +11 -1
  188. package/templates/.agents/skills/commit/SKILL.zh-CN.md +11 -1
  189. package/templates/.agents/skills/commit/reference/task-status-update.en.md +24 -28
  190. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +24 -28
  191. package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
  192. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
  193. package/templates/.agents/skills/create-pr/SKILL.en.md +10 -10
  194. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +10 -10
  195. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +3 -3
  196. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +3 -3
  197. package/templates/.agents/skills/create-task/SKILL.en.md +11 -11
  198. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +11 -11
  199. package/templates/.agents/skills/import-codescan/SKILL.en.md +5 -5
  200. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +5 -5
  201. package/templates/.agents/skills/import-dependabot/SKILL.en.md +5 -5
  202. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +5 -5
  203. package/templates/.agents/skills/import-issue/SKILL.en.md +5 -5
  204. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -5
  205. package/templates/.agents/skills/init-labels/SKILL.en.md +4 -4
  206. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +4 -4
  207. package/templates/.agents/skills/init-milestones/SKILL.en.md +4 -4
  208. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +4 -4
  209. package/templates/.agents/skills/plan-task/SKILL.en.md +5 -5
  210. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +5 -5
  211. package/templates/.agents/skills/refine-title/SKILL.en.md +1 -1
  212. package/templates/.agents/skills/refine-title/SKILL.zh-CN.md +1 -1
  213. package/templates/.agents/skills/release/SKILL.en.md +1 -1
  214. package/templates/.agents/skills/review-analysis/SKILL.en.md +13 -2
  215. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +14 -3
  216. package/templates/.agents/skills/review-analysis/config/verify.en.json +6 -0
  217. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +6 -0
  218. package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +13 -15
  219. package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +13 -15
  220. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +51 -5
  221. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +52 -6
  222. package/templates/.agents/skills/review-analysis/reference/review-criteria.en.md +25 -22
  223. package/templates/.agents/skills/review-analysis/reference/review-criteria.zh-CN.md +25 -22
  224. package/templates/.agents/skills/review-code/SKILL.en.md +28 -8
  225. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +28 -8
  226. package/templates/.agents/skills/review-code/config/verify.en.json +7 -0
  227. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +7 -0
  228. package/templates/.agents/skills/review-code/reference/concurrency-risks.en.md +25 -0
  229. package/templates/.agents/skills/review-code/reference/concurrency-risks.zh-CN.md +25 -0
  230. package/templates/.agents/skills/review-code/reference/cross-platform-risks.en.md +27 -0
  231. package/templates/.agents/skills/review-code/reference/cross-platform-risks.zh-CN.md +27 -0
  232. package/templates/.agents/skills/review-code/reference/documentation-antipatterns.en.md +13 -0
  233. package/templates/.agents/skills/review-code/reference/documentation-antipatterns.zh-CN.md +13 -0
  234. package/templates/.agents/skills/review-code/reference/migration-risks.en.md +25 -0
  235. package/templates/.agents/skills/review-code/reference/migration-risks.zh-CN.md +25 -0
  236. package/templates/.agents/skills/review-code/reference/output-templates.en.md +22 -24
  237. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +22 -24
  238. package/templates/.agents/skills/review-code/reference/report-template.en.md +54 -7
  239. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +54 -7
  240. package/templates/.agents/skills/review-code/reference/review-criteria.en.md +29 -24
  241. package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +29 -24
  242. package/templates/.agents/skills/review-code/reference/security-risks.en.md +25 -0
  243. package/templates/.agents/skills/review-code/reference/security-risks.zh-CN.md +25 -0
  244. package/templates/.agents/skills/review-plan/SKILL.en.md +13 -2
  245. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +14 -3
  246. package/templates/.agents/skills/review-plan/config/verify.en.json +6 -0
  247. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +6 -0
  248. package/templates/.agents/skills/review-plan/reference/output-templates.en.md +13 -15
  249. package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +13 -15
  250. package/templates/.agents/skills/review-plan/reference/report-template.en.md +61 -5
  251. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +61 -5
  252. package/templates/.agents/skills/review-plan/reference/review-criteria.en.md +21 -24
  253. package/templates/.agents/skills/review-plan/reference/review-criteria.zh-CN.md +21 -24
  254. package/templates/.agents/skills/run-task/SKILL.en.md +22 -0
  255. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +22 -0
  256. package/templates/.agents/skills/run-task/config/verify.en.json +9 -0
  257. package/templates/.agents/skills/run-task/config/verify.zh-CN.json +9 -0
  258. package/templates/.agents/skills/test/SKILL.en.md +4 -4
  259. package/templates/.agents/skills/test/SKILL.zh-CN.md +4 -4
  260. package/templates/.agents/skills/test-integration/SKILL.en.md +4 -4
  261. package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +4 -4
  262. package/templates/.agents/skills/update-agent-infra/SKILL.en.md +5 -5
  263. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +5 -5
  264. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +647 -203
  265. package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +4 -4
  266. package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +4 -4
  267. package/templates/.agents/skills/watch-pr/SKILL.en.md +8 -8
  268. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +8 -8
  269. package/templates/.claude/agents/agent-infra-lifecycle-executor.en.md +6 -0
  270. package/templates/.claude/agents/agent-infra-lifecycle-executor.zh-CN.md +6 -0
  271. package/templates/.claude/agents/agent-infra-lifecycle-reviewer.en.md +6 -0
  272. package/templates/.claude/agents/agent-infra-lifecycle-reviewer.zh-CN.md +6 -0
  273. package/templates/.claude/commands/run-task.en.md +8 -0
  274. package/templates/.claude/commands/run-task.zh-CN.md +8 -0
  275. package/templates/.claude/settings.json +24 -0
  276. package/templates/.codex/agents/agent-infra-lifecycle-executor.en.toml +3 -0
  277. package/templates/.codex/agents/agent-infra-lifecycle-executor.zh-CN.toml +3 -0
  278. package/templates/.codex/agents/agent-infra-lifecycle-reviewer.en.toml +3 -0
  279. package/templates/.codex/agents/agent-infra-lifecycle-reviewer.zh-CN.toml +3 -0
  280. package/templates/.gemini/commands/_project_/run-task.en.toml +8 -0
  281. package/templates/.gemini/commands/_project_/run-task.zh-CN.toml +8 -0
  282. package/templates/.opencode/commands/run-task.en.md +11 -0
  283. package/templates/.opencode/commands/run-task.zh-CN.md +11 -0
@@ -139,10 +139,58 @@ function runOptionalDemo(cwd, run = command) {
139
139
  writeDigestAtomically(cwd, digest);
140
140
  return { status: 'recorded', reasonCode: null, message: null, outputPath };
141
141
  }
142
- function git(cwd, args) {
143
- const result = command(cwd, 'git', args);
142
+ function git(cwd, args, run = command) {
143
+ const result = run(cwd, 'git', args);
144
144
  return result.status === 0 ? String(result.stdout).trim() : null;
145
145
  }
146
+ function inspectLocalReleaseFacts(cwd, version, run = command) {
147
+ const tag = `v${version}`;
148
+ const head = git(cwd, ['rev-parse', 'HEAD'], run);
149
+ const tagCommit = git(cwd, ['rev-parse', '--verify', `${tag}^{commit}`], run);
150
+ const localTag = Boolean(tagCommit && tagCommit === head);
151
+ const localTagAncestor = Boolean(tagCommit && head && !localTag
152
+ && run(cwd, 'git', ['merge-base', '--is-ancestor', tagCommit, head]).status === 0);
153
+ const localTagConflict = Boolean(tagCommit && !localTag && !localTagAncestor);
154
+ const postSubject = `chore: prepare next dev iteration after v${version}`;
155
+ const postLog = localTag || localTagAncestor
156
+ ? git(cwd, ['log', '--format=%s', `${tag}..HEAD`], run) ?? ''
157
+ : '';
158
+ const postCommit = postLog.split('\n').some((subject) => subject === postSubject);
159
+ return { localTag, localTagAncestor, localTagConflict, postCommit };
160
+ }
161
+ function releaseSmokeStatus(workflows, version, tagCommit) {
162
+ const manualTitle = `Post-Release Smoke v${version}`;
163
+ const targets = workflows.filter((run) => {
164
+ if (String(run.workflowName || run.name) !== 'Post-Release Smoke')
165
+ return false;
166
+ if (String(run.event) === 'workflow_run')
167
+ return Boolean(tagCommit && String(run.headSha) === tagCommit);
168
+ return String(run.event) === 'workflow_dispatch' && String(run.displayTitle) === manualTitle;
169
+ }).sort((left, right) => {
170
+ const leftOrder = [Date.parse(String(left.createdAt || '')) || 0, Number(left.databaseId) || 0, Number(left.attempt) || 0];
171
+ const rightOrder = [Date.parse(String(right.createdAt || '')) || 0, Number(right.databaseId) || 0, Number(right.attempt) || 0];
172
+ for (let index = 0; index < leftOrder.length; index += 1) {
173
+ if (leftOrder[index] !== rightOrder[index])
174
+ return rightOrder[index] - leftOrder[index];
175
+ }
176
+ return 0;
177
+ });
178
+ const latest = targets[0];
179
+ if (!latest)
180
+ return null;
181
+ if (String(latest.status) !== 'completed')
182
+ return 'pending';
183
+ return String(latest.conclusion) === 'success' ? 'success' : 'failed';
184
+ }
185
+ function inspectPostWorktree(cwd) {
186
+ const inspected = inspectGitWorkflow(cwd);
187
+ if (!inspected.snapshot)
188
+ return inspected.error ?? { code: 'GIT_INSPECT_FAILED', message: 'Unable to inspect Git repository' };
189
+ if (inspected.snapshot.worktree.length || inspected.snapshot.staged.length) {
190
+ return { code: 'WORKTREE_DIRTY', message: 'Release post requires a clean worktree' };
191
+ }
192
+ return null;
193
+ }
146
194
  async function inspectFacts(cwd, version) {
147
195
  const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
148
196
  const config = JSON.parse(fs.readFileSync(path.join(cwd, '.agents', '.airc.json'), 'utf8'));
@@ -150,6 +198,7 @@ async function inspectFacts(cwd, version) {
150
198
  const branch = git(cwd, ['branch', '--show-current']) || '';
151
199
  const head = git(cwd, ['rev-parse', 'HEAD']);
152
200
  const localTagTarget = git(cwd, ['rev-parse', '--verify', `${tag}^{commit}`]);
201
+ const local = inspectLocalReleaseFacts(cwd, version);
153
202
  const remoteBranch = branch ? git(cwd, ['ls-remote', '--heads', 'origin', branch]) : null;
154
203
  const remoteTag = git(cwd, ['ls-remote', '--tags', 'origin', `refs/tags/${tag}`]);
155
204
  const platform = inspectPlatformRelease(tag, { cwd });
@@ -157,18 +206,14 @@ async function inspectFacts(cwd, version) {
157
206
  const formulaUrl = `https://raw.githubusercontent.com/${config.org}/homebrew-tap/main/Formula/${config.project}.rb`;
158
207
  const homebrew = await inspectHomebrewChannel(formulaUrl, version);
159
208
  const workflows = platform.workflows;
160
- const smokeRun = workflows.find((run) => String(run.name).toLowerCase().includes('post-release-smoke') && String(run.headBranch) === tag);
161
- const smoke = smokeRun ? String(smokeRun.conclusion) === 'success' ? 'success' : String(smokeRun.status) === 'completed' ? 'failed' : 'pending' : null;
162
- const postMessage = git(cwd, ['log', '-1', '--pretty=%s']) || '';
209
+ const smoke = releaseSmokeStatus(workflows, version, localTagTarget);
163
210
  return {
164
- localTag: Boolean(localTagTarget && localTagTarget === head),
165
- localTagConflict: Boolean(localTagTarget && localTagTarget !== head),
211
+ ...local,
166
212
  remoteBranch: Boolean(remoteBranch && remoteBranch.split(/\s+/)[0] === head), remoteTag: Boolean(remoteTag),
167
213
  githubRelease: platform.platform.type !== 'github'
168
214
  ? true
169
215
  : platform.status === 'blocked' ? null : Boolean(platform.release?.published),
170
- npm: npm.published, homebrew: homebrew.published, smoke,
171
- postCommit: postMessage.includes(`after v${version}`)
216
+ npm: npm.published, homebrew: homebrew.published, smoke
172
217
  };
173
218
  }
174
219
  function changedPaths(cwd) {
@@ -208,12 +253,12 @@ async function releaseWorkflow(args = []) {
208
253
  process.stdout.write(`${JSON.stringify({ status: 'no-op', changed: false, snapshot: before, error: null })}\n`);
209
254
  return;
210
255
  }
256
+ if (before.facts.localTagConflict) {
257
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, snapshot: before, error: { code: 'GIT_TAG_CONFLICT', message: `Tag v${version} is not reachable from HEAD` } })}\n`);
258
+ process.exitCode = 1;
259
+ return;
260
+ }
211
261
  if (action === 'prepare') {
212
- if (before.facts.localTagConflict) {
213
- process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, snapshot: before, error: { code: 'GIT_TAG_CONFLICT', message: `Tag v${version} does not point to HEAD` } })}\n`);
214
- process.exitCode = 1;
215
- return;
216
- }
217
262
  if (before.phase !== 'unprepared') {
218
263
  const milestones = reconcileReleaseMilestones(version, { cwd });
219
264
  process.stdout.write(`${JSON.stringify({ ...milestones, snapshot: before })}\n`);
@@ -267,7 +312,7 @@ async function releaseWorkflow(args = []) {
267
312
  return;
268
313
  }
269
314
  if (action === 'publish') {
270
- if (!['prepared', 'partially-published'].includes(before.phase)) {
315
+ if (!before.facts.localTag || !['prepared', 'partially-published'].includes(before.phase)) {
271
316
  process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, snapshot: before, error: { code: 'RELEASE_PHASE_INVALID', message: `Cannot publish from ${before.phase}` } })}\n`);
272
317
  process.exitCode = 1;
273
318
  return;
@@ -279,12 +324,22 @@ async function releaseWorkflow(args = []) {
279
324
  process.exitCode = result.status === 'failed' ? 1 : result.status === 'degraded' ? 2 : 0;
280
325
  return;
281
326
  }
327
+ if (before.phase === 'complete') {
328
+ process.stdout.write(`${JSON.stringify({ status: 'no-op', changed: false, snapshot: before, error: null })}\n`);
329
+ return;
330
+ }
282
331
  const channelsComplete = before.facts.githubRelease === true && before.facts.npm === true && before.facts.homebrew === true;
283
332
  if (!['published', 'post-pending'].includes(before.phase) || !channelsComplete || before.facts.smoke !== 'success') {
284
333
  process.stdout.write(`${JSON.stringify({ status: before.facts.smoke === 'failed' ? 'failed' : 'blocked', changed: false, snapshot: before, error: { code: 'RELEASE_CHANNELS_PENDING', message: 'Release channels or smoke workflow are not complete' } })}\n`);
285
334
  process.exitCode = before.facts.smoke === 'failed' ? 1 : 2;
286
335
  return;
287
336
  }
337
+ const worktreeError = inspectPostWorktree(cwd);
338
+ if (worktreeError) {
339
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, snapshot: before, error: worktreeError })}\n`);
340
+ process.exitCode = 1;
341
+ return;
342
+ }
288
343
  const built = command(cwd, 'npm', ['run', 'build']);
289
344
  if (built.status !== 0) {
290
345
  process.stdout.write(`${JSON.stringify({ status: 'failed', changed: true, snapshot: before, error: { code: 'RELEASE_POST_COMMAND_FAILED', message: String(built.stderr || built.stdout) } })}\n`);
@@ -316,5 +371,5 @@ async function releaseWorkflow(args = []) {
316
371
  process.stdout.write(`${JSON.stringify({ ...pushed, demo, snapshot: releaseSnapshot(version, await inspectFacts(cwd, version)) })}\n`);
317
372
  process.exitCode = pushed.status === 'failed' ? 1 : pushed.status === 'degraded' ? 2 : 0;
318
373
  }
319
- export { computeDemoInputDigest, inspectFacts, releaseWorkflow, runOptionalDemo };
374
+ export { computeDemoInputDigest, inspectFacts, inspectLocalReleaseFacts, inspectPostWorktree, releaseSmokeStatus, releaseWorkflow, runOptionalDemo };
320
375
  //# sourceMappingURL=release-workflow.js.map
@@ -0,0 +1,151 @@
1
+ import { activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, advanceOrchestration, beginOrResumeOrchestration, completeCommitOrchestrationStage, pauseOrchestration, prepareOrchestrationDelegation, routeOrchestration, sealMatchingOrchestrationDelegation, sealOrchestrationDelegation, statusOrchestration } from '../task/orchestration.js';
2
+ const USAGE = 'Usage: agent-infra-internal task-orchestration <task-ref|auto> <begin-or-resume|route|prepare|hook-start|stage-completed|hook-stop|advance|pause|status> [options]\n';
3
+ function usageFailure(message) {
4
+ process.stdout.write(`${JSON.stringify({
5
+ status: 'failed', changed: false,
6
+ error: { code: 'ORCHESTRATION_PAYLOAD_INVALID', message }
7
+ })}\n`);
8
+ process.stderr.write(USAGE);
9
+ process.exitCode = 2;
10
+ }
11
+ function taskOrchestration(args = []) {
12
+ if (args[0] === '--help' || args[0] === '-h') {
13
+ process.stdout.write(USAGE);
14
+ return;
15
+ }
16
+ if (args.length < 2) {
17
+ usageFailure('task ref and intent are required');
18
+ return;
19
+ }
20
+ const [taskRef, intent] = args;
21
+ if (!['begin-or-resume', 'route', 'prepare', 'hook-start', 'stage-completed', 'hook-stop', 'advance', 'pause', 'status'].includes(intent)) {
22
+ usageFailure(`unknown intent '${intent}'`);
23
+ return;
24
+ }
25
+ const values = {};
26
+ const seen = new Set();
27
+ for (let index = 2; index < args.length; index += 1) {
28
+ const flag = args[index];
29
+ if (![
30
+ '--max-steps', '--client', '--requested-model', '--parent-id', '--before-fingerprint',
31
+ '--native-agent', '--child-id', '--spawn-mode', '--actual-model', '--fallback-reason',
32
+ '--exit-code', '--after-fingerprint', '--changed-paths', '--code', '--message', '--recoverable', '--agent'
33
+ ].includes(flag)) {
34
+ usageFailure(`unknown option '${flag}'`);
35
+ return;
36
+ }
37
+ if (seen.has(flag)) {
38
+ usageFailure(`duplicate option '${flag}'`);
39
+ return;
40
+ }
41
+ seen.add(flag);
42
+ const value = args[++index];
43
+ if (!value || value.startsWith('--')) {
44
+ usageFailure(`option '${flag}' requires a value`);
45
+ return;
46
+ }
47
+ values[flag] = value;
48
+ }
49
+ const requireValues = (flags) => flags.find((flag) => values[flag] === undefined);
50
+ let result;
51
+ if (intent === 'begin-or-resume') {
52
+ const maxSteps = values['--max-steps'] === undefined ? undefined : Number(values['--max-steps']);
53
+ if (maxSteps !== undefined && (!Number.isInteger(maxSteps) || maxSteps < 1)) {
54
+ usageFailure('--max-steps must be a positive integer');
55
+ return;
56
+ }
57
+ result = beginOrResumeOrchestration(taskRef, { maxSteps });
58
+ }
59
+ else if (intent === 'route') {
60
+ result = routeOrchestration(taskRef);
61
+ }
62
+ else if (intent === 'status') {
63
+ result = statusOrchestration(taskRef);
64
+ }
65
+ else if (intent === 'prepare') {
66
+ const missing = requireValues(['--client']);
67
+ if (missing) {
68
+ usageFailure(`intent 'prepare' requires '${missing}'`);
69
+ return;
70
+ }
71
+ result = prepareOrchestrationDelegation(taskRef, {
72
+ client: values['--client'],
73
+ requestedModel: values['--requested-model']
74
+ });
75
+ }
76
+ else if (intent === 'hook-start') {
77
+ const missing = requireValues([
78
+ ...(taskRef === 'auto' ? ['--client'] : []),
79
+ '--native-agent', '--child-id', '--parent-id', '--spawn-mode'
80
+ ]);
81
+ if (missing) {
82
+ usageFailure(`intent 'hook-start' requires '${missing}'`);
83
+ return;
84
+ }
85
+ const event = {
86
+ nativeAgent: values['--native-agent'], childId: values['--child-id'],
87
+ parentId: values['--parent-id'], spawnMode: values['--spawn-mode'],
88
+ actualModel: values['--actual-model'], modelFallbackReason: values['--fallback-reason']
89
+ };
90
+ result = taskRef === 'auto'
91
+ ? activateMatchingOrchestrationDelegation(values['--client'], event)
92
+ : activateOrchestrationDelegation(taskRef, event);
93
+ }
94
+ else if (intent === 'hook-stop') {
95
+ if (taskRef === 'auto') {
96
+ const missing = requireValues(['--client', '--native-agent', '--child-id']);
97
+ if (missing) {
98
+ usageFailure(`intent 'hook-stop' requires '${missing}'`);
99
+ return;
100
+ }
101
+ result = sealMatchingOrchestrationDelegation(values['--client'], {
102
+ nativeAgent: values['--native-agent'], childId: values['--child-id']
103
+ });
104
+ }
105
+ else {
106
+ const missing = requireValues(['--child-id', '--exit-code', '--after-fingerprint']);
107
+ if (missing) {
108
+ usageFailure(`intent 'hook-stop' requires '${missing}'`);
109
+ return;
110
+ }
111
+ const exitCode = Number(values['--exit-code']);
112
+ if (!Number.isInteger(exitCode)) {
113
+ usageFailure('--exit-code must be an integer');
114
+ return;
115
+ }
116
+ result = sealOrchestrationDelegation(taskRef, {
117
+ childId: values['--child-id'], exitCode,
118
+ afterFingerprint: values['--after-fingerprint'],
119
+ changedPaths: values['--changed-paths'] ? values['--changed-paths'].split(',').filter(Boolean) : []
120
+ });
121
+ }
122
+ }
123
+ else if (intent === 'stage-completed') {
124
+ const missing = requireValues(['--agent']);
125
+ if (missing) {
126
+ usageFailure(`intent 'stage-completed' requires '${missing}'`);
127
+ return;
128
+ }
129
+ result = completeCommitOrchestrationStage(taskRef, values['--agent']);
130
+ }
131
+ else if (intent === 'advance') {
132
+ result = advanceOrchestration(taskRef);
133
+ }
134
+ else {
135
+ const missing = requireValues(['--code', '--message', '--recoverable']);
136
+ if (missing) {
137
+ usageFailure(`intent 'pause' requires '${missing}'`);
138
+ return;
139
+ }
140
+ if (!['true', 'false'].includes(values['--recoverable'])) {
141
+ usageFailure('--recoverable must be true or false');
142
+ return;
143
+ }
144
+ result = pauseOrchestration(taskRef, values['--code'], values['--message'], values['--recoverable'] === 'true');
145
+ }
146
+ process.stdout.write(`${JSON.stringify(result)}\n`);
147
+ if (result.status === 'failed')
148
+ process.exitCode = 1;
149
+ }
150
+ export { taskOrchestration };
151
+ //# sourceMappingURL=task-orchestration.js.map
@@ -0,0 +1,65 @@
1
+ import { finalizeReviewSummary } from '../task/review-finalization.js';
2
+ const USAGE = 'Usage: agent-infra-internal task-review <task-ref> finalize-summary --stage <analysis|plan|code> --artifact <review-*.md> [--dry-run]\n';
3
+ function failUsage(message) {
4
+ process.stdout.write(`${JSON.stringify({
5
+ status: 'failed',
6
+ changed: false,
7
+ intent: 'finalize-summary',
8
+ error: { code: 'REVIEW_PAYLOAD_INVALID', message }
9
+ })}\n`);
10
+ process.stderr.write(USAGE);
11
+ process.exitCode = 2;
12
+ }
13
+ function taskReview(args = []) {
14
+ if (args[0] === '--help' || args[0] === '-h') {
15
+ process.stdout.write(USAGE);
16
+ return;
17
+ }
18
+ if (args.length < 2) {
19
+ failUsage('task ref and intent are required');
20
+ return;
21
+ }
22
+ if (args[1] !== 'finalize-summary') {
23
+ failUsage(`unknown intent '${args[1]}'`);
24
+ return;
25
+ }
26
+ let stage = '';
27
+ let artifact = '';
28
+ let dryRun = false;
29
+ const seen = new Set();
30
+ for (let index = 2; index < args.length; index += 1) {
31
+ const flag = args[index];
32
+ if (!['--stage', '--artifact', '--dry-run'].includes(flag)) {
33
+ failUsage(`unknown option '${flag}'`);
34
+ return;
35
+ }
36
+ if (seen.has(flag)) {
37
+ failUsage(`duplicate option '${flag}'`);
38
+ return;
39
+ }
40
+ seen.add(flag);
41
+ if (flag === '--dry-run') {
42
+ dryRun = true;
43
+ continue;
44
+ }
45
+ const value = args[++index];
46
+ if (!value || value.startsWith('--')) {
47
+ failUsage(`option '${flag}' requires a value`);
48
+ return;
49
+ }
50
+ if (flag === '--stage')
51
+ stage = value;
52
+ else
53
+ artifact = value;
54
+ }
55
+ if (!stage || !artifact) {
56
+ failUsage("options '--stage' and '--artifact' are required");
57
+ return;
58
+ }
59
+ const result = finalizeReviewSummary({ taskRef: args[0], stage, artifact, dryRun });
60
+ process.stdout.write(`${JSON.stringify(result)}\n`);
61
+ if (result.status === 'failed')
62
+ process.exitCode = 1;
63
+ }
64
+ export { taskReview };
65
+ //# sourceMappingURL=task-review.js.map
package/dist/lib/merge.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { info, ok } from './log.js';
4
+ import { removeDirRecursive } from './remove-dir.js';
4
5
  const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
5
6
  const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/;
6
7
  const TITLE_RE = /^# (.+)$/m;
@@ -527,7 +528,7 @@ function mergeMutableSections({ sourceWorkspace, localWorkspace, backupRoot, rep
527
528
  if (comparison > 0) {
528
529
  backupTaskDir(backupRoot, localMatch.section, localMatch.taskDir, localMatch.taskId);
529
530
  report.backupCount += 1;
530
- fs.rmSync(localMatch.taskDir, { recursive: true, force: true });
531
+ removeDirRecursive(localMatch.taskDir);
531
532
  const destinationDir = copyTaskToSection(sourceTask, localWorkspace);
532
533
  localIndex.set(sourceTask.taskId, {
533
534
  taskId: sourceTask.taskId,
@@ -7,8 +7,45 @@ function registerPlatformAdapter(adapter) {
7
7
  function getPlatformAdapter(type) {
8
8
  return type ? adapters.get(type) ?? null : null;
9
9
  }
10
+ function registerPlatformCapabilities(type, capabilities) {
11
+ const adapter = adapters.get(type);
12
+ if (!adapter)
13
+ throw new Error(`Platform adapter '${type}' must be registered before its capabilities`);
14
+ adapters.set(type, { ...adapter, ...capabilities });
15
+ }
16
+ function hasPlatformCapability(type, capability) {
17
+ const adapter = getPlatformAdapter(type);
18
+ if (capability === 'change-request')
19
+ return typeof adapter?.inspectChangeRequest === 'function';
20
+ if (capability === 'required-checks')
21
+ return typeof adapter?.inspectRequiredChecks === 'function';
22
+ return typeof adapter?.resolveChangeRequestGitEvidence === 'function';
23
+ }
24
+ function unsupported(type, capability) {
25
+ return {
26
+ ok: false,
27
+ error: {
28
+ code: 'PLATFORM_CAPABILITY_UNSUPPORTED',
29
+ message: `Platform '${type || 'none'}' does not provide ${capability}`,
30
+ retryable: false
31
+ }
32
+ };
33
+ }
34
+ function inspectPlatformChangeRequest(type, context) {
35
+ const adapter = getPlatformAdapter(type);
36
+ return adapter?.inspectChangeRequest?.(context) ?? unsupported(type, 'change-request inspection');
37
+ }
38
+ function inspectPlatformRequiredChecks(type, context) {
39
+ const adapter = getPlatformAdapter(type);
40
+ return adapter?.inspectRequiredChecks?.(context) ?? unsupported(type, 'required-checks inspection');
41
+ }
42
+ function resolvePlatformChangeRequestGitEvidence(type, context) {
43
+ const adapter = getPlatformAdapter(type);
44
+ return adapter?.resolveChangeRequestGitEvidence?.(context) ??
45
+ unsupported(type, 'change-request Git evidence');
46
+ }
10
47
  function listPlatformAdapters() {
11
48
  return [...adapters.keys()].sort();
12
49
  }
13
- export { getPlatformAdapter, listPlatformAdapters, registerPlatformAdapter };
50
+ export { getPlatformAdapter, hasPlatformCapability, inspectPlatformChangeRequest, inspectPlatformRequiredChecks, listPlatformAdapters, registerPlatformAdapter, registerPlatformCapabilities, resolvePlatformChangeRequestGitEvidence };
14
51
  //# sourceMappingURL=adapters.js.map
@@ -0,0 +1,113 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { execFileSync, spawnSync } from "node:child_process";
5
+ import { resolvePlatformChangeRequestGitEvidence } from "./adapters.js";
6
+ import { resolveReviewedHeadRelation } from "./merged-pr-equivalence.js";
7
+ const SHA_PATTERN = /^[0-9a-f]{40}$/i;
8
+ const REVIEWED_HEAD_REF = "refs/agent-infra/reviewed-head";
9
+ const TARGET_HEAD_REF = "refs/agent-infra/target-head";
10
+ function git(cwd, args) {
11
+ return execFileSync("git", args, {
12
+ cwd,
13
+ encoding: "utf8",
14
+ maxBuffer: 32 * 1024 * 1024,
15
+ stdio: ["ignore", "pipe", "pipe"]
16
+ }).trim();
17
+ }
18
+ function validRef(ref) {
19
+ return spawnSync("git", ["check-ref-format", ref], {
20
+ encoding: "utf8",
21
+ stdio: ["ignore", "ignore", "ignore"]
22
+ }).status === 0;
23
+ }
24
+ function blocked(code, message) {
25
+ return { status: "blocked", code, message };
26
+ }
27
+ function failed(code, message) {
28
+ return { status: "failed", code, message };
29
+ }
30
+ function validMergeIdentity(lastReviewedCommit, pullRequest) {
31
+ if (pullRequest.state !== "closed" ||
32
+ !pullRequest.mergedAt ||
33
+ !pullRequest.mergeCommitSha ||
34
+ !SHA_PATTERN.test(lastReviewedCommit) ||
35
+ lastReviewedCommit !== pullRequest.head.sha) {
36
+ return failed("PR_MERGE_IDENTITY_INVALID", "PR merge identity does not match the reviewed head");
37
+ }
38
+ return null;
39
+ }
40
+ function resolveMaterializedReviewedHeadRelation(input) {
41
+ const identityFailure = validMergeIdentity(input.lastReviewedCommit, input.pullRequest);
42
+ if (identityFailure)
43
+ return identityFailure;
44
+ const source = resolvePlatformChangeRequestGitEvidence(input.platformType, {
45
+ cwd: input.cwd,
46
+ repository: input.pullRequest.repository,
47
+ number: input.pullRequest.number,
48
+ pullRequest: input.pullRequest
49
+ });
50
+ if (!source.ok || !source.value) {
51
+ return blocked(source.error?.code || "PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE", source.error?.message || "Unable to resolve authoritative PR Git evidence");
52
+ }
53
+ if (!source.value.remoteUrl.trim() ||
54
+ source.value.remoteUrl.trimStart().startsWith("-") ||
55
+ !validRef(source.value.reviewedHeadRef) ||
56
+ !validRef(source.value.targetHeadRef)) {
57
+ return blocked("PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE", "Platform returned an invalid PR Git evidence source");
58
+ }
59
+ let evidenceRoot = null;
60
+ let phase = "prepare";
61
+ let result = blocked("PR_MERGE_GIT_EVIDENCE_UNAVAILABLE", "Unable to prepare isolated PR Git evidence");
62
+ try {
63
+ evidenceRoot = fs.mkdtempSync(path.join(os.tmpdir(), "agent-infra-pr-evidence-"));
64
+ execFileSync("git", ["init", "--bare", "-q", evidenceRoot], {
65
+ cwd: input.cwd,
66
+ encoding: "utf8",
67
+ stdio: ["ignore", "pipe", "pipe"]
68
+ });
69
+ phase = "fetch";
70
+ git(evidenceRoot, [
71
+ "fetch",
72
+ "--no-tags",
73
+ "--no-write-fetch-head",
74
+ source.value.remoteUrl,
75
+ `+${source.value.reviewedHeadRef}:${REVIEWED_HEAD_REF}`,
76
+ `+${source.value.targetHeadRef}:${TARGET_HEAD_REF}`
77
+ ]);
78
+ phase = "resolve";
79
+ const fetchedReviewedHead = git(evidenceRoot, ["rev-parse", `${REVIEWED_HEAD_REF}^{commit}`]);
80
+ const comparisonHead = git(evidenceRoot, ["rev-parse", `${TARGET_HEAD_REF}^{commit}`]);
81
+ if (fetchedReviewedHead !== input.pullRequest.head.sha) {
82
+ result = failed("PR_MERGE_FETCHED_HEAD_MISMATCH", "Fetched PR head does not match the authoritative platform snapshot");
83
+ }
84
+ else {
85
+ result = resolveReviewedHeadRelation({
86
+ gitRoot: evidenceRoot,
87
+ comparisonHead,
88
+ lastReviewedCommit: input.lastReviewedCommit,
89
+ pullRequest: input.pullRequest
90
+ });
91
+ }
92
+ }
93
+ catch {
94
+ result = phase === "fetch"
95
+ ? blocked("PR_MERGE_EVIDENCE_FETCH_FAILED", "Unable to fetch PR Git evidence; verify Git credentials and platform refs")
96
+ : phase === "resolve"
97
+ ? blocked("PR_MERGE_OBJECT_MISSING", "Fetched PR Git evidence is incomplete")
98
+ : blocked("PR_MERGE_GIT_EVIDENCE_UNAVAILABLE", "Unable to prepare isolated PR Git evidence");
99
+ }
100
+ finally {
101
+ if (evidenceRoot) {
102
+ try {
103
+ fs.rmSync(evidenceRoot, { recursive: true, force: true });
104
+ }
105
+ catch {
106
+ result = blocked("PR_MERGE_EVIDENCE_CLEANUP_FAILED", "Unable to clean up isolated PR Git evidence");
107
+ }
108
+ }
109
+ }
110
+ return result;
111
+ }
112
+ export { resolveMaterializedReviewedHeadRelation };
113
+ //# sourceMappingURL=change-request-git-evidence.js.map
@@ -1,7 +1,12 @@
1
- import { execFileSync } from "node:child_process";
1
+ import { execFileSync, spawnSync } from "node:child_process";
2
2
  const SHA_PATTERN = /^[0-9a-f]{40}$/i;
3
3
  function git(cwd, args) {
4
- return execFileSync("git", args, { cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
4
+ return execFileSync("git", args, {
5
+ cwd,
6
+ encoding: "utf8",
7
+ maxBuffer: 32 * 1024 * 1024,
8
+ stdio: ["ignore", "pipe", "pipe"]
9
+ });
5
10
  }
6
11
  function commitExists(gitRoot, sha) {
7
12
  try {
@@ -12,21 +17,49 @@ function commitExists(gitRoot, sha) {
12
17
  return false;
13
18
  }
14
19
  }
20
+ function isAncestor(gitRoot, ancestor, descendant) {
21
+ const result = spawnSync("git", ["merge-base", "--is-ancestor", ancestor, descendant], {
22
+ cwd: gitRoot,
23
+ encoding: "utf8",
24
+ stdio: ["ignore", "pipe", "pipe"]
25
+ });
26
+ if (result.status === 0)
27
+ return { status: "yes" };
28
+ if (result.status === 1)
29
+ return { status: "no" };
30
+ return { status: "error" };
31
+ }
15
32
  export function resolveReviewedHeadRelation(input) {
16
- const { gitRoot, localHead, lastReviewedCommit, pullRequest, pathspecs } = input;
17
- if (localHead === lastReviewedCommit && lastReviewedCommit === pullRequest.head.sha) {
33
+ const { gitRoot, comparisonHead, lastReviewedCommit, pullRequest } = input;
34
+ if (comparisonHead === lastReviewedCommit &&
35
+ lastReviewedCommit === pullRequest.head.sha &&
36
+ !(pullRequest.state === "closed" && pullRequest.mergedAt && pullRequest.mergeCommitSha)) {
18
37
  return { status: "strict", reviewedHead: lastReviewedCommit };
19
38
  }
20
39
  if (pullRequest.state !== "closed" || !pullRequest.mergedAt || !pullRequest.mergeCommitSha ||
21
40
  lastReviewedCommit !== pullRequest.head.sha) {
22
41
  return { status: "failed", code: "PR_MERGE_IDENTITY_INVALID", message: "PR merge identity does not match the reviewed head" };
23
42
  }
24
- const shas = [pullRequest.base.sha, pullRequest.head.sha, pullRequest.mergeCommitSha, localHead];
43
+ const shas = [pullRequest.base.sha, pullRequest.head.sha, pullRequest.mergeCommitSha, comparisonHead];
25
44
  if (shas.some((sha) => !SHA_PATTERN.test(sha) || !commitExists(gitRoot, sha))) {
26
45
  return { status: "blocked", code: "PR_MERGE_OBJECT_MISSING", message: "Required PR merge Git objects are unavailable" };
27
46
  }
47
+ const ancestry = isAncestor(gitRoot, pullRequest.mergeCommitSha, comparisonHead);
48
+ if (ancestry.status === "no") {
49
+ return {
50
+ status: "failed",
51
+ code: "PR_MERGE_TARGET_MISMATCH",
52
+ message: "PR merge commit is not in the authoritative target history"
53
+ };
54
+ }
55
+ if (ancestry.status === "error") {
56
+ return {
57
+ status: "blocked",
58
+ code: "PR_MERGE_GIT_EVIDENCE_UNAVAILABLE",
59
+ message: "Unable to verify PR merge ancestry"
60
+ };
61
+ }
28
62
  try {
29
- git(gitRoot, ["merge-base", "--is-ancestor", pullRequest.mergeCommitSha, localHead]);
30
63
  const parents = git(gitRoot, ["rev-list", "--parents", "-n", "1", pullRequest.mergeCommitSha]).trim().split(/\s+/);
31
64
  if (parents.length !== 2) {
32
65
  return { status: "failed", code: "PR_MERGE_TOPOLOGY_INVALID", message: "PR merge commit is not a single-parent squash commit" };
@@ -37,10 +70,6 @@ export function resolveReviewedHeadRelation(input) {
37
70
  if (reviewedDiff !== mergedDiff) {
38
71
  return { status: "failed", code: "PR_MERGE_CONTENT_MISMATCH", message: "Squash merge content differs from the reviewed PR changes" };
39
72
  }
40
- const postMerge = git(gitRoot, ["rev-list", `${pullRequest.mergeCommitSha}..${localHead}`, "--", ...pathspecs]).trim();
41
- if (postMerge) {
42
- return { status: "failed", code: "POST_MERGE_CHANGES", message: "Protected paths changed after the squash merge" };
43
- }
44
73
  }
45
74
  catch {
46
75
  return { status: "blocked", code: "PR_MERGE_GIT_EVIDENCE_UNAVAILABLE", message: "Unable to verify PR merge topology or content" };
@@ -48,7 +77,8 @@ export function resolveReviewedHeadRelation(input) {
48
77
  return {
49
78
  status: "merged-equivalent",
50
79
  reviewedHead: lastReviewedCommit,
51
- mergeCommit: pullRequest.mergeCommitSha
80
+ mergeCommit: pullRequest.mergeCommitSha,
81
+ comparisonHead
52
82
  };
53
83
  }
54
84
  //# sourceMappingURL=merged-pr-equivalence.js.map