@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
@@ -15,7 +15,7 @@ import { inspectLifecycleExecution } from './lifecycle-execution.ts';
15
15
  import { applyLedgerIntent } from './ledger-intents.ts';
16
16
  import { bindPlatformIssue } from '../platform/issues.ts';
17
17
  import { bindPlatformPullRequest } from '../platform/pull-requests.ts';
18
- import type { GitHubClient } from '../platform/github-client.ts';
18
+ import type { PlatformClient } from '../platform/context.ts';
19
19
  import { finalizeReviewSummary } from './review-finalization.ts';
20
20
  import { resolveTaskRef } from './resolve-ref.ts';
21
21
  import { DocumentMutationError, parseTable } from './sections.ts';
@@ -25,6 +25,9 @@ import { verifyInProcess } from './verification-engine.ts';
25
25
  import { canonicalTimestamp, writeTask } from './write.ts';
26
26
  import type { TaskFileSystem, TaskWriteMetadata, TaskWriteResult } from './write.ts';
27
27
  import { readPrDeliveryFact } from './pr-delivery-fact.ts';
28
+ import { resourceIdentity, resourceIdentityNumber } from '../platform/provider-bridge.ts';
29
+ import type { ResourceIdentity } from '../platform/resource-identity.ts';
30
+ import { taskIssueIdentity } from '../platform/task-identities.ts';
28
31
 
29
32
  type Eligibility = 'eligible' | 'repair-only' | 'runtime-recovery-only' | 'never-overridable';
30
33
  type OutcomeEffect = 'apply-target' | 'retry-same-intent' | 'record-only' | 'no-write';
@@ -96,8 +99,8 @@ type HumanOverrideOptions = Readonly<{
96
99
  probeIssueNumber?: number;
97
100
  probePullRequestNumber?: number;
98
101
  probeStagingDir?: string;
99
- platformClient?: GitHubClient;
100
- effectExecutor?: (capability: ManualOverrideCapability) => OverrideError | null;
102
+ platformClient?: PlatformClient;
103
+ effectExecutor?: (capability: ManualOverrideCapability) => OverrideError | null | Promise<OverrideError | null>;
101
104
  }>;
102
105
 
103
106
  type FailureSnapshot = Readonly<{
@@ -584,7 +587,7 @@ function snapshotEvidence(
584
587
  content: string,
585
588
  frontmatter: Record<string, unknown> | null,
586
589
  producer: ProducerFailure,
587
- resource: Readonly<{ kind: 'issue' | 'pull-request'; number: number }> | null
590
+ resource: Readonly<{ kind: 'issue' | 'pull-request'; identity: ResourceIdentity; number: number | null }> | null
588
591
  ): string {
589
592
  const stableFrontmatter = frontmatter
590
593
  ? Object.fromEntries(Object.entries(frontmatter).filter(([key]) => !['updated_at', 'agent_infra_version', 'status', 'blocked_at', 'completed_at', 'cancelled_at'].includes(key)))
@@ -609,15 +612,22 @@ function resourceForSnapshot(
609
612
  policy: FailurePolicy,
610
613
  options: HumanOverrideOptions,
611
614
  frontmatter: Record<string, unknown> | null
612
- ): Readonly<{ kind: 'issue' | 'pull-request'; number: number }> | null {
615
+ ): Readonly<{ kind: 'issue' | 'pull-request'; identity: ResourceIdentity; number: number | null }> | null {
613
616
  if (policy.producerId === 'platform.issue') {
614
- const number = options.probeIssueNumber ?? toPositiveInteger(frontmatter?.issue_number);
615
- return number === null ? null : { kind: 'issue', number };
617
+ const identity = options.probeIssueNumber !== undefined
618
+ ? resourceIdentity(options.probeIssueNumber)
619
+ : taskIssueIdentity((frontmatter || {}) as Record<string, string | number | boolean | null>) || (() => {
620
+ const number = toPositiveInteger(frontmatter?.issue_number);
621
+ return number === null ? null : resourceIdentity(number);
622
+ })();
623
+ return identity ? { kind: 'issue', identity, number: resourceIdentityNumber(identity) } : null;
616
624
  }
617
625
  if (policy.producerId === 'platform.pull-request') {
618
626
  const fact = frontmatter ? readPrDeliveryFact(frontmatter) : null;
619
- const number = options.probePullRequestNumber ?? (fact?.status === 'valid' && fact.fact.state === 'bound' ? fact.fact.identity.number : null);
620
- return number === null ? null : { kind: 'pull-request', number };
627
+ const identity = options.probePullRequestNumber !== undefined
628
+ ? resourceIdentity(options.probePullRequestNumber)
629
+ : fact?.status === 'valid' && fact.fact.state === 'bound' ? fact.fact.identity.resource : null;
630
+ return identity ? { kind: 'pull-request', identity, number: resourceIdentityNumber(identity) } : null;
621
631
  }
622
632
  return null;
623
633
  }
@@ -694,41 +704,52 @@ function probeNotObserved(policy: FailurePolicy): OverrideError {
694
704
  return error('OVERRIDE_FAILURE_NOT_PRESENT', `producer ${policy.producerId} did not produce ${policy.code}`);
695
705
  }
696
706
 
697
- function probePlatformFailure(
707
+ async function probePlatformFailure(
698
708
  taskRef: string,
699
709
  policy: FailurePolicy,
700
710
  resolved: ProbeTask,
701
711
  options: HumanOverrideOptions
702
- ): ProducerFailure | OverrideError {
712
+ ): Promise<ProducerFailure | OverrideError> {
703
713
  let frontmatter: ReturnType<typeof parseTypedTaskFrontmatter>;
704
714
  try {
705
715
  frontmatter = parseTypedTaskFrontmatter(fs.readFileSync(path.join(resolved.taskDir, 'task.md'), 'utf8'));
706
716
  } catch (cause) {
707
717
  return error('OVERRIDE_DOCUMENT_INVALID', cause instanceof Error ? cause.message : String(cause));
708
718
  }
709
- const issueNumber = options.probeIssueNumber ?? toPositiveInteger(frontmatter.issue_number);
719
+ const issueIdentity = options.probeIssueNumber !== undefined
720
+ ? resourceIdentity(options.probeIssueNumber)
721
+ : taskIssueIdentity(frontmatter) || (() => {
722
+ const number = toPositiveInteger(frontmatter.issue_number);
723
+ return number === null ? null : resourceIdentity(number);
724
+ })();
710
725
  const fact = readPrDeliveryFact(frontmatter);
711
- const pullRequestNumber = options.probePullRequestNumber ?? (fact.status === 'valid' && fact.fact.state === 'bound' ? fact.fact.identity.number : null);
712
- const operation = policy.producerId === 'platform.issue'
713
- ? issueNumber === null
726
+ const pullRequestIdentity = options.probePullRequestNumber !== undefined
727
+ ? resourceIdentity(options.probePullRequestNumber)
728
+ : fact.status === 'valid' && fact.fact.state === 'bound' ? fact.fact.identity.resource : null;
729
+ const token = (identity: ResourceIdentity | null): string | null => identity ? String(identity.value) : null;
730
+ const issueToken = token(issueIdentity);
731
+ const pullRequestToken = token(pullRequestIdentity);
732
+ const operation = await (policy.producerId === 'platform.issue'
733
+ ? issueToken === null
714
734
  ? null
715
735
  : bindPlatformIssue(taskRef, {
716
- issue: issueNumber,
736
+ issue: issueToken,
717
737
  agent: options.probeOperator ?? 'local-operator',
718
738
  dryRun: true,
719
739
  cwd: resolved.repoRoot,
720
740
  ...(options.platformClient ? { client: options.platformClient } : {})
721
741
  })
722
- : pullRequestNumber === null
742
+ : pullRequestToken === null
723
743
  ? null
724
744
  : bindPlatformPullRequest(taskRef, {
725
- pr: pullRequestNumber,
745
+ pr: pullRequestToken,
726
746
  agent: options.probeOperator ?? 'local-operator',
727
747
  dryRun: true,
728
748
  cwd: resolved.repoRoot,
729
749
  ...(options.platformClient ? { client: options.platformClient } : {})
730
- });
731
- if (!operation) return error('OVERRIDE_PROBE_INPUT_MISSING', `${policy.producerId} requires a bound issue or pull request number`);
750
+ })
751
+ );
752
+ if (!operation) return error('OVERRIDE_PROBE_INPUT_MISSING', `${policy.producerId} requires a bound issue or pull request identity`);
732
753
  if (operation.status !== 'failed' && operation.status !== 'blocked') return probeNotObserved(policy);
733
754
  if (!operation.error) return error('OVERRIDE_PROBE_INPUT_MISSING', `${policy.producerId} returned no platform error evidence`);
734
755
  return {
@@ -766,6 +787,9 @@ function probeTaskEventFailure(taskRef: string, policy: FailurePolicy, resolved:
766
787
  event: candidate.event,
767
788
  agent: 'codex',
768
789
  dryRun: true,
790
+ initiator: 'model',
791
+ requestId: `override-probe:${taskRef}:${candidate.event}`,
792
+ reasonCode: 'user-request',
769
793
  ...(candidate.artifact ? { artifact: candidate.artifact } : {})
770
794
  }, { repoRoot: resolved.repoRoot, lockAlreadyHeld: true });
771
795
  const matched = probeResult(policy, result, resolved.state);
@@ -774,8 +798,8 @@ function probeTaskEventFailure(taskRef: string, policy: FailurePolicy, resolved:
774
798
  return probeNotObserved(policy);
775
799
  }
776
800
 
777
- function probeVerificationFailure(taskRef: string, policy: FailurePolicy, resolved: ProbeTask): ProducerFailure | OverrideError {
778
- const result = verifyTaskEvent(
801
+ async function probeVerificationFailure(taskRef: string, policy: FailurePolicy, resolved: ProbeTask): Promise<ProducerFailure | OverrideError> {
802
+ const result = await verifyTaskEvent(
779
803
  {
780
804
  taskRef,
781
805
  event: 'review-code.completed',
@@ -795,12 +819,12 @@ function latestArtifact(taskDir: string, family: string): string | undefined {
795
819
  ));
796
820
  candidates.sort((a, b) => {
797
821
  const round = (name: string) => Number(/-r(\d+)\.md$/.exec(name)?.[1] ?? 1);
798
- return round(b) - round(a);
799
- });
822
+ return round(b) - round(a);
823
+ });
800
824
  return candidates[0];
801
825
  }
802
826
 
803
- function probeVerificationEngineFailure(policy: FailurePolicy, resolved: ProbeTask): ProducerFailure | OverrideError {
827
+ async function probeVerificationEngineFailure(policy: FailurePolicy, resolved: ProbeTask): Promise<ProducerFailure | OverrideError> {
804
828
  const candidates = [
805
829
  { skillName: 'review-code', family: 'review-code' },
806
830
  { skillName: 'review-plan', family: 'review-plan' },
@@ -814,7 +838,7 @@ function probeVerificationEngineFailure(policy: FailurePolicy, resolved: ProbeTa
814
838
  const artifactFile = candidate.family ? latestArtifact(resolved.taskDir, candidate.family) : undefined;
815
839
  if (candidate.family && !artifactFile) continue;
816
840
  try {
817
- const payload = verifyInProcess({
841
+ const payload = await verifyInProcess({
818
842
  mode: 'gate',
819
843
  skillName: candidate.skillName,
820
844
  taskDir: resolved.taskDir,
@@ -838,12 +862,12 @@ function probeVerificationEngineFailure(policy: FailurePolicy, resolved: ProbeTa
838
862
  return probeNotObserved(policy);
839
863
  }
840
864
 
841
- function probeGenericFailure(
865
+ async function probeGenericFailure(
842
866
  taskRef: string,
843
867
  policy: FailurePolicy,
844
868
  resolved: ProbeTask,
845
869
  options: HumanOverrideOptions = {}
846
- ): ProducerFailure | OverrideError {
870
+ ): Promise<ProducerFailure | OverrideError> {
847
871
  switch (policy.producerId) {
848
872
  case 'task.resolve':
849
873
  return probeNotObserved(policy);
@@ -852,9 +876,9 @@ function probeGenericFailure(
852
876
  case 'task-event':
853
877
  return probeTaskEventFailure(taskRef, policy, resolved);
854
878
  case 'task-verify':
855
- return probeVerificationFailure(taskRef, policy, resolved);
879
+ return await probeVerificationFailure(taskRef, policy, resolved);
856
880
  case 'verification-engine':
857
- return probeVerificationEngineFailure(policy, resolved);
881
+ return await probeVerificationEngineFailure(policy, resolved);
858
882
  case 'activity-intent':
859
883
  return probeResult(policy, applyPrReviewActivityIntent({
860
884
  kind: 'pr-review-start', taskRef, agent: 'codex', artifact: 'pr-review.md', head: '0'.repeat(40), dryRun: true
@@ -889,7 +913,7 @@ function probeGenericFailure(
889
913
  }
890
914
  case 'platform.issue':
891
915
  case 'platform.pull-request':
892
- return probePlatformFailure(taskRef, policy, resolved, options);
916
+ return await probePlatformFailure(taskRef, policy, resolved, options);
893
917
  default:
894
918
  return error('OVERRIDE_PROBE_INPUT_MISSING', `no producer probe is registered for ${policy.producerId}`);
895
919
  }
@@ -899,7 +923,7 @@ function isProducerFailure(value: ProducerFailure | OverrideError): value is Pro
899
923
  return 'observedFacts' in value && 'state' in value && Array.isArray(value.observedFacts);
900
924
  }
901
925
 
902
- function captureFailureSnapshot(taskRef: string, failureIdValue: string, target: string, options: HumanOverrideOptions = {}): FailureSnapshot | OverrideError {
926
+ async function captureFailureSnapshot(taskRef: string, failureIdValue: string, target: string, options: HumanOverrideOptions = {}): Promise<FailureSnapshot | OverrideError> {
903
927
  const policy = policyFor(failureIdValue);
904
928
  if (!policy) return error('OVERRIDE_FAILURE_UNKNOWN', `failure policy '${failureIdValue}' is not registered`);
905
929
  if (!policy.targets.includes(target)) return error('OVERRIDE_TARGET_INVALID', `target '${target}' is not registered for ${failureIdValue}`);
@@ -915,7 +939,7 @@ function captureFailureSnapshot(taskRef: string, failureIdValue: string, target:
915
939
  try { frontmatter = parseTypedTaskFrontmatter(content); } catch { /* probe reports an unconfirmed identity */ }
916
940
  const producer: ProducerFailure | OverrideError = policy.producerId === LIFECYCLE_PRODUCER
917
941
  ? probeLifecycleFailure(taskRef, policy, resolved, options)
918
- : probeGenericFailure(taskRef, policy, resolved, options);
942
+ : await probeGenericFailure(taskRef, policy, resolved, options);
919
943
  if (!isProducerFailure(producer)) return producer;
920
944
  const digest = computeFailureDigest(snapshotEvidence(
921
945
  resolved.taskId,
@@ -984,7 +1008,7 @@ function validateResourceNumbers(request: HumanOverrideRequest): OverrideError |
984
1008
  return null;
985
1009
  }
986
1010
 
987
- function issueHumanOverride(request: HumanOverrideRequest, options: HumanOverrideOptions = {}): HumanOverrideIssueResult {
1011
+ async function issueHumanOverride(request: HumanOverrideRequest, options: HumanOverrideOptions = {}): Promise<HumanOverrideIssueResult> {
988
1012
  const invalid = validateCommon(request);
989
1013
  if (invalid) return failedIssue(invalid.code, invalid.message);
990
1014
  const policy = policyFor(request.failureId);
@@ -1005,7 +1029,7 @@ function issueHumanOverride(request: HumanOverrideRequest, options: HumanOverrid
1005
1029
  const document = readTask(resolved.taskMdPath);
1006
1030
  if ('code' in document) return failedIssue(document.code, document.message, resolved.taskId);
1007
1031
  if (document.frontmatter.id !== resolved.taskId) return failedIssue('OVERRIDE_DOCUMENT_INVALID', 'task frontmatter id does not match resolved task', resolved.taskId);
1008
- const snapshotResult = captureFailureSnapshot(request.taskRef, request.failureId, request.target, {
1032
+ const snapshotResult = await captureFailureSnapshot(request.taskRef, request.failureId, request.target, {
1009
1033
  ...options,
1010
1034
  probeIntent: request.intent,
1011
1035
  probeOperator: request.operator,
@@ -1127,18 +1151,18 @@ function lifecycleRequestFor(
1127
1151
  return { taskRef, intent: 'restore', agent: operator, stagingDir, issueNumber };
1128
1152
  }
1129
1153
 
1130
- function applyOutcomeEffect(
1154
+ async function applyOutcomeEffect(
1131
1155
  request: ConsumeHumanOverrideRequest,
1132
1156
  row: Readonly<Record<string, string>>,
1133
1157
  snapshot: FailureSnapshot,
1134
1158
  outcome: Outcome,
1135
1159
  options: HumanOverrideOptions
1136
- ): OverrideError | null {
1160
+ ): Promise<OverrideError | null> {
1137
1161
  if (outcome.effect === 'no-write' || outcome.effect === 'record-only') return null;
1138
1162
  const intent = outcome.target === 'safe-close' ? 'cancel' : row.intent || request.intent;
1139
1163
  if (!intent && outcome.target === 'continue-local') {
1140
1164
  if (!options.effectExecutor) return error('OVERRIDE_EFFECT_EXECUTOR_MISSING', 'the producer must supply an effect executor for a continue-local outcome');
1141
- return options.effectExecutor({
1165
+ return await options.effectExecutor({
1142
1166
  failureId: request.failureId,
1143
1167
  operator: row.operator ?? 'local-operator',
1144
1168
  reason: row.reason ?? snapshot.message
@@ -1242,7 +1266,7 @@ function snapshotFromTicket(taskId: string, row: Readonly<Record<string, string>
1242
1266
  };
1243
1267
  }
1244
1268
 
1245
- function consumeHumanOverride(request: ConsumeHumanOverrideRequest, options: HumanOverrideOptions = {}): HumanOverrideConsumeResult {
1269
+ async function consumeHumanOverride(request: ConsumeHumanOverrideRequest, options: HumanOverrideOptions = {}): Promise<HumanOverrideConsumeResult> {
1246
1270
  const values = [request.taskRef, request.ticketId, request.failureId, request.target, request.scope];
1247
1271
  if (values.some((value) => !singleLine(value))) {
1248
1272
  return failedConsume('OVERRIDE_PAYLOAD_INVALID', 'consume fields must be non-empty single-line values', request.ticketId || null);
@@ -1278,7 +1302,7 @@ function consumeHumanOverride(request: ConsumeHumanOverrideRequest, options: Hum
1278
1302
  if (isOverrideError(recoverySnapshot)) return failedConsume(recoverySnapshot.code, recoverySnapshot.message, request.ticketId, resolved.taskId);
1279
1303
  snapshot = recoverySnapshot;
1280
1304
  } else {
1281
- const snapshotResult = captureFailureSnapshot(request.taskRef, request.failureId, request.target, {
1305
+ const snapshotResult = await captureFailureSnapshot(request.taskRef, request.failureId, request.target, {
1282
1306
  ...options,
1283
1307
  probeIntent: row.values.intent ? row.values.intent as TaskLifecycleIntent : request.intent,
1284
1308
  probeOperator: row.values.operator,
@@ -1323,7 +1347,7 @@ function consumeHumanOverride(request: ConsumeHumanOverrideRequest, options: Hum
1323
1347
  if (staged.status === 'planned') return failedConsume('OVERRIDE_WRITE_INVALID', 'override writes cannot be planned', request.ticketId, resolved.taskId);
1324
1348
  effectRow = { ...row.values, status: 'consuming', effect: outcome.effect, result: outcome.result, context_id: outcome.contextId, residual: `effect pending: ${outcome.residual}` };
1325
1349
  }
1326
- const effectError = applyOutcomeEffect(request, effectRow, snapshot, outcome, options);
1350
+ const effectError = await applyOutcomeEffect(request, effectRow, snapshot, outcome, options);
1327
1351
  if (effectError) return failedConsume(effectError.code, effectError.message, request.ticketId, resolved.taskId);
1328
1352
  const after = resolveTask(request.taskRef, options);
1329
1353
  if (!after.ok) return failedConsume(after.code, after.message, request.ticketId, after.taskId);
@@ -1359,7 +1383,7 @@ function diagnoseHumanOverride(failure: string, target?: string) {
1359
1383
  };
1360
1384
  }
1361
1385
 
1362
- function diagnoseHumanOverrideForTask(
1386
+ async function diagnoseHumanOverrideForTask(
1363
1387
  taskRef: string,
1364
1388
  failure?: string,
1365
1389
  target?: string,
@@ -1403,12 +1427,12 @@ function diagnoseHumanOverrideForTask(
1403
1427
  error: error('OVERRIDE_FAILURE_UNKNOWN', `failure policy '${failure}' is not registered`)
1404
1428
  };
1405
1429
  }
1406
- const probes = policies
1430
+ const probes = await Promise.all(policies
1407
1431
  .filter((policy) => !target || policy.targets.includes(target))
1408
- .map((policy) => {
1432
+ .map(async (policy) => {
1409
1433
  const producer = policy.producerId === LIFECYCLE_PRODUCER
1410
1434
  ? probeLifecycleFailure(taskRef, policy, resolved, options)
1411
- : probeGenericFailure(taskRef, policy, resolved, options);
1435
+ : await probeGenericFailure(taskRef, policy, resolved, options);
1412
1436
  const observed = isProducerFailure(producer);
1413
1437
  return {
1414
1438
  failureId: policy.id,
@@ -1422,7 +1446,7 @@ function diagnoseHumanOverrideForTask(
1422
1446
  message: observed ? producer.message : producer.message,
1423
1447
  errorCode: observed ? null : producer.code
1424
1448
  };
1425
- });
1449
+ }));
1426
1450
  const blockedBy = probes.filter((probe) => probe.status === 'observed');
1427
1451
  return {
1428
1452
  status: 'ready' as const,
@@ -0,0 +1,86 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ import { parseArtifactReceipts, sha256File } from './artifact-receipts.ts';
5
+ import { invalidationMutation, parseInvalidationDocument, reconcileInvalidation } from './invalidation.ts';
6
+ import { resolveTaskRef } from './resolve-ref.ts';
7
+ import { writeTask } from './write.ts';
8
+ import type { TaskWriteOptions } from './write.ts';
9
+
10
+ type InvalidationCommandOptions = TaskWriteOptions & { now?: () => string; maxTargets?: number; dryRun?: boolean };
11
+ type InvalidationCommandResult = {
12
+ status: 'applied' | 'planned' | 'no-op' | 'failed';
13
+ changed: boolean;
14
+ taskId: string | null;
15
+ processed: number;
16
+ remaining: number;
17
+ error: { code: string; message: string } | null;
18
+ };
19
+
20
+ function reconcileTaskInvalidation(taskRef: string, options: InvalidationCommandOptions = {}): InvalidationCommandResult {
21
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
22
+ if (!resolved.ok) return { status: 'failed', changed: false, taskId: resolved.taskId, processed: 0, remaining: 0, error: { code: resolved.code, message: resolved.message } };
23
+ let content: string;
24
+ try { content = fs.readFileSync(resolved.taskMdPath, 'utf8'); }
25
+ catch (error) { return { status: 'failed', changed: false, taskId: resolved.taskId, processed: 0, remaining: 0, error: { code: 'TASK_READ_FAILED', message: error instanceof Error ? error.message : String(error) } }; }
26
+ const parsed = parseInvalidationDocument(content);
27
+ if (!parsed.ok) return { status: 'failed', changed: false, taskId: resolved.taskId, processed: 0, remaining: 0, error: { code: parsed.code, message: parsed.message } };
28
+ if (!parsed.present) return { status: 'no-op', changed: false, taskId: resolved.taskId, processed: 0, remaining: 0, error: null };
29
+ const beforePending = parsed.document.targets.filter((target) => target.status !== 'completed').length;
30
+ let receipts;
31
+ try { receipts = parseArtifactReceipts(content).rows; }
32
+ catch (error) {
33
+ if (parsed.document.targets.some((target) => target.status !== 'completed' && target.targetKind === 'receipt')) {
34
+ return {
35
+ status: 'failed', changed: false, taskId: resolved.taskId, processed: 0, remaining: beforePending,
36
+ error: { code: 'INVALIDATION_TARGET_HASH_CONFLICT', message: error instanceof Error ? error.message : String(error) }
37
+ };
38
+ }
39
+ receipts = [];
40
+ }
41
+ for (const target of parsed.document.targets) {
42
+ if (target.status === 'completed') continue;
43
+ if (target.targetKind === 'receipt') {
44
+ const receipt = receipts.find((candidate) => candidate.output === target.targetArtifact);
45
+ if (!receipt || receipt.inputSha256 !== target.targetSha256) {
46
+ return {
47
+ status: 'failed', changed: false, taskId: resolved.taskId, processed: 0, remaining: beforePending,
48
+ error: { code: 'INVALIDATION_TARGET_HASH_CONFLICT', message: `invalidation receipt target '${target.targetArtifact}' changed before reconcile` }
49
+ };
50
+ }
51
+ continue;
52
+ }
53
+ const targetPath = path.join(resolved.taskDir, target.targetArtifact);
54
+ try {
55
+ const stat = fs.lstatSync(targetPath);
56
+ if (!stat.isFile() || stat.isSymbolicLink() || sha256File(targetPath) !== target.targetSha256) {
57
+ return {
58
+ status: 'failed', changed: false, taskId: resolved.taskId, processed: 0, remaining: beforePending,
59
+ error: { code: 'INVALIDATION_TARGET_HASH_CONFLICT', message: `invalidation target '${target.targetArtifact}' changed before reconcile` }
60
+ };
61
+ }
62
+ } catch {
63
+ return {
64
+ status: 'failed', changed: false, taskId: resolved.taskId, processed: 0, remaining: beforePending,
65
+ error: { code: 'INVALIDATION_TARGET_HASH_CONFLICT', message: `invalidation target '${target.targetArtifact}' is unavailable before reconcile` }
66
+ };
67
+ }
68
+ }
69
+ const reconciled = reconcileInvalidation(parsed.document, (options.now ?? (() => new Date().toISOString()))(), options.maxTargets);
70
+ if (!reconciled.ok) return { status: 'failed', changed: false, taskId: resolved.taskId, processed: 0, remaining: beforePending, error: { code: reconciled.code, message: reconciled.message } };
71
+ if (!reconciled.changed) return { status: 'no-op', changed: false, taskId: resolved.taskId, processed: 0, remaining: beforePending, error: null };
72
+ const result = writeTask({
73
+ taskRef, expectedState: resolved.state, dryRun: options.dryRun,
74
+ mutations: [invalidationMutation(content, reconciled.document)]
75
+ }, { ...options, invalidationContext: 'reconcile', taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state } });
76
+ if (result.status === 'failed') return { status: 'failed', changed: false, taskId: resolved.taskId, processed: beforePending - reconciled.document.targets.filter((target) => target.status !== 'completed').length, remaining: reconciled.document.targets.filter((target) => target.status !== 'completed').length, error: result.error };
77
+ return {
78
+ status: result.status, changed: result.changed, taskId: resolved.taskId,
79
+ processed: beforePending - reconciled.document.targets.filter((target) => target.status !== 'completed').length,
80
+ remaining: reconciled.document.targets.filter((target) => target.status !== 'completed').length,
81
+ error: null
82
+ };
83
+ }
84
+
85
+ export { reconcileTaskInvalidation };
86
+ export type { InvalidationCommandOptions, InvalidationCommandResult };