@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
@@ -0,0 +1,65 @@
1
+ import { finalizeReviewSummary } from '../task/review-finalization.ts';
2
+
3
+ const USAGE = 'Usage: agent-infra-internal task-review <task-ref> finalize-summary --stage <analysis|plan|code> --artifact <review-*.md> [--dry-run]\n';
4
+
5
+ function failUsage(message: string): void {
6
+ process.stdout.write(`${JSON.stringify({
7
+ status: 'failed',
8
+ changed: false,
9
+ intent: 'finalize-summary',
10
+ error: { code: 'REVIEW_PAYLOAD_INVALID', message }
11
+ })}\n`);
12
+ process.stderr.write(USAGE);
13
+ process.exitCode = 2;
14
+ }
15
+
16
+ function taskReview(args: string[] = []): void {
17
+ if (args[0] === '--help' || args[0] === '-h') {
18
+ process.stdout.write(USAGE);
19
+ return;
20
+ }
21
+ if (args.length < 2) {
22
+ failUsage('task ref and intent are required');
23
+ return;
24
+ }
25
+ if (args[1] !== 'finalize-summary') {
26
+ failUsage(`unknown intent '${args[1]}'`);
27
+ return;
28
+ }
29
+ let stage = '';
30
+ let artifact = '';
31
+ let dryRun = false;
32
+ const seen = new Set<string>();
33
+ for (let index = 2; index < args.length; index += 1) {
34
+ const flag = args[index]!;
35
+ if (!['--stage', '--artifact', '--dry-run'].includes(flag)) {
36
+ failUsage(`unknown option '${flag}'`);
37
+ return;
38
+ }
39
+ if (seen.has(flag)) {
40
+ failUsage(`duplicate option '${flag}'`);
41
+ return;
42
+ }
43
+ seen.add(flag);
44
+ if (flag === '--dry-run') {
45
+ dryRun = true;
46
+ continue;
47
+ }
48
+ const value = args[++index];
49
+ if (!value || value.startsWith('--')) {
50
+ failUsage(`option '${flag}' requires a value`);
51
+ return;
52
+ }
53
+ if (flag === '--stage') stage = value;
54
+ else artifact = value;
55
+ }
56
+ if (!stage || !artifact) {
57
+ failUsage("options '--stage' and '--artifact' are required");
58
+ return;
59
+ }
60
+ const result = finalizeReviewSummary({ taskRef: args[0]!, stage, artifact, dryRun });
61
+ process.stdout.write(`${JSON.stringify(result)}\n`);
62
+ if (result.status === 'failed') process.exitCode = 1;
63
+ }
64
+
65
+ export { taskReview };
package/lib/merge.ts 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.ts';
4
+ import { removeDirRecursive } from './remove-dir.ts';
4
5
 
5
6
  const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
6
7
  const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/;
@@ -690,7 +691,7 @@ function mergeMutableSections({ sourceWorkspace, localWorkspace, backupRoot, rep
690
691
  if (comparison > 0) {
691
692
  backupTaskDir(backupRoot, localMatch.section, localMatch.taskDir, localMatch.taskId);
692
693
  report.backupCount += 1;
693
- fs.rmSync(localMatch.taskDir, { recursive: true, force: true });
694
+ removeDirRecursive(localMatch.taskDir);
694
695
 
695
696
  const destinationDir = copyTaskToSection(sourceTask, localWorkspace);
696
697
  localIndex.set(sourceTask.taskId, {
@@ -1,4 +1,4 @@
1
- import type { PlatformResult } from './types.ts';
1
+ import type { PlatformError, PlatformResult } from './types.ts';
2
2
 
3
3
  type PlatformAdapterContext = {
4
4
  cwd: string;
@@ -6,9 +6,71 @@ type PlatformAdapterContext = {
6
6
  client?: unknown;
7
7
  };
8
8
 
9
+ type PlatformChangeRequestSnapshot = {
10
+ repository: string;
11
+ number: number;
12
+ nodeId: string;
13
+ url: string;
14
+ state: 'open' | 'closed';
15
+ title: string;
16
+ body: string;
17
+ draft: boolean;
18
+ head: { repository: string; ref: string; sha: string };
19
+ base: { repository: string; ref: string; sha: string };
20
+ mergedAt: string | null;
21
+ mergeCommitSha: string | null;
22
+ labels: string[];
23
+ assignees: string[];
24
+ milestone: string | null;
25
+ };
26
+
27
+ type PlatformCheckSnapshot = {
28
+ name: string;
29
+ bucket: 'pass' | 'fail' | 'pending' | 'cancel';
30
+ workflow?: string | null;
31
+ conclusion?: string | null;
32
+ detailsUrl?: string | null;
33
+ startedAt?: string | null;
34
+ completedAt?: string | null;
35
+ };
36
+
37
+ type PlatformInspectionResult<T> = {
38
+ ok: boolean;
39
+ value?: T;
40
+ error?: PlatformError;
41
+ };
42
+
43
+ type ChangeRequestInspectionContext = PlatformAdapterContext & {
44
+ repository: string;
45
+ number: number;
46
+ };
47
+
48
+ type RequiredChecksInspectionContext = ChangeRequestInspectionContext & {
49
+ headSha: string;
50
+ };
51
+
52
+ type ChangeRequestGitEvidenceContext = ChangeRequestInspectionContext & {
53
+ pullRequest: PlatformChangeRequestSnapshot;
54
+ };
55
+
56
+ type PlatformChangeRequestGitEvidenceSpec = {
57
+ remoteUrl: string;
58
+ reviewedHeadRef: string;
59
+ targetHeadRef: string;
60
+ };
61
+
9
62
  type PlatformAdapter = {
10
63
  type: string;
11
64
  resolveContext(context: PlatformAdapterContext): PlatformResult;
65
+ inspectChangeRequest?(
66
+ context: ChangeRequestInspectionContext
67
+ ): PlatformInspectionResult<PlatformChangeRequestSnapshot>;
68
+ inspectRequiredChecks?(
69
+ context: RequiredChecksInspectionContext
70
+ ): PlatformInspectionResult<PlatformCheckSnapshot[]>;
71
+ resolveChangeRequestGitEvidence?(
72
+ context: ChangeRequestGitEvidenceContext
73
+ ): PlatformInspectionResult<PlatformChangeRequestGitEvidenceSpec>;
12
74
  };
13
75
 
14
76
  const adapters = new Map<string, PlatformAdapter>();
@@ -22,9 +84,86 @@ function getPlatformAdapter(type: string | null): PlatformAdapter | null {
22
84
  return type ? adapters.get(type) ?? null : null;
23
85
  }
24
86
 
87
+ function registerPlatformCapabilities(
88
+ type: string,
89
+ capabilities: Pick<
90
+ PlatformAdapter,
91
+ 'inspectChangeRequest' | 'inspectRequiredChecks' | 'resolveChangeRequestGitEvidence'
92
+ >
93
+ ): void {
94
+ const adapter = adapters.get(type);
95
+ if (!adapter) throw new Error(`Platform adapter '${type}' must be registered before its capabilities`);
96
+ adapters.set(type, { ...adapter, ...capabilities });
97
+ }
98
+
99
+ function hasPlatformCapability(
100
+ type: string | null,
101
+ capability: 'change-request' | 'required-checks' | 'change-request-git-evidence'
102
+ ): boolean {
103
+ const adapter = getPlatformAdapter(type);
104
+ if (capability === 'change-request') return typeof adapter?.inspectChangeRequest === 'function';
105
+ if (capability === 'required-checks') return typeof adapter?.inspectRequiredChecks === 'function';
106
+ return typeof adapter?.resolveChangeRequestGitEvidence === 'function';
107
+ }
108
+
109
+ function unsupported<T>(type: string | null, capability: string): PlatformInspectionResult<T> {
110
+ return {
111
+ ok: false,
112
+ error: {
113
+ code: 'PLATFORM_CAPABILITY_UNSUPPORTED',
114
+ message: `Platform '${type || 'none'}' does not provide ${capability}`,
115
+ retryable: false
116
+ }
117
+ };
118
+ }
119
+
120
+ function inspectPlatformChangeRequest(
121
+ type: string | null,
122
+ context: ChangeRequestInspectionContext
123
+ ): PlatformInspectionResult<PlatformChangeRequestSnapshot> {
124
+ const adapter = getPlatformAdapter(type);
125
+ return adapter?.inspectChangeRequest?.(context) ?? unsupported(type, 'change-request inspection');
126
+ }
127
+
128
+ function inspectPlatformRequiredChecks(
129
+ type: string | null,
130
+ context: RequiredChecksInspectionContext
131
+ ): PlatformInspectionResult<PlatformCheckSnapshot[]> {
132
+ const adapter = getPlatformAdapter(type);
133
+ return adapter?.inspectRequiredChecks?.(context) ?? unsupported(type, 'required-checks inspection');
134
+ }
135
+
136
+ function resolvePlatformChangeRequestGitEvidence(
137
+ type: string | null,
138
+ context: ChangeRequestGitEvidenceContext
139
+ ): PlatformInspectionResult<PlatformChangeRequestGitEvidenceSpec> {
140
+ const adapter = getPlatformAdapter(type);
141
+ return adapter?.resolveChangeRequestGitEvidence?.(context) ??
142
+ unsupported(type, 'change-request Git evidence');
143
+ }
144
+
25
145
  function listPlatformAdapters(): string[] {
26
146
  return [...adapters.keys()].sort();
27
147
  }
28
148
 
29
- export { getPlatformAdapter, listPlatformAdapters, registerPlatformAdapter };
30
- export type { PlatformAdapter, PlatformAdapterContext };
149
+ export {
150
+ getPlatformAdapter,
151
+ hasPlatformCapability,
152
+ inspectPlatformChangeRequest,
153
+ inspectPlatformRequiredChecks,
154
+ listPlatformAdapters,
155
+ registerPlatformAdapter,
156
+ registerPlatformCapabilities,
157
+ resolvePlatformChangeRequestGitEvidence
158
+ };
159
+ export type {
160
+ ChangeRequestGitEvidenceContext,
161
+ ChangeRequestInspectionContext,
162
+ PlatformAdapter,
163
+ PlatformAdapterContext,
164
+ PlatformChangeRequestGitEvidenceSpec,
165
+ PlatformChangeRequestSnapshot,
166
+ PlatformCheckSnapshot,
167
+ PlatformInspectionResult,
168
+ RequiredChecksInspectionContext
169
+ };
@@ -0,0 +1,159 @@
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
+
6
+ import { resolvePlatformChangeRequestGitEvidence } from "./adapters.ts";
7
+ import { resolveReviewedHeadRelation } from "./merged-pr-equivalence.ts";
8
+ import type { ReviewedHeadRelation } from "./merged-pr-equivalence.ts";
9
+ import type { PullRequestSnapshot } from "./pull-requests.ts";
10
+
11
+ const SHA_PATTERN = /^[0-9a-f]{40}$/i;
12
+ const REVIEWED_HEAD_REF = "refs/agent-infra/reviewed-head";
13
+ const TARGET_HEAD_REF = "refs/agent-infra/target-head";
14
+
15
+ function git(cwd: string, args: string[]): string {
16
+ return execFileSync("git", args, {
17
+ cwd,
18
+ encoding: "utf8",
19
+ maxBuffer: 32 * 1024 * 1024,
20
+ stdio: ["ignore", "pipe", "pipe"]
21
+ }).trim();
22
+ }
23
+
24
+ function validRef(ref: string): boolean {
25
+ return spawnSync("git", ["check-ref-format", ref], {
26
+ encoding: "utf8",
27
+ stdio: ["ignore", "ignore", "ignore"]
28
+ }).status === 0;
29
+ }
30
+
31
+ function blocked(code: string, message: string): ReviewedHeadRelation {
32
+ return { status: "blocked", code, message };
33
+ }
34
+
35
+ function failed(code: string, message: string): ReviewedHeadRelation {
36
+ return { status: "failed", code, message };
37
+ }
38
+
39
+ function validMergeIdentity(lastReviewedCommit: string, pullRequest: PullRequestSnapshot):
40
+ ReviewedHeadRelation | null {
41
+ if (
42
+ pullRequest.state !== "closed" ||
43
+ !pullRequest.mergedAt ||
44
+ !pullRequest.mergeCommitSha ||
45
+ !SHA_PATTERN.test(lastReviewedCommit) ||
46
+ lastReviewedCommit !== pullRequest.head.sha
47
+ ) {
48
+ return failed(
49
+ "PR_MERGE_IDENTITY_INVALID",
50
+ "PR merge identity does not match the reviewed head"
51
+ );
52
+ }
53
+ return null;
54
+ }
55
+
56
+ function resolveMaterializedReviewedHeadRelation(input: {
57
+ cwd: string;
58
+ platformType: string | null;
59
+ lastReviewedCommit: string;
60
+ pullRequest: PullRequestSnapshot;
61
+ }): ReviewedHeadRelation {
62
+ const identityFailure = validMergeIdentity(input.lastReviewedCommit, input.pullRequest);
63
+ if (identityFailure) return identityFailure;
64
+
65
+ const source = resolvePlatformChangeRequestGitEvidence(input.platformType, {
66
+ cwd: input.cwd,
67
+ repository: input.pullRequest.repository,
68
+ number: input.pullRequest.number,
69
+ pullRequest: input.pullRequest
70
+ });
71
+ if (!source.ok || !source.value) {
72
+ return blocked(
73
+ source.error?.code || "PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE",
74
+ source.error?.message || "Unable to resolve authoritative PR Git evidence"
75
+ );
76
+ }
77
+ if (
78
+ !source.value.remoteUrl.trim() ||
79
+ source.value.remoteUrl.trimStart().startsWith("-") ||
80
+ !validRef(source.value.reviewedHeadRef) ||
81
+ !validRef(source.value.targetHeadRef)
82
+ ) {
83
+ return blocked(
84
+ "PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE",
85
+ "Platform returned an invalid PR Git evidence source"
86
+ );
87
+ }
88
+
89
+ let evidenceRoot: string | null = null;
90
+ let phase: "prepare" | "fetch" | "resolve" = "prepare";
91
+ let result: ReviewedHeadRelation = blocked(
92
+ "PR_MERGE_GIT_EVIDENCE_UNAVAILABLE",
93
+ "Unable to prepare isolated PR Git evidence"
94
+ );
95
+ try {
96
+ evidenceRoot = fs.mkdtempSync(path.join(os.tmpdir(), "agent-infra-pr-evidence-"));
97
+ execFileSync("git", ["init", "--bare", "-q", evidenceRoot], {
98
+ cwd: input.cwd,
99
+ encoding: "utf8",
100
+ stdio: ["ignore", "pipe", "pipe"]
101
+ });
102
+
103
+ phase = "fetch";
104
+ git(evidenceRoot, [
105
+ "fetch",
106
+ "--no-tags",
107
+ "--no-write-fetch-head",
108
+ source.value.remoteUrl,
109
+ `+${source.value.reviewedHeadRef}:${REVIEWED_HEAD_REF}`,
110
+ `+${source.value.targetHeadRef}:${TARGET_HEAD_REF}`
111
+ ]);
112
+
113
+ phase = "resolve";
114
+ const fetchedReviewedHead = git(evidenceRoot, ["rev-parse", `${REVIEWED_HEAD_REF}^{commit}`]);
115
+ const comparisonHead = git(evidenceRoot, ["rev-parse", `${TARGET_HEAD_REF}^{commit}`]);
116
+ if (fetchedReviewedHead !== input.pullRequest.head.sha) {
117
+ result = failed(
118
+ "PR_MERGE_FETCHED_HEAD_MISMATCH",
119
+ "Fetched PR head does not match the authoritative platform snapshot"
120
+ );
121
+ } else {
122
+ result = resolveReviewedHeadRelation({
123
+ gitRoot: evidenceRoot,
124
+ comparisonHead,
125
+ lastReviewedCommit: input.lastReviewedCommit,
126
+ pullRequest: input.pullRequest
127
+ });
128
+ }
129
+ } catch {
130
+ result = phase === "fetch"
131
+ ? blocked(
132
+ "PR_MERGE_EVIDENCE_FETCH_FAILED",
133
+ "Unable to fetch PR Git evidence; verify Git credentials and platform refs"
134
+ )
135
+ : phase === "resolve"
136
+ ? blocked(
137
+ "PR_MERGE_OBJECT_MISSING",
138
+ "Fetched PR Git evidence is incomplete"
139
+ )
140
+ : blocked(
141
+ "PR_MERGE_GIT_EVIDENCE_UNAVAILABLE",
142
+ "Unable to prepare isolated PR Git evidence"
143
+ );
144
+ } finally {
145
+ if (evidenceRoot) {
146
+ try {
147
+ fs.rmSync(evidenceRoot, { recursive: true, force: true });
148
+ } catch {
149
+ result = blocked(
150
+ "PR_MERGE_EVIDENCE_CLEANUP_FAILED",
151
+ "Unable to clean up isolated PR Git evidence"
152
+ );
153
+ }
154
+ }
155
+ }
156
+ return result;
157
+ }
158
+
159
+ export { resolveMaterializedReviewedHeadRelation };
@@ -1,4 +1,4 @@
1
- import { execFileSync } from "node:child_process";
1
+ import { execFileSync, spawnSync } from "node:child_process";
2
2
 
3
3
  import type { PullRequestSnapshot } from "./pull-requests.ts";
4
4
 
@@ -6,11 +6,21 @@ const SHA_PATTERN = /^[0-9a-f]{40}$/i;
6
6
 
7
7
  type ReviewedHeadRelation =
8
8
  | { status: "strict"; reviewedHead: string }
9
- | { status: "merged-equivalent"; reviewedHead: string; mergeCommit: string }
9
+ | {
10
+ status: "merged-equivalent";
11
+ reviewedHead: string;
12
+ mergeCommit: string;
13
+ comparisonHead: string;
14
+ }
10
15
  | { status: "failed" | "blocked"; code: string; message: string };
11
16
 
12
17
  function git(cwd: string, args: string[]): string {
13
- return execFileSync("git", args, { cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
18
+ return execFileSync("git", args, {
19
+ cwd,
20
+ encoding: "utf8",
21
+ maxBuffer: 32 * 1024 * 1024,
22
+ stdio: ["ignore", "pipe", "pipe"]
23
+ });
14
24
  }
15
25
 
16
26
  function commitExists(gitRoot: string, sha: string): boolean {
@@ -22,27 +32,57 @@ function commitExists(gitRoot: string, sha: string): boolean {
22
32
  }
23
33
  }
24
34
 
35
+ function isAncestor(gitRoot: string, ancestor: string, descendant: string):
36
+ | { status: "yes" | "no" }
37
+ | { status: "error" } {
38
+ const result = spawnSync("git", ["merge-base", "--is-ancestor", ancestor, descendant], {
39
+ cwd: gitRoot,
40
+ encoding: "utf8",
41
+ stdio: ["ignore", "pipe", "pipe"]
42
+ });
43
+ if (result.status === 0) return { status: "yes" };
44
+ if (result.status === 1) return { status: "no" };
45
+ return { status: "error" };
46
+ }
47
+
25
48
  export function resolveReviewedHeadRelation(input: {
26
49
  gitRoot: string;
27
- localHead: string;
50
+ comparisonHead: string;
28
51
  lastReviewedCommit: string;
29
52
  pullRequest: PullRequestSnapshot;
30
- pathspecs: string[];
31
53
  }): ReviewedHeadRelation {
32
- const { gitRoot, localHead, lastReviewedCommit, pullRequest, pathspecs } = input;
33
- if (localHead === lastReviewedCommit && lastReviewedCommit === pullRequest.head.sha) {
54
+ const { gitRoot, comparisonHead, lastReviewedCommit, pullRequest } = input;
55
+ if (
56
+ comparisonHead === lastReviewedCommit &&
57
+ lastReviewedCommit === pullRequest.head.sha &&
58
+ !(pullRequest.state === "closed" && pullRequest.mergedAt && pullRequest.mergeCommitSha)
59
+ ) {
34
60
  return { status: "strict", reviewedHead: lastReviewedCommit };
35
61
  }
36
62
  if (pullRequest.state !== "closed" || !pullRequest.mergedAt || !pullRequest.mergeCommitSha ||
37
63
  lastReviewedCommit !== pullRequest.head.sha) {
38
64
  return { status: "failed", code: "PR_MERGE_IDENTITY_INVALID", message: "PR merge identity does not match the reviewed head" };
39
65
  }
40
- const shas = [pullRequest.base.sha, pullRequest.head.sha, pullRequest.mergeCommitSha, localHead];
66
+ const shas = [pullRequest.base.sha, pullRequest.head.sha, pullRequest.mergeCommitSha, comparisonHead];
41
67
  if (shas.some((sha) => !SHA_PATTERN.test(sha) || !commitExists(gitRoot, sha))) {
42
68
  return { status: "blocked", code: "PR_MERGE_OBJECT_MISSING", message: "Required PR merge Git objects are unavailable" };
43
69
  }
70
+ const ancestry = isAncestor(gitRoot, pullRequest.mergeCommitSha, comparisonHead);
71
+ if (ancestry.status === "no") {
72
+ return {
73
+ status: "failed",
74
+ code: "PR_MERGE_TARGET_MISMATCH",
75
+ message: "PR merge commit is not in the authoritative target history"
76
+ };
77
+ }
78
+ if (ancestry.status === "error") {
79
+ return {
80
+ status: "blocked",
81
+ code: "PR_MERGE_GIT_EVIDENCE_UNAVAILABLE",
82
+ message: "Unable to verify PR merge ancestry"
83
+ };
84
+ }
44
85
  try {
45
- git(gitRoot, ["merge-base", "--is-ancestor", pullRequest.mergeCommitSha, localHead]);
46
86
  const parents = git(gitRoot, ["rev-list", "--parents", "-n", "1", pullRequest.mergeCommitSha]).trim().split(/\s+/);
47
87
  if (parents.length !== 2) {
48
88
  return { status: "failed", code: "PR_MERGE_TOPOLOGY_INVALID", message: "PR merge commit is not a single-parent squash commit" };
@@ -53,17 +93,14 @@ export function resolveReviewedHeadRelation(input: {
53
93
  if (reviewedDiff !== mergedDiff) {
54
94
  return { status: "failed", code: "PR_MERGE_CONTENT_MISMATCH", message: "Squash merge content differs from the reviewed PR changes" };
55
95
  }
56
- const postMerge = git(gitRoot, ["rev-list", `${pullRequest.mergeCommitSha}..${localHead}`, "--", ...pathspecs]).trim();
57
- if (postMerge) {
58
- return { status: "failed", code: "POST_MERGE_CHANGES", message: "Protected paths changed after the squash merge" };
59
- }
60
96
  } catch {
61
97
  return { status: "blocked", code: "PR_MERGE_GIT_EVIDENCE_UNAVAILABLE", message: "Unable to verify PR merge topology or content" };
62
98
  }
63
99
  return {
64
100
  status: "merged-equivalent",
65
101
  reviewedHead: lastReviewedCommit,
66
- mergeCommit: pullRequest.mergeCommitSha
102
+ mergeCommit: pullRequest.mergeCommitSha,
103
+ comparisonHead
67
104
  };
68
105
  }
69
106
 
@@ -2,6 +2,11 @@ import fs from 'node:fs';
2
2
 
3
3
  import { parseTaskFrontmatter } from '../task/frontmatter.ts';
4
4
  import { resolveTaskRef } from '../task/resolve-ref.ts';
5
+ import {
6
+ inspectPlatformRequiredChecks,
7
+ registerPlatformCapabilities
8
+ } from './adapters.ts';
9
+ import type { PlatformCheckSnapshot } from './adapters.ts';
5
10
  import { resolvePlatformContext } from './context.ts';
6
11
  import { createGitHubClient } from './github-client.ts';
7
12
  import type { GitHubClient } from './github-client.ts';
@@ -12,15 +17,7 @@ import type { PullRequestSnapshot } from './pull-requests.ts';
12
17
 
13
18
  type CheckBucket = 'pass' | 'fail' | 'pending' | 'cancel';
14
19
  type CheckState = 'passed' | 'failed' | 'pending' | 'timed-out' | 'cancelled' | 'no-required';
15
- type CheckSnapshot = {
16
- name: string;
17
- bucket: CheckBucket;
18
- workflow?: string | null;
19
- conclusion?: string | null;
20
- detailsUrl?: string | null;
21
- startedAt?: string | null;
22
- completedAt?: string | null;
23
- };
20
+ type CheckSnapshot = PlatformCheckSnapshot;
24
21
  type ChecksSnapshot = { state: Exclude<CheckState, 'timed-out'>; required: CheckSnapshot[] };
25
22
  type RunCandidate = { id: number; name: string; headSha: string; jobId?: number | null };
26
23
  type ChecksResult = PlatformResult & {
@@ -29,6 +26,7 @@ type ChecksResult = PlatformResult & {
29
26
  resolution?: { status: 'resolved' | 'missing' | 'ambiguous'; runId: number | null; jobId: number | null };
30
27
  logs?: { runId: number; jobId?: number; text: string };
31
28
  };
29
+ type InspectionOptions = { cwd?: string; client?: unknown };
32
30
  type SharedOptions = { cwd?: string; client?: GitHubClient };
33
31
 
34
32
  function classifyRequiredChecks(required: CheckSnapshot[]): ChecksSnapshot {
@@ -114,14 +112,27 @@ function normalizeChecks(value: unknown): CheckSnapshot[] {
114
112
  });
115
113
  }
116
114
 
117
- function resolvedTask(taskRef: string, options: SharedOptions) {
115
+ registerPlatformCapabilities('github', {
116
+ inspectRequiredChecks({ client, repository, number, cwd }) {
117
+ const github = (client as GitHubClient | undefined) || createGitHubClient();
118
+ const inspected = github.json<unknown>([
119
+ 'pr', 'checks', String(number), '--repo', repository, '--required',
120
+ '--json', 'name,state,bucket,link,workflow,startedAt,completedAt'
121
+ ], { cwd });
122
+ return inspected.ok
123
+ ? { ok: true, value: normalizeChecks(inspected.value) }
124
+ : { ok: false, error: inspected.error };
125
+ }
126
+ });
127
+
128
+ function resolvedTask(taskRef: string, options: InspectionOptions) {
118
129
  const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
119
130
  if (!resolved.ok) return { ok: false as const, output: checksResult('failed', { error: { code: resolved.code, message: resolved.message, retryable: false } }) };
120
131
  const frontmatter = parseTaskFrontmatter(fs.readFileSync(resolved.taskMdPath, 'utf8'));
121
132
  const pr = Number(frontmatter.pr_number);
122
133
  const prNumber = Number.isInteger(pr) && pr > 0 ? pr : null;
123
134
  if (!prNumber) return { ok: false as const, output: checksResult('failed', { error: { code: 'PR_NOT_LINKED', message: 'Task has no valid pr_number', retryable: false } }) };
124
- const client = options.client || createGitHubClient();
135
+ const client = (options.client as GitHubClient | undefined) || createGitHubClient();
125
136
  const context = resolvePlatformContext({ cwd: resolved.repoRoot, client });
126
137
  if (!context.platform.repository || !['no-op', 'degraded'].includes(context.status)) return { ok: false as const, output: checksResult(context.status, { platform: context.platform, capabilities: context.capabilities, error: context.error }) };
127
138
  const inspected = inspectPlatformPullRequest(taskRef, { cwd: resolved.repoRoot, client });
@@ -129,22 +140,30 @@ function resolvedTask(taskRef: string, options: SharedOptions) {
129
140
  return { ok: true as const, resolved, prNumber, client, context, pullRequest: inspected.pullRequest };
130
141
  }
131
142
 
132
- function inspectRequiredChecks(taskRef: string, options: SharedOptions = {}): ChecksResult {
143
+ function inspectRequiredChecks(taskRef: string, options: InspectionOptions = {}): ChecksResult {
133
144
  const base = resolvedTask(taskRef, options);
134
145
  if (!base.ok) return base.output;
135
146
  const repository = base.context.platform.repository!;
136
- const inspected = base.client.json<unknown>([
137
- 'pr', 'checks', String(base.prNumber), '--repo', repository, '--required',
138
- '--json', 'name,state,bucket,link,workflow,startedAt,completedAt'
139
- ], { cwd: base.resolved.repoRoot });
140
- if (!inspected.ok) {
141
- return checksResult(inspected.error.retryable ? 'blocked' : 'failed', {
147
+ const inspected = inspectPlatformRequiredChecks(base.context.platform.type, {
148
+ cwd: base.resolved.repoRoot,
149
+ repository,
150
+ number: base.prNumber,
151
+ headSha: base.pullRequest.head.sha,
152
+ client: options.client || base.client
153
+ });
154
+ if (!inspected.ok || !inspected.value) {
155
+ const error = inspected.error || {
156
+ code: 'REQUIRED_CHECKS_INSPECTION_INVALID',
157
+ message: 'Platform adapter returned no required-checks snapshot',
158
+ retryable: false
159
+ };
160
+ return checksResult(error.retryable ? 'blocked' : 'failed', {
142
161
  platform: base.context.platform, capabilities: base.context.capabilities,
143
162
  resource: { kind: 'pull-request', number: base.prNumber },
144
- pullRequest: base.pullRequest, error: inspected.error
163
+ pullRequest: base.pullRequest, error
145
164
  });
146
165
  }
147
- const classified = classifyRequiredChecks(normalizeChecks(inspected.value));
166
+ const classified = classifyRequiredChecks(inspected.value);
148
167
  const status = classified.state === 'passed' || classified.state === 'no-required'
149
168
  ? 'no-op'
150
169
  : classified.state === 'failed' || classified.state === 'cancelled' ? 'failed' : 'blocked';
@@ -160,7 +179,7 @@ function inspectRequiredChecks(taskRef: string, options: SharedOptions = {}): Ch
160
179
  });
161
180
  }
162
181
 
163
- async function watchPlatformChecks(taskRef: string, options: SharedOptions & {
182
+ async function watchPlatformChecks(taskRef: string, options: InspectionOptions & {
164
183
  intervalSeconds: number;
165
184
  deadlineSeconds: number;
166
185
  signal?: AbortSignal;
@@ -218,13 +237,21 @@ function resolvePlatformCheckRun(taskRef: string, options: SharedOptions & { che
218
237
  });
219
238
  }
220
239
 
240
+ function fetchCheckLogText(client: GitHubClient, args: string[], cwd: string) {
241
+ const fetched = client.text(args, { cwd });
242
+ if (fetched.ok || args[0] !== 'api' || !/response contains terminal escape sequences/i.test(fetched.error.message)) {
243
+ return fetched;
244
+ }
245
+ return client.text([...args, '--allow-escape-sequences'], { cwd });
246
+ }
247
+
221
248
  function fetchPlatformCheckLogs(taskRef: string, options: SharedOptions & { run: number; job?: number }): ChecksResult {
222
249
  const base = resolvedTask(taskRef, options);
223
250
  if (!base.ok) return base.output;
224
251
  const args = options.job
225
252
  ? ['api', `repos/${base.context.platform.repository}/actions/jobs/${options.job}/logs`]
226
253
  : ['run', 'view', String(options.run), '--repo', base.context.platform.repository!, '--log-failed'];
227
- const fetched = base.client.text(args, { cwd: base.resolved.repoRoot });
254
+ const fetched = fetchCheckLogText(base.client, args, base.resolved.repoRoot);
228
255
  if (!fetched.ok) return checksResult(fetched.error.retryable ? 'blocked' : 'failed', { platform: base.context.platform, capabilities: base.context.capabilities, error: fetched.error });
229
256
  if (!fetched.value) return checksResult('failed', { error: { code: 'CHECK_LOGS_MISSING', message: 'No failed logs are available', retryable: false } });
230
257
  return checksResult('no-op', {
@@ -236,6 +263,7 @@ function fetchPlatformCheckLogs(taskRef: string, options: SharedOptions & { run:
236
263
 
237
264
  export {
238
265
  classifyRequiredChecks,
266
+ fetchCheckLogText,
239
267
  fetchPlatformCheckLogs,
240
268
  inspectRequiredChecks,
241
269
  parseRunJobIdentity,