@fitlab-ai/agent-infra 0.9.12 → 0.9.13

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 (239) hide show
  1. package/README.md +11 -11
  2. package/README.zh-CN.md +11 -11
  3. package/dist/bin/internal-cli.js +24 -19
  4. package/dist/lib/agent-clients/next-steps.js +6 -1
  5. package/dist/lib/decide.js +3 -3
  6. package/dist/lib/internal/platform-checks.js +3 -3
  7. package/dist/lib/internal/platform-comment.js +9 -9
  8. package/dist/lib/internal/platform-context.js +2 -2
  9. package/dist/lib/internal/platform-issue.js +8 -9
  10. package/dist/lib/internal/platform-pr-review.js +10 -10
  11. package/dist/lib/internal/platform-pr.js +63 -24
  12. package/dist/lib/internal/platform-release-notes.js +3 -3
  13. package/dist/lib/internal/pr-review-grade.js +11 -9
  14. package/dist/lib/internal/release-workflow.js +4 -4
  15. package/dist/lib/internal/sandbox-control.js +30 -4
  16. package/dist/lib/internal/task-activity.js +3 -3
  17. package/dist/lib/internal/task-artifact.js +76 -24
  18. package/dist/lib/internal/task-create.js +2 -2
  19. package/dist/lib/internal/task-event.js +6 -3
  20. package/dist/lib/internal/task-finalization.js +2 -2
  21. package/dist/lib/internal/task-invalidation.js +60 -0
  22. package/dist/lib/internal/task-ledger.js +9 -6
  23. package/dist/lib/internal/task-lifecycle.js +2 -2
  24. package/dist/lib/internal/task-override.js +4 -4
  25. package/dist/lib/internal/task-review.js +3 -3
  26. package/dist/lib/internal/task-verify.js +6 -6
  27. package/dist/lib/internal/task-warning.js +3 -3
  28. package/dist/lib/platform/adapters.js +1 -3
  29. package/dist/lib/platform/change-request-git-evidence.js +27 -7
  30. package/dist/lib/platform/completion-backfill.js +6 -5
  31. package/dist/lib/platform/context.js +70 -130
  32. package/dist/lib/platform/github-client.js +1 -1
  33. package/dist/lib/platform/github-provider.js +903 -0
  34. package/dist/lib/platform/in-label-sync.js +143 -0
  35. package/dist/lib/platform/issue-comments.js +94 -41
  36. package/dist/lib/platform/issues.js +335 -189
  37. package/dist/lib/platform/metadata-labels.js +6 -2
  38. package/dist/lib/platform/none-provider.js +28 -0
  39. package/dist/lib/platform/pr-change-report.js +551 -0
  40. package/dist/lib/platform/pr-checks.js +119 -104
  41. package/dist/lib/platform/pr-review.js +88 -72
  42. package/dist/lib/platform/pr-summary.js +520 -50
  43. package/dist/lib/platform/provider-bridge.js +45 -0
  44. package/dist/lib/platform/provider-contract.d.ts +487 -0
  45. package/dist/lib/platform/provider-contract.js +95 -0
  46. package/dist/lib/platform/provider-loader.js +251 -0
  47. package/dist/lib/platform/provider-validation.js +504 -0
  48. package/dist/lib/platform/pull-request-metadata.js +8 -6
  49. package/dist/lib/platform/pull-requests.js +783 -537
  50. package/dist/lib/platform/release-notes.js +78 -55
  51. package/dist/lib/platform/releases.js +79 -50
  52. package/dist/lib/platform/resource-identity.d.ts +26 -0
  53. package/dist/lib/platform/resource-identity.js +92 -0
  54. package/dist/lib/platform/task-identities.js +31 -0
  55. package/dist/lib/platform/verification-required.js +11 -12
  56. package/dist/lib/platform/verification-sync.js +112 -238
  57. package/dist/lib/pr-review/evidence-grading.js +19 -6
  58. package/dist/lib/run/index.js +63 -23
  59. package/dist/lib/sandbox/control/client.js +96 -15
  60. package/dist/lib/sandbox/control/executor.js +3 -3
  61. package/dist/lib/sandbox/control/protocol.js +77 -0
  62. package/dist/lib/sandbox/control/server.js +398 -25
  63. package/dist/lib/sandbox/control/state.js +204 -1
  64. package/dist/lib/server/protocol.js +38 -1
  65. package/dist/lib/task/activity-log.js +8 -1
  66. package/dist/lib/task/artifact-lifecycle.js +33 -5
  67. package/dist/lib/task/capabilities.js +239 -0
  68. package/dist/lib/task/commands/status.js +32 -0
  69. package/dist/lib/task/commit-operation.js +12 -1
  70. package/dist/lib/task/control-authority.js +8 -5
  71. package/dist/lib/task/create-service.js +8 -8
  72. package/dist/lib/task/current-contract.js +5 -1
  73. package/dist/lib/task/decision-details.js +1 -1
  74. package/dist/lib/task/events.js +297 -17
  75. package/dist/lib/task/finalization.js +37 -13
  76. package/dist/lib/task/human-override.js +58 -36
  77. package/dist/lib/task/invalidation-command.js +86 -0
  78. package/dist/lib/task/invalidation.js +260 -0
  79. package/dist/lib/task/ledger-intents.js +37 -0
  80. package/dist/lib/task/local-artifact-finalization.js +374 -0
  81. package/dist/lib/task/orchestration.js +49 -93
  82. package/dist/lib/task/pr-delivery-fact.js +102 -101
  83. package/dist/lib/task/review-fingerprint.js +24 -2
  84. package/dist/lib/task/rework-intent.js +138 -0
  85. package/dist/lib/task/skill-scope.js +23 -0
  86. package/dist/lib/task/task-execution-lock.js +27 -17
  87. package/dist/lib/task/verification-engine.js +43 -16
  88. package/dist/lib/task/verification.js +3 -3
  89. package/dist/lib/task/workflow-warning-intents.js +1 -1
  90. package/dist/lib/task/write.js +11 -0
  91. package/dist/lib/version.js +11 -1
  92. package/dist/lifecycle-build-manifest.json +3 -3
  93. package/lib/agent-clients/next-steps.ts +6 -1
  94. package/lib/decide.ts +3 -3
  95. package/lib/init.ts +1 -1
  96. package/lib/internal/platform-checks.ts +3 -3
  97. package/lib/internal/platform-comment.ts +8 -8
  98. package/lib/internal/platform-context.ts +2 -2
  99. package/lib/internal/platform-issue.ts +7 -8
  100. package/lib/internal/platform-pr-review.ts +9 -9
  101. package/lib/internal/platform-pr.ts +47 -22
  102. package/lib/internal/platform-release-notes.ts +3 -3
  103. package/lib/internal/pr-review-grade.ts +10 -8
  104. package/lib/internal/release-workflow.ts +4 -4
  105. package/lib/internal/sandbox-control.ts +27 -3
  106. package/lib/internal/task-activity.ts +3 -3
  107. package/lib/internal/task-artifact.ts +65 -22
  108. package/lib/internal/task-create.ts +2 -2
  109. package/lib/internal/task-event.ts +6 -3
  110. package/lib/internal/task-finalization.ts +3 -3
  111. package/lib/internal/task-invalidation.ts +39 -0
  112. package/lib/internal/task-ledger.ts +11 -8
  113. package/lib/internal/task-lifecycle.ts +2 -2
  114. package/lib/internal/task-override.ts +6 -6
  115. package/lib/internal/task-review.ts +3 -3
  116. package/lib/internal/task-verify.ts +6 -6
  117. package/lib/internal/task-warning.ts +3 -3
  118. package/lib/platform/adapters.ts +3 -2
  119. package/lib/platform/change-request-git-evidence.ts +36 -9
  120. package/lib/platform/completion-backfill.ts +9 -8
  121. package/lib/platform/context.ts +97 -134
  122. package/lib/platform/github-client.ts +2 -2
  123. package/lib/platform/github-provider.ts +910 -0
  124. package/lib/platform/in-label-sync.ts +208 -0
  125. package/lib/platform/issue-comments.ts +97 -47
  126. package/lib/platform/issues.ts +344 -174
  127. package/lib/platform/metadata-labels.ts +5 -2
  128. package/lib/platform/none-provider.ts +35 -0
  129. package/lib/platform/pr-change-report.ts +645 -0
  130. package/lib/platform/pr-checks.ts +126 -106
  131. package/lib/platform/pr-review.ts +88 -85
  132. package/lib/platform/pr-summary.ts +560 -53
  133. package/lib/platform/provider-bridge.ts +82 -0
  134. package/lib/platform/provider-contract.ts +536 -0
  135. package/lib/platform/provider-loader.ts +307 -0
  136. package/lib/platform/provider-validation.ts +529 -0
  137. package/lib/platform/pull-request-metadata.ts +10 -7
  138. package/lib/platform/pull-requests.ts +826 -543
  139. package/lib/platform/release-notes.ts +76 -59
  140. package/lib/platform/releases.ts +77 -43
  141. package/lib/platform/resource-identity.ts +121 -0
  142. package/lib/platform/task-identities.ts +40 -0
  143. package/lib/platform/types.ts +4 -2
  144. package/lib/platform/verification-required.ts +11 -13
  145. package/lib/platform/verification-sync.ts +112 -292
  146. package/lib/pr-review/evidence-grading.ts +25 -8
  147. package/lib/run/index.ts +57 -22
  148. package/lib/sandbox/control/client.ts +105 -15
  149. package/lib/sandbox/control/executor.ts +4 -4
  150. package/lib/sandbox/control/protocol.ts +113 -0
  151. package/lib/sandbox/control/server.ts +431 -23
  152. package/lib/sandbox/control/state.ts +251 -1
  153. package/lib/server/protocol.ts +36 -1
  154. package/lib/task/activity-log.ts +8 -0
  155. package/lib/task/artifact-lifecycle.ts +32 -6
  156. package/lib/task/capabilities.ts +262 -0
  157. package/lib/task/commands/status.ts +38 -0
  158. package/lib/task/commit-operation.ts +12 -1
  159. package/lib/task/control-authority.ts +11 -8
  160. package/lib/task/create-service.ts +8 -8
  161. package/lib/task/current-contract.ts +6 -1
  162. package/lib/task/decision-details.ts +6 -2
  163. package/lib/task/events.ts +296 -16
  164. package/lib/task/finalization.ts +50 -14
  165. package/lib/task/human-override.ts +70 -46
  166. package/lib/task/invalidation-command.ts +86 -0
  167. package/lib/task/invalidation.ts +332 -0
  168. package/lib/task/ledger-intents.ts +35 -1
  169. package/lib/task/local-artifact-finalization.ts +563 -0
  170. package/lib/task/orchestration.ts +52 -85
  171. package/lib/task/pr-delivery-fact.ts +131 -148
  172. package/lib/task/review-fingerprint.ts +23 -2
  173. package/lib/task/rework-intent.ts +165 -0
  174. package/lib/task/skill-scope.ts +24 -0
  175. package/lib/task/task-execution-lock.ts +23 -14
  176. package/lib/task/verification-engine.ts +52 -17
  177. package/lib/task/verification.ts +4 -4
  178. package/lib/task/workflow-warning-intents.ts +1 -1
  179. package/lib/task/write.ts +19 -0
  180. package/lib/update.ts +1 -1
  181. package/lib/version.ts +15 -1
  182. package/package.json +7 -1
  183. package/templates/.agents/rules/README.en.md +1 -1
  184. package/templates/.agents/rules/README.zh-CN.md +1 -1
  185. package/templates/.agents/rules/issue-pr-commands.en.md +1 -1
  186. package/templates/.agents/rules/issue-pr-commands.zh-CN.md +1 -1
  187. package/templates/.agents/rules/issue-sync.en.md +2 -0
  188. package/templates/.agents/rules/issue-sync.zh-CN.md +2 -0
  189. package/templates/.agents/rules/local-artifact-repair.en.md +9 -2
  190. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +9 -2
  191. package/templates/.agents/rules/pr-sync.en.md +10 -4
  192. package/templates/.agents/rules/pr-sync.zh-CN.md +17 -4
  193. package/templates/.agents/rules/task-short-id.en.md +1 -4
  194. package/templates/.agents/rules/task-short-id.zh-CN.md +1 -3
  195. package/templates/.agents/skills/analyze-task/SKILL.en.md +13 -3
  196. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +10 -3
  197. package/templates/.agents/skills/check-task/SKILL.en.md +16 -37
  198. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -37
  199. package/templates/.agents/skills/code-task/SKILL.en.md +27 -8
  200. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +29 -10
  201. package/templates/.agents/skills/code-task/config/verify.en.json +1 -0
  202. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -0
  203. package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +2 -0
  204. package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +2 -0
  205. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +3 -3
  206. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +3 -3
  207. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +5 -3
  208. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +5 -3
  209. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +4 -3
  210. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +4 -3
  211. package/templates/.agents/skills/complete-task/SKILL.en.md +21 -3
  212. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +20 -3
  213. package/templates/.agents/skills/create-pr/SKILL.en.md +7 -6
  214. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +7 -7
  215. package/templates/.agents/skills/create-pr/config/verify.json +1 -0
  216. package/templates/.agents/skills/create-pr/reference/change-report.en.md +16 -4
  217. package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +16 -4
  218. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +5 -3
  219. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +5 -3
  220. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
  221. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
  222. package/templates/.agents/skills/create-pr/scripts/change-report.mjs +3 -0
  223. package/templates/.agents/skills/plan-task/SKILL.en.md +13 -3
  224. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +10 -3
  225. package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -3
  226. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -3
  227. package/templates/.agents/skills/review-code/SKILL.en.md +4 -4
  228. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +4 -4
  229. package/templates/.agents/skills/review-plan/SKILL.en.md +3 -3
  230. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -3
  231. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +4 -2
  232. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +4 -2
  233. package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
  234. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
  235. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +6 -0
  236. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +6 -0
  237. package/templates/.agents/templates/task.en.md +18 -1
  238. package/templates/.agents/templates/task.zh-CN.md +18 -1
  239. package/templates/.github/workflows/pr-label.yml +7 -26
@@ -8,24 +8,27 @@ import {
8
8
  inspectPlatformPullRequest,
9
9
  resolveExternalPullRequest,
10
10
  skipPlatformPullRequestFact,
11
- syncPlatformPullRequest
11
+ syncPlatformPullRequest,
12
+ syncPlatformPullRequestInLabels
12
13
  } from '../platform/pull-requests.ts';
13
14
  import type { PullRequestResult } from '../platform/pull-requests.ts';
14
- import { summaryContext, syncPullRequestSummary } from '../platform/pr-summary.ts';
15
+ import { reportWrite, summaryContext, syncPullRequestSummary } from '../platform/pr-summary.ts';
15
16
  import type { PlatformResult } from '../platform/types.ts';
16
17
 
17
18
  const USAGE = `Usage: agent-infra-internal platform-pr inspect <task-ref> [--cwd <path>]
18
- agent-infra-internal platform-pr resolve-external <task-ref> --agent <agent> [--pr <N>] [--dry-run] [--cwd <path>]
19
+ agent-infra-internal platform-pr resolve-external <task-ref> --agent <agent> [--pr <token>] [--dry-run] [--cwd <path>]
19
20
  agent-infra-internal platform-pr create <task-ref> --agent <agent> --base <branch> --head <branch> --title-file <path|-> --body-file <path|-> [--draft] [--dry-run] [--cwd <path>]
20
- agent-infra-internal platform-pr bind <task-ref> --pr <N> --agent <agent> [--dry-run] [--cwd <path>]
21
+ agent-infra-internal platform-pr bind <task-ref> --pr <token> --agent <agent> [--dry-run] [--cwd <path>]
21
22
  agent-infra-internal platform-pr skip <task-ref> --agent <agent> [--dry-run] [--cwd <path>]
22
23
  agent-infra-internal platform-pr sync <task-ref> --agent <agent> [--metadata] [--closing-issue] --result <pr_created|pr_reused|no_op> [--dry-run] [--cwd <path>]
24
+ agent-infra-internal platform-pr sync-in-labels --pr <N> [--dry-run] [--cwd <path>]
23
25
  agent-infra-internal platform-pr summary-context <task-ref> [--cwd <path>]
24
- agent-infra-internal platform-pr summary-sync <task-ref> --agent <agent> --body-file <path|-> --result <pr_created|pr_reused|no_op> [--dry-run] [--cwd <path>]
26
+ agent-infra-internal platform-pr change-report <task-ref> --agent <agent> --mechanical-file <path> --precheck-file <path> [--dry-run] [--cwd <path>]
27
+ agent-infra-internal platform-pr summary-sync <task-ref> --agent <agent> --body-file <path|-> --change-report-file <path> --result <pr_created|pr_reused|no_op> [--strict] [--dry-run] [--cwd <path>]
25
28
  `;
26
29
 
27
- const BOOLEAN_FLAGS = new Set(['--draft', '--dry-run', '--metadata', '--closing-issue']);
28
- const VALUE_FLAGS = new Set(['--cwd', '--agent', '--base', '--head', '--title-file', '--body-file', '--pr', '--result']);
30
+ const BOOLEAN_FLAGS = new Set(['--draft', '--dry-run', '--metadata', '--closing-issue', '--strict']);
31
+ const VALUE_FLAGS = new Set(['--cwd', '--agent', '--base', '--head', '--title-file', '--body-file', '--change-report-file', '--mechanical-file', '--precheck-file', '--pr', '--result']);
29
32
 
30
33
  function key(flag: string): string {
31
34
  return flag.slice(2).replace(/-([a-z])/g, (_, letter: string) => letter.toUpperCase());
@@ -63,10 +66,22 @@ function readFile(value: string, cwd: string): string {
63
66
  return value === '-' ? fs.readFileSync(0, 'utf8') : fs.readFileSync(path.resolve(cwd, value), 'utf8');
64
67
  }
65
68
 
66
- function platformPr(args: string[] = []): void {
69
+ async function platformPr(args: string[] = []): Promise<void> {
67
70
  if (args[0] === '--help' || args[0] === '-h') { process.stdout.write(USAGE); return; }
68
71
  const operation = args[0];
69
- if (!operation || !['inspect', 'resolve-external', 'create', 'bind', 'skip', 'sync', 'summary-context', 'summary-sync'].includes(operation)) { fail('a valid operation is required'); return; }
72
+ if (!operation || !['inspect', 'resolve-external', 'create', 'bind', 'skip', 'sync', 'sync-in-labels', 'summary-context', 'change-report', 'summary-sync'].includes(operation)) { fail('a valid operation is required'); return; }
73
+ if (operation === 'sync-in-labels') {
74
+ const parsed = parse(args, 1);
75
+ if (parsed.error) { fail(parsed.error); return; }
76
+ const values = parsed.values;
77
+ const unexpected = Object.keys(values).find((name) => !['cwd', 'pr', 'dryRun'].includes(name));
78
+ if (unexpected) { fail(`sync-in-labels does not accept --${unexpected}`); return; }
79
+ const pr = Number(values.pr);
80
+ if (!Number.isInteger(pr) || pr <= 0) { fail('sync-in-labels requires a positive --pr'); return; }
81
+ const cwd = path.resolve(typeof values.cwd === 'string' ? values.cwd : process.cwd());
82
+ finish(await syncPlatformPullRequestInLabels(pr, { cwd, dryRun: values.dryRun === true }));
83
+ return;
84
+ }
70
85
  const taskRef = args[1];
71
86
  if (!taskRef || taskRef.startsWith('--')) { fail(`${operation} requires a task ref`); return; }
72
87
  const parsed = parse(args, 2);
@@ -80,19 +95,28 @@ function platformPr(args: string[] = []): void {
80
95
  bind: ['cwd', 'agent', 'pr', 'dryRun'],
81
96
  skip: ['cwd', 'agent', 'dryRun'],
82
97
  sync: ['cwd', 'agent', 'metadata', 'closingIssue', 'result', 'dryRun'],
98
+ 'sync-in-labels': ['cwd', 'pr', 'dryRun'],
83
99
  'summary-context': ['cwd'],
84
- 'summary-sync': ['cwd', 'agent', 'bodyFile', 'result', 'dryRun']
100
+ 'change-report': ['cwd', 'agent', 'mechanicalFile', 'precheckFile', 'dryRun'],
101
+ 'summary-sync': ['cwd', 'agent', 'bodyFile', 'changeReportFile', 'result', 'dryRun', 'strict']
85
102
  };
86
103
  const unexpected = Object.keys(values).find((name) => !allowed[operation]!.includes(name));
87
104
  if (unexpected) { fail(`${operation} does not accept --${unexpected}`); return; }
88
- if (operation === 'inspect') { finish(inspectPlatformPullRequest(taskRef, { cwd })); return; }
89
- if (operation === 'summary-context') { finish(summaryContext(taskRef, { cwd })); return; }
105
+ if (operation === 'inspect') { finish(await inspectPlatformPullRequest(taskRef, { cwd })); return; }
106
+ if (operation === 'summary-context') { finish(await summaryContext(taskRef, { cwd })); return; }
90
107
  if (typeof values.agent !== 'string' || !values.agent) { fail(`${operation} requires --agent`); return; }
91
108
  const agent = normalizeAgentToken(values.agent);
92
109
  if (!agent) { fail(`invalid --agent '${values.agent}': ${AGENT_USAGE_HINT}`); return; }
93
110
  values.agent = agent;
111
+ if (operation === 'change-report') {
112
+ if (typeof values.mechanicalFile !== 'string' || typeof values.precheckFile !== 'string') { fail('change-report requires --mechanical-file and --precheck-file'); return; }
113
+ finish(await reportWrite(taskRef, {
114
+ cwd, agent: values.agent, mechanicalFile: values.mechanicalFile, precheckFile: values.precheckFile, dryRun: values.dryRun === true
115
+ }));
116
+ return;
117
+ }
94
118
  if (operation === 'skip') {
95
- finish(skipPlatformPullRequestFact(taskRef, { cwd, agent, dryRun: values.dryRun === true }));
119
+ finish(await skipPlatformPullRequestFact(taskRef, { cwd, agent, dryRun: values.dryRun === true }));
96
120
  return;
97
121
  }
98
122
  const primaryResult = values.result === undefined ? undefined : values.result;
@@ -101,28 +125,29 @@ function platformPr(args: string[] = []): void {
101
125
  return;
102
126
  }
103
127
  if (operation === 'resolve-external') {
104
- const pr = values.pr === undefined ? undefined : Number(values.pr);
105
- if (pr !== undefined && (!Number.isInteger(pr) || pr <= 0)) { fail('resolve-external requires a positive --pr'); return; }
106
- finish(resolveExternalPullRequest(taskRef, { cwd, agent: values.agent, pr, dryRun: values.dryRun === true }));
128
+ const pr = values.pr === undefined ? undefined : values.pr;
129
+ if (pr !== undefined && (typeof pr !== 'string' || !pr)) { fail('resolve-external requires --pr <token>'); return; }
130
+ finish(await resolveExternalPullRequest(taskRef, { cwd, agent: values.agent, pr, dryRun: values.dryRun === true }));
107
131
  return;
108
132
  }
109
133
  if (operation === 'bind') {
110
- const pr = Number(values.pr);
111
- if (!Number.isInteger(pr) || pr <= 0) { fail('bind requires a positive --pr'); return; }
112
- finish(bindPlatformPullRequest(taskRef, { cwd, agent: values.agent, pr, dryRun: values.dryRun === true }));
134
+ const pr = values.pr;
135
+ if (typeof pr !== 'string' || !pr) { fail('bind requires --pr <token>'); return; }
136
+ finish(await bindPlatformPullRequest(taskRef, { cwd, agent: values.agent, pr, dryRun: values.dryRun === true }));
113
137
  return;
114
138
  }
115
139
  if (operation === 'sync') {
116
140
  if (values.metadata !== true && values.closingIssue !== true) { fail('sync requires --metadata or --closing-issue'); return; }
117
141
  if (!primaryResult) { fail('sync requires --result pr_created, pr_reused, or no_op'); return; }
118
- finish(syncPlatformPullRequest(taskRef, { cwd, agent: values.agent, metadata: values.metadata === true, closingIssue: values.closingIssue === true, primaryResult: primaryResult as 'pr_created' | 'pr_reused' | 'no_op', dryRun: values.dryRun === true }));
142
+ finish(await syncPlatformPullRequest(taskRef, { cwd, agent: values.agent, metadata: values.metadata === true, closingIssue: values.closingIssue === true, primaryResult: primaryResult as 'pr_created' | 'pr_reused' | 'no_op', dryRun: values.dryRun === true }));
119
143
  return;
120
144
  }
121
145
  if (operation === 'summary-sync') {
122
146
  if (typeof values.bodyFile !== 'string') { fail('summary-sync requires --body-file'); return; }
147
+ if (typeof values.changeReportFile !== 'string') { fail('summary-sync requires --change-report-file'); return; }
123
148
  if (!primaryResult) { fail('summary-sync requires --result pr_created, pr_reused, or no_op'); return; }
124
149
  try {
125
- finish(syncPullRequestSummary(taskRef, { cwd, agent: values.agent, body: readFile(values.bodyFile, cwd), primaryResult: primaryResult as 'pr_created' | 'pr_reused' | 'no_op', dryRun: values.dryRun === true }));
150
+ finish(await syncPullRequestSummary(taskRef, { cwd, agent: values.agent, body: readFile(values.bodyFile, cwd), changeReportFile: values.changeReportFile, primaryResult: primaryResult as 'pr_created' | 'pr_reused' | 'no_op', dryRun: values.dryRun === true, strict: values.strict === true }));
126
151
  } catch (error) {
127
152
  fail(`unable to read body file: ${error instanceof Error ? error.message : String(error)}`);
128
153
  }
@@ -133,7 +158,7 @@ function platformPr(args: string[] = []): void {
133
158
  return;
134
159
  }
135
160
  try {
136
- finish(createPlatformPullRequest(taskRef, {
161
+ finish(await createPlatformPullRequest(taskRef, {
137
162
  cwd, agent: values.agent, base: values.base, head: values.head,
138
163
  title: readFile(values.titleFile, cwd), body: readFile(values.bodyFile, cwd),
139
164
  draft: values.draft === true, dryRun: values.dryRun === true
@@ -36,14 +36,14 @@ function finish(result: { status: string; [key: string]: unknown }): void {
36
36
  process.exitCode = result.status === 'blocked' ? 2 : result.status === 'failed' ? 1 : 0;
37
37
  }
38
38
 
39
- function platformReleaseNotes(args: string[] = []): void {
39
+ async function platformReleaseNotes(args: string[] = []): Promise<void> {
40
40
  const [action, ...rest] = args;
41
41
  if (action === 'context') {
42
42
  const parsed = parseOptions(rest, ['--cwd', '--history-limit', '--from-tag', '--to-tag', '--branch']);
43
43
  if (!parsed) return finish(invalidInput('invalid context options'));
44
44
  const cwd = path.resolve(parsed.values.get('--cwd') || process.cwd());
45
45
  const historyRaw = parsed.values.get('--history-limit') ?? null;
46
- finish(releaseNoteContext({
46
+ finish(await releaseNoteContext({
47
47
  fromTag: parsed.values.get('--from-tag') || '',
48
48
  toTag: parsed.values.get('--to-tag') || '',
49
49
  branch: parsed.values.get('--branch') || '',
@@ -80,7 +80,7 @@ function platformReleaseNotes(args: string[] = []): void {
80
80
  fs.writeFileSync(resolvedNotesPath, fs.readFileSync(0));
81
81
  }
82
82
  try {
83
- finish(publishReleaseNotes({
83
+ finish(await publishReleaseNotes({
84
84
  tag: parsed.values.get('--tag') || '',
85
85
  title: parsed.values.get('--title') || parsed.values.get('--tag') || '',
86
86
  notesFile: resolvedNotesPath,
@@ -13,7 +13,7 @@ import { inspectPlatformPullRequestByNumber } from '../platform/pull-requests.ts
13
13
  import { verifyInProcess } from '../task/verification-engine.ts';
14
14
 
15
15
  const USAGE = `Usage: agent-infra-internal pr-review-grade decide --input-file <path|-> [--cwd <path>]
16
- agent-infra-internal pr-review-grade resolve-host --pr <N> [--cwd <path>]
16
+ agent-infra-internal pr-review-grade resolve-host --pr <token> [--cwd <path>]
17
17
  agent-infra-internal pr-review-grade verify-artifact --artifact-file <path> [--cwd <path>]
18
18
  `;
19
19
 
@@ -49,7 +49,7 @@ function readInput(value: string, cwd: string): string {
49
49
  return value === '-' ? fs.readFileSync(0, 'utf8') : fs.readFileSync(path.resolve(cwd, value), 'utf8');
50
50
  }
51
51
 
52
- function prReviewGrade(args: string[] = []): void {
52
+ async function prReviewGrade(args: string[] = []): Promise<void> {
53
53
  if (args[0] === '--help' || args[0] === '-h') { process.stdout.write(USAGE); return; }
54
54
  const operation = args[0];
55
55
  if (!operation || !['decide', 'resolve-host', 'verify-artifact'].includes(operation)) { fail('a valid operation is required'); return; }
@@ -85,9 +85,9 @@ function prReviewGrade(args: string[] = []): void {
85
85
  }
86
86
 
87
87
  if (operation === 'resolve-host') {
88
- const pr = Number(values.pr);
89
- if (!Number.isInteger(pr) || pr <= 0) { fail('resolve-host requires a positive --pr'); return; }
90
- const inspected = inspectPlatformPullRequestByNumber(pr, { cwd });
88
+ const pr = typeof values.pr === 'string' ? values.pr : '';
89
+ if (!pr) { fail('resolve-host requires --pr <token>'); return; }
90
+ const inspected = await inspectPlatformPullRequestByNumber(pr, { cwd });
91
91
  if (!inspected.pullRequest) {
92
92
  finish({
93
93
  status: 'failed', changed: false, host: null, candidates: [], closingIssues: [],
@@ -95,12 +95,14 @@ function prReviewGrade(args: string[] = []): void {
95
95
  }, inspected.status === 'blocked' ? 2 : 1);
96
96
  return;
97
97
  }
98
+ const prNumber = inspected.pullRequest.number;
99
+ const prIdentity = inspected.pullRequest.identity || { kind: 'number' as const, value: prNumber };
98
100
  const closingIssues = extractClosingIssueNumbers(inspected.pullRequest.body);
99
- const candidates = collectHostCandidates({ prNumber: pr, closingIssues, workspaceRoot: cwd });
101
+ const candidates = collectHostCandidates({ prNumber, prIdentity, closingIssues, workspaceRoot: cwd });
100
102
  const host: HostResolution = resolveHostFromCandidates(candidates);
101
103
  finish({
102
104
  status: 'ok', changed: false,
103
- pr: { number: pr, baseSha: inspected.pullRequest.base.sha, headSha: inspected.pullRequest.head.sha, state: inspected.pullRequest.state },
105
+ pr: { number: prNumber, identity: prIdentity, baseSha: inspected.pullRequest.base.sha, headSha: inspected.pullRequest.head.sha, state: inspected.pullRequest.state },
104
106
  closingIssues, candidates, host,
105
107
  taskIssueMatches: deriveTaskIssueMatches(host, closingIssues), error: null
106
108
  });
@@ -111,7 +113,7 @@ function prReviewGrade(args: string[] = []): void {
111
113
  const artifactPath = path.resolve(cwd, values.artifactFile);
112
114
  let result: Record<string, unknown>;
113
115
  try {
114
- result = verifyInProcess({
116
+ result = await verifyInProcess({
115
117
  mode: 'checks',
116
118
  skillName: 'review-pr',
117
119
  taskDir: path.dirname(artifactPath),
@@ -251,7 +251,7 @@ async function inspectFacts(cwd: string, version: string): Promise<ReleaseFacts>
251
251
  const local = inspectLocalReleaseFacts(cwd, version);
252
252
  const remoteBranch = branch ? git(cwd, ['ls-remote', '--heads', 'origin', branch]) : null;
253
253
  const remoteTag = git(cwd, ['ls-remote', '--tags', 'origin', `refs/tags/${tag}`]);
254
- const platform = inspectPlatformRelease(tag, { cwd });
254
+ const platform = await inspectPlatformRelease(tag, { cwd });
255
255
  const npm = await inspectNpmChannel(pkg.name, version);
256
256
  const formulaUrl = `https://raw.githubusercontent.com/${config.org}/homebrew-tap/main/Formula/${config.project}.rb`;
257
257
  const homebrew = await inspectHomebrewChannel(formulaUrl, version);
@@ -262,7 +262,7 @@ async function inspectFacts(cwd: string, version: string): Promise<ReleaseFacts>
262
262
  localTagAncestor: local.localTagAncestor,
263
263
  localTagConflict: local.localTagConflict,
264
264
  remoteBranch: Boolean(remoteBranch && remoteBranch.split(/\s+/)[0] === head), remoteTag: Boolean(remoteTag),
265
- githubRelease: platform.platform.type !== 'github'
265
+ githubRelease: platform.error?.code === 'PLATFORM_CAPABILITY_UNSUPPORTED'
266
266
  ? true
267
267
  : platform.status === 'blocked' ? null : Boolean(platform.release?.published),
268
268
  npm: npm.published, homebrew: homebrew.published, smoke,
@@ -324,7 +324,7 @@ async function releaseWorkflow(args: string[] = []): Promise<void> {
324
324
  }
325
325
  if (action === 'prepare') {
326
326
  if (before.phase !== 'unprepared') {
327
- const milestones = reconcileReleaseMilestones(version, { cwd });
327
+ const milestones = await reconcileReleaseMilestones(version, { cwd });
328
328
  process.stdout.write(`${JSON.stringify({ ...milestones, snapshot: before })}\n`);
329
329
  process.exitCode = milestones.status === 'failed' ? 1 : milestones.status === 'blocked' ? 2 : 0;
330
330
  return;
@@ -344,7 +344,7 @@ async function releaseWorkflow(args: string[] = []): Promise<void> {
344
344
  const committed = commitExplicitPaths({ cwd, paths: changedPaths(cwd), message: `chore: release v${version}` });
345
345
  if (committed.status === 'failed') { process.stdout.write(`${JSON.stringify(committed)}\n`); process.exitCode = 1; return; }
346
346
  const tagged = command(cwd, 'git', ['tag', '-a', `v${version}`, '-m', `Release v${version}`]);
347
- const milestones = tagged.status === 0 ? reconcileReleaseMilestones(version, { cwd }) : null;
347
+ const milestones = tagged.status === 0 ? await reconcileReleaseMilestones(version, { cwd }) : null;
348
348
  const snapshot = releaseSnapshot(version, await inspectFacts(cwd, version));
349
349
  const failed = tagged.status !== 0 || milestones?.status === 'failed';
350
350
  const blocked = milestones?.status === 'blocked';
@@ -1,4 +1,5 @@
1
1
  import {
2
+ recoverSandboxControl,
2
3
  requestSandboxControl,
3
4
  requestSandboxTaskFinalization,
4
5
  requestSandboxTaskControl,
@@ -39,6 +40,11 @@ function finalizationErrorStatus(error: { retryable: boolean; code: string }): F
39
40
  return error.retryable ? 'blocked' : 'failed';
40
41
  }
41
42
 
43
+ function writeClientError(error: SandboxControlClientError): void {
44
+ process.stderr.write(`${error.detail.message}\n`);
45
+ if (error.requestId) process.stderr.write(`SANDBOX_CONTROL_REQUEST_ID: ${error.requestId}\n`);
46
+ }
47
+
42
48
  function sandboxFinalizationClient(args: string[]): void {
43
49
  if (args.length !== 4 || !args[0] || args[1] !== 'complete' || args[2] !== '--agent' || !args[3]) {
44
50
  const error = { code: 'TASK_FINALIZATION_PAYLOAD_INVALID', message: 'task ref, complete intent, and --agent are required', retryable: false };
@@ -60,7 +66,7 @@ function sandboxFinalizationClient(args: string[]): void {
60
66
  } catch (error) {
61
67
  if (!(error instanceof SandboxControlClientError)) throw error;
62
68
  writeFinalizationEnvelope(finalizationErrorStatus(error.detail), error.accepted, error.detail);
63
- process.stderr.write(`${error.detail.message}\n`);
69
+ writeClientError(error);
64
70
  process.exitCode = error.detail.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN' ? 1 : error.detail.retryable ? 2 : 1;
65
71
  return;
66
72
  }
@@ -107,6 +113,24 @@ async function sandboxControl(args: string[]): Promise<void> {
107
113
  await runSandboxControlExecutor(request, nonce);
108
114
  return;
109
115
  }
116
+ if (operation === 'recover') {
117
+ if (rest.length !== 1 || !rest[0]) throw new Error('sandbox-control recover requires <request-id>');
118
+ let response;
119
+ try {
120
+ response = recoverSandboxControl(rest[0]);
121
+ } catch (error) {
122
+ if (!(error instanceof SandboxControlClientError)) throw error;
123
+ writeClientError(error);
124
+ process.exitCode = error.detail.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN' ? 1 : error.detail.retryable ? 75 : 1;
125
+ return;
126
+ }
127
+ process.stdout.write(response.stdout);
128
+ process.stderr.write(response.stderr);
129
+ process.exitCode = response.phase === 'rejected'
130
+ ? response.error?.retryable ? 75 : 1
131
+ : response.exitCode ?? 1;
132
+ return;
133
+ }
110
134
  if (operation === 'client') {
111
135
  const [family = '', ...commandArgs] = rest;
112
136
  if (family === 'task-finalization') {
@@ -126,7 +150,7 @@ async function sandboxControl(args: string[]): Promise<void> {
126
150
  }
127
151
  } catch (error) {
128
152
  if (!(error instanceof SandboxControlClientError)) throw error;
129
- process.stderr.write(`${error.detail.message}\n`);
153
+ writeClientError(error);
130
154
  process.exitCode = error.detail.retryable ? 75 : 1;
131
155
  return;
132
156
  }
@@ -140,7 +164,7 @@ async function sandboxControl(args: string[]): Promise<void> {
140
164
  }
141
165
  return;
142
166
  }
143
- throw new Error('Usage: agent-infra-internal sandbox-control serve --manifest <path> | execute --request <path> --nonce <nonce> | client <family> [args...]');
167
+ throw new Error('Usage: agent-infra-internal sandbox-control serve --manifest <path> | execute --request <path> --nonce <nonce> | client <family> [args...] | recover <request-id>');
144
168
  }
145
169
 
146
170
  export { sandboxControl };
@@ -43,7 +43,7 @@ function usageFailure(message: string): void {
43
43
  process.exitCode = 1;
44
44
  }
45
45
 
46
- function taskActivity(args: string[] = []): void {
46
+ async function taskActivity(args: string[] = []): Promise<void> {
47
47
  if (args[0] === '--help' || args[0] === '-h') { process.stdout.write(USAGE); return; }
48
48
  const [taskRef, kind] = args;
49
49
  if (!taskRef || taskRef.startsWith('--') || !kind || !OPERATIONS.has(kind)) {
@@ -94,11 +94,11 @@ function taskActivity(args: string[] = []): void {
94
94
  let result;
95
95
  let humanOverride: unknown = null;
96
96
  try {
97
- result = withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-activity.${kind}`, () => {
97
+ result = await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-activity.${kind}`, async () => {
98
98
  let current = applyPrReviewActivityIntent(values as PrReviewActivityIntent, { lockAlreadyHeld: true });
99
99
  if (current.status !== 'failed' || !values.overrideTicket) return current;
100
100
  if (!values.overrideTarget || !values.overrideScope) { usageFailure('override ticket requires target and scope'); return current; }
101
- const consumed = consumeHumanOverride({
101
+ const consumed = await consumeHumanOverride({
102
102
  taskRef, ticketId: String(values.overrideTicket),
103
103
  failureId: failureId('activity-intent', current.error?.code ?? 'TASK_STATE_MISMATCH'),
104
104
  target: String(values.overrideTarget), scope: String(values.overrideScope)
@@ -1,6 +1,10 @@
1
1
  import { resolveArtifactContext } from '../task/artifact-lifecycle.ts';
2
+ import { finalizeLocalArtifact } from '../task/local-artifact-finalization.ts';
3
+ import type { LocalArtifactFamily } from '../task/local-artifact-finalization.ts';
4
+ import { resolveTaskRef } from '../task/resolve-ref.ts';
5
+ import { loadVerificationConfig } from '../task/verification-config.ts';
2
6
 
3
- const USAGE = `Usage: agent-infra-internal task-artifact <N | TASK-id> inspect --family <family>\n\nInspect one workflow artifact family and print a read-only JSON context.\n`;
7
+ const USAGE = `Usage: agent-infra-internal task-artifact <N | TASK-id> inspect --family <family>\n agent-infra-internal task-artifact <N | TASK-id> finalize-local --family <analysis|plan|code> --artifact <artifact>\n\nInspect workflow artifact context or finalize a local analysis/plan/code artifact without changing task state.\n`;
4
8
 
5
9
  function failUsage(message: string): void {
6
10
  process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'ARTIFACT_PAYLOAD_INVALID', message } })}\n`);
@@ -11,37 +15,76 @@ function failUsage(message: string): void {
11
15
  function taskArtifact(args: string[] = []): void {
12
16
  if (args[0] === '--help' || args[0] === '-h') { process.stdout.write(USAGE); return; }
13
17
  if (args.length < 2) { failUsage('task ref and operation are required'); return; }
14
- if (args[1] !== 'inspect') { failUsage(`unknown operation '${args[1]}'`); return; }
18
+ const operation = args[1];
19
+ if (operation !== 'inspect' && operation !== 'finalize-local') { failUsage(`unknown operation '${operation}'`); return; }
15
20
  let family = '';
21
+ let artifact = '';
16
22
  const seen = new Set<string>();
17
23
  for (let index = 2; index < args.length; index += 1) {
18
24
  const flag = args[index]!;
19
- if (flag !== '--family') { failUsage(`unknown option '${flag}'`); return; }
25
+ if (flag !== '--family' && flag !== '--artifact') { failUsage(`unknown option '${flag}'`); return; }
20
26
  if (seen.has(flag)) { failUsage(`duplicate option '${flag}'`); return; }
21
27
  const value = args[++index];
22
28
  if (!value || value.startsWith('--')) { failUsage(`option '${flag}' requires a value`); return; }
23
29
  seen.add(flag);
24
- family = value;
30
+ if (flag === '--family') family = value;
31
+ else artifact = value;
25
32
  }
26
33
  if (!family) { failUsage("option '--family' is required"); return; }
27
- const result = resolveArtifactContext(args[0]!, family);
28
- const output = family === 'code' && result.codeMode ? {
29
- ...result,
30
- mode: result.codeMode.mode,
31
- code_max: result.codeMode.codeMax,
32
- rev_max: result.codeMode.reviewMax,
33
- verdict: result.codeMode.verdict,
34
- next_round: result.next?.round ?? null,
35
- next_artifact: result.next?.name ?? null,
36
- review_artifact: result.codeMode.reviewArtifact,
37
- implementation_input: result.codeMode.implementationInput,
38
- decision_id: result.codeMode.decisionId,
39
- decision_evidence: result.codeMode.decisionEvidence,
40
- message: result.codeMode.message
41
- } : result;
42
- process.stdout.write(`${JSON.stringify(output)}\n`);
43
- if (result.status === 'refused') process.exitCode = 1;
44
- else if (result.status === 'failed') process.exitCode = 2;
34
+ if (operation === 'inspect') {
35
+ if (artifact) { failUsage("option '--artifact' is only valid for finalize-local"); return; }
36
+ const result = resolveArtifactContext(args[0]!, family);
37
+ const output = family === 'code' && result.codeMode ? {
38
+ ...result,
39
+ mode: result.codeMode.mode,
40
+ code_max: result.codeMode.codeMax,
41
+ rev_max: result.codeMode.reviewMax,
42
+ verdict: result.codeMode.verdict,
43
+ next_round: result.next?.round ?? null,
44
+ next_artifact: result.next?.name ?? null,
45
+ review_artifact: result.codeMode.reviewArtifact,
46
+ implementation_input: result.codeMode.implementationInput,
47
+ decision_id: result.codeMode.decisionId,
48
+ decision_evidence: result.codeMode.decisionEvidence,
49
+ message: result.codeMode.message
50
+ } : result;
51
+ process.stdout.write(`${JSON.stringify(output)}\n`);
52
+ if (result.status === 'refused') process.exitCode = 1;
53
+ else if (result.status === 'failed') process.exitCode = 2;
54
+ return;
55
+ }
56
+ const resolved = resolveTaskRef(args[0]!);
57
+ const repositoryRoot = resolved.ok ? resolved.repoRoot : process.cwd();
58
+ if (!artifact) { failUsage("option '--artifact' is required"); return; }
59
+ if (family !== 'analysis' && family !== 'plan' && family !== 'code') {
60
+ failUsage("finalize-local only supports 'analysis', 'plan', and 'code'");
61
+ return;
62
+ }
63
+ const skillName = family === 'analysis' ? 'analyze-task' : family === 'plan' ? 'plan-task' : 'code-task';
64
+ let config: { requiredSections?: readonly string[]; requiredPatterns?: readonly string[] } = {};
65
+ try {
66
+ const loaded = loadVerificationConfig(repositoryRoot, skillName);
67
+ const artifactConfig = loaded.checks.artifact;
68
+ if (artifactConfig && typeof artifactConfig === 'object' && !Array.isArray(artifactConfig)) {
69
+ const sections = artifactConfig.required_sections;
70
+ const patterns = artifactConfig.required_patterns;
71
+ config = {
72
+ requiredSections: Array.isArray(sections) ? sections.filter((value): value is string => typeof value === 'string') : undefined,
73
+ requiredPatterns: Array.isArray(patterns) ? patterns.filter((value): value is string => typeof value === 'string') : undefined
74
+ };
75
+ }
76
+ } catch {
77
+ // Isolated fixture repositories may not contain the project's verification config.
78
+ }
79
+ const result = finalizeLocalArtifact({
80
+ taskRef: args[0]!,
81
+ family: family as LocalArtifactFamily,
82
+ artifact,
83
+ repoRoot: repositoryRoot,
84
+ ...config
85
+ });
86
+ process.stdout.write(`${JSON.stringify(result)}\n`);
87
+ if (result.status === 'failed') process.exitCode = 1;
45
88
  }
46
89
 
47
90
  export { taskArtifact };
@@ -18,7 +18,7 @@ function output(result: TaskCreateResult): void {
18
18
  process.exitCode = result.status === 'blocked' ? 2 : result.status === 'failed' ? 1 : 0;
19
19
  }
20
20
 
21
- function taskCreate(args: string[]): void {
21
+ async function taskCreate(args: string[]): Promise<void> {
22
22
  const inputIndex = args.indexOf('--input');
23
23
  const input = inputIndex >= 0 ? args[inputIndex + 1] : undefined;
24
24
  if (!input || args.length !== 2 || inputIndex !== 0) {
@@ -46,7 +46,7 @@ function taskCreate(args: string[]): void {
46
46
  process.exitCode = response.exitCode;
47
47
  return;
48
48
  }
49
- output(createTask(candidate, { repoRoot: process.cwd() }));
49
+ output(await createTask(candidate, { repoRoot: process.cwd() }));
50
50
  } catch (error) {
51
51
  if (error instanceof SandboxControlClientError) {
52
52
  output(failed(error.detail.code, error.detail.message, error.detail.retryable));
@@ -13,6 +13,9 @@ Events: ${eventCatalog.join(', ')}
13
13
 
14
14
  const FLAGS: Record<string, keyof TaskEventRequest> = {
15
15
  '--agent': 'agent', '--round': 'round', '--question': 'question', '--artifact': 'artifact',
16
+ '--artifact-sha256': 'artifactSha256', '--semantic-digest': 'semanticDigest',
17
+ '--initiator': 'initiator', '--request-id': 'requestId', '--reason-code': 'reasonCode',
18
+ '--source-finding': 'sourceFinding', '--source-artifact': 'sourceArtifact', '--source-sha256': 'sourceSha256',
16
19
  '--fix-for': 'fixFor', '--implementation-input': 'implementationInput',
17
20
  '--verdict': 'verdict', '--blockers': 'blockers', '--major': 'major',
18
21
  '--minor': 'minor', '--manual-validation': 'manualValidation', '--files-modified': 'filesModified',
@@ -27,7 +30,7 @@ function usageFailure(message: string): void {
27
30
  process.exitCode = 1;
28
31
  }
29
32
 
30
- function taskEvent(args: string[] = []): void {
33
+ async function taskEvent(args: string[] = []): Promise<void> {
31
34
  if (args[0] === '--help' || args[0] === '-h') { process.stdout.write(USAGE); return; }
32
35
  if (args.length < 2) { usageFailure('task ref and event are required'); return; }
33
36
  const request: TaskEventRequest = { taskRef: args[0]!, event: args[1]!, agent: '' };
@@ -66,14 +69,14 @@ function taskEvent(args: string[] = []): void {
66
69
  let result;
67
70
  let humanOverride: unknown = null;
68
71
  try {
69
- result = withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-event.${request.event}`, () => {
72
+ result = await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-event.${request.event}`, async () => {
70
73
  let current = applyTaskEvent(request, { lockAlreadyHeld: true });
71
74
  const values = request as Record<string, unknown>;
72
75
  if (current.status !== 'failed' || !values.overrideTicket) return current;
73
76
  if (!values.overrideTarget || !values.overrideScope) {
74
77
  return { ...current, humanOverride: { status: 'failed', error: { code: 'OVERRIDE_PAYLOAD_INVALID', message: 'override ticket requires target and scope' } } } as typeof current & { humanOverride: unknown };
75
78
  }
76
- const consumed = consumeHumanOverride({
79
+ const consumed = await consumeHumanOverride({
77
80
  taskRef: request.taskRef,
78
81
  ticketId: String(values.overrideTicket),
79
82
  failureId: failureId('task-event', current.error?.code ?? 'EVENT_TRANSITION_INVALID'),
@@ -14,7 +14,7 @@ function envelope(
14
14
  status: 'completed' | 'failed' | 'blocked' | 'unknown',
15
15
  changed: boolean,
16
16
  accepted: boolean,
17
- result: ReturnType<typeof applyTaskFinalization> | null,
17
+ result: Awaited<ReturnType<typeof applyTaskFinalization>> | null,
18
18
  error: FinalizationError | null
19
19
  ): string {
20
20
  return `${JSON.stringify({ version: 1, status, changed, accepted, result, error })}\n`;
@@ -36,7 +36,7 @@ function parseFailure(error: unknown): string {
36
36
  return message.replace(/^TASK_CONTROL_OPERATION_INVALID: /u, '');
37
37
  }
38
38
 
39
- function taskFinalization(args: string[] = []): void {
39
+ async function taskFinalization(args: string[] = []): Promise<void> {
40
40
  if (args[0] === '--help' || args[0] === '-h') { process.stdout.write(USAGE); return; }
41
41
 
42
42
  let operation;
@@ -71,7 +71,7 @@ function taskFinalization(args: string[] = []): void {
71
71
  ...operation,
72
72
  request: { ...operation.request, taskRef: resolved.taskId }
73
73
  };
74
- const result = dispatchTaskControlOperation(
74
+ const result = await dispatchTaskControlOperation(
75
75
  createDirectHostExecutionContext({ repoRoot }),
76
76
  boundOperation
77
77
  );
@@ -0,0 +1,39 @@
1
+ import { reconcileTaskInvalidation } from '../task/invalidation-command.ts';
2
+ import { resolveTaskRef } from '../task/resolve-ref.ts';
3
+ import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
4
+
5
+ const USAGE = 'Usage: agent-infra-internal task-invalidation <task-ref> reconcile [--max-targets <n>] [--dry-run]\n';
6
+
7
+ function taskInvalidation(args: string[] = []): void {
8
+ if (args[0] === '--help' || args[0] === '-h') { process.stdout.write(USAGE); return; }
9
+ const taskRef = args[0];
10
+ if (!taskRef || args[1] !== 'reconcile') {
11
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'INVALIDATION_PAYLOAD_INVALID', message: 'task ref and reconcile are required' } })}\n`);
12
+ process.stderr.write(USAGE);
13
+ process.exitCode = 1;
14
+ return;
15
+ }
16
+ let maxTargets: number | undefined;
17
+ let dryRun = false;
18
+ for (let index = 2; index < args.length; index += 1) {
19
+ const flag = args[index]!;
20
+ if (flag === '--dry-run') { if (dryRun) { process.exitCode = 1; return; } dryRun = true; continue; }
21
+ if (flag !== '--max-targets') { process.exitCode = 1; return; }
22
+ const value = Number(args[++index]);
23
+ if (!Number.isInteger(value) || value < 1 || maxTargets !== undefined) { process.exitCode = 1; return; }
24
+ maxTargets = value;
25
+ }
26
+ const resolved = resolveTaskRef(taskRef);
27
+ if (!resolved.ok) { process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: resolved.code, message: resolved.message } })}\n`); process.exitCode = 1; return; }
28
+ try {
29
+ const result = withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-invalidation.reconcile', () => reconcileTaskInvalidation(taskRef, { ...(maxTargets === undefined ? {} : { maxTargets }), dryRun }));
30
+ process.stdout.write(`${JSON.stringify(result)}\n`);
31
+ if (result.status === 'failed') process.exitCode = 1;
32
+ } catch (error) {
33
+ if (!(error instanceof TaskExecutionLockError)) throw error;
34
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: error.code, message: error.message } })}\n`);
35
+ process.exitCode = 1;
36
+ }
37
+ }
38
+
39
+ export { taskInvalidation };