@fitlab-ai/agent-infra 0.9.15 → 0.11.0

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 (224) hide show
  1. package/README.md +2 -6
  2. package/README.zh-CN.md +2 -6
  3. package/bin/internal-cli.sh +0 -13
  4. package/dist/bin/internal-cli.js +11 -49
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +6 -2
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +43 -3
  7. package/dist/lib/fs/durable-write.js +2 -0
  8. package/dist/lib/internal/cli-route-inventory.js +3 -4
  9. package/dist/lib/internal/manual-validation.js +17 -8
  10. package/dist/lib/internal/platform-comment.js +60 -2
  11. package/dist/lib/internal/task-artifact.js +1 -1
  12. package/dist/lib/internal/task-finalization.js +2 -2
  13. package/dist/lib/internal/task-lifecycle.js +4 -4
  14. package/dist/lib/internal/task-operation-registry.js +22 -13
  15. package/dist/lib/internal/task-orchestration.js +2 -2
  16. package/dist/lib/internal/task-review.js +12 -7
  17. package/dist/lib/platform/activity-recovery.js +159 -0
  18. package/dist/lib/platform/issue-comments.js +89 -31
  19. package/dist/lib/platform/issues.js +1 -1
  20. package/dist/lib/platform/pr-change-report.js +5 -4
  21. package/dist/lib/platform/pr-summary.js +64 -32
  22. package/dist/lib/platform/pull-requests.js +3 -3
  23. package/dist/lib/platform/task-comment-projection.js +70 -0
  24. package/dist/lib/platform/task-identities.js +2 -9
  25. package/dist/lib/platform/task-recovery.js +55 -0
  26. package/dist/lib/platform/verification-sync.js +45 -7
  27. package/dist/lib/pr-review/evidence-grading.js +4 -8
  28. package/dist/lib/sandbox/commands/rm.js +21 -2
  29. package/dist/lib/sandbox/control/executor.js +4 -2
  30. package/dist/lib/sandbox/control/server.js +29 -9
  31. package/dist/lib/sandbox/control/state.js +7 -1
  32. package/dist/lib/sandbox/control/task-workflow.js +1 -1
  33. package/dist/lib/sandbox/control/workflow-executor.js +87 -35
  34. package/dist/lib/sandbox/index.js +1 -2
  35. package/dist/lib/sandbox/removal.js +116 -1011
  36. package/dist/lib/sandbox/workspace-identity.js +17 -1
  37. package/dist/lib/server/process-state.js +17 -5
  38. package/dist/lib/task/artifact-command.js +11 -30
  39. package/dist/lib/task/artifact-operations.js +17 -231
  40. package/dist/lib/task/artifact-recovery.js +476 -0
  41. package/dist/lib/task/artifact-repair-intent.js +90 -39
  42. package/dist/lib/task/commit-operation.js +31 -4
  43. package/dist/lib/task/control-authority.js +39 -8
  44. package/dist/lib/task/control-recovery.js +14 -2
  45. package/dist/lib/task/create-service.js +1 -8
  46. package/dist/lib/task/delegation-receipts.js +64 -3
  47. package/dist/lib/task/events.js +165 -23
  48. package/dist/lib/task/human-override.js +5 -8
  49. package/dist/lib/task/intermediate-cleanup.js +91 -8
  50. package/dist/lib/task/lifecycle-recovery.js +549 -0
  51. package/dist/lib/task/lifecycle.js +12 -3
  52. package/dist/lib/task/local-artifact-finalization.js +212 -123
  53. package/dist/lib/task/manual-validation-receipt.js +5 -1
  54. package/dist/lib/task/orchestration.js +52 -2
  55. package/dist/lib/task/pr-delivery-fact.js +12 -56
  56. package/dist/lib/task/recovery-warning.js +19 -0
  57. package/dist/lib/task/review-command.js +3 -2
  58. package/dist/lib/task/review-finalization.js +294 -74
  59. package/dist/lib/{host-control → task}/secure-fs.js +8 -19
  60. package/dist/lib/task/short-id.js +15 -7
  61. package/dist/lib/task/task-execution-lock.js +1 -1
  62. package/dist/lib/task/verification-engine.js +3 -14
  63. package/dist/lib/task/workflow-command.js +2 -1
  64. package/dist/lib/task/workflow-dispatch.js +161 -0
  65. package/dist/lib/version.js +1 -11
  66. package/dist/lifecycle-build-manifest.json +3 -3
  67. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +7 -2
  68. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +55 -3
  69. package/lib/fs/durable-write.ts +1 -0
  70. package/lib/internal/cli-route-inventory.ts +3 -4
  71. package/lib/internal/manual-validation.ts +17 -8
  72. package/lib/internal/platform-comment.ts +53 -2
  73. package/lib/internal/task-artifact.ts +1 -1
  74. package/lib/internal/task-finalization.ts +1 -1
  75. package/lib/internal/task-lifecycle.ts +5 -4
  76. package/lib/internal/task-operation-registry.ts +22 -13
  77. package/lib/internal/task-orchestration.ts +1 -1
  78. package/lib/internal/task-review.ts +11 -7
  79. package/lib/platform/activity-recovery.ts +179 -0
  80. package/lib/platform/issue-comments.ts +105 -30
  81. package/lib/platform/issues.ts +1 -1
  82. package/lib/platform/pr-change-report.ts +7 -5
  83. package/lib/platform/pr-summary.ts +64 -32
  84. package/lib/platform/pull-requests.ts +3 -3
  85. package/lib/platform/task-comment-projection.ts +79 -0
  86. package/lib/platform/task-identities.ts +3 -14
  87. package/lib/platform/task-recovery.ts +54 -0
  88. package/lib/platform/verification-sync.ts +48 -11
  89. package/lib/pr-review/evidence-grading.ts +4 -9
  90. package/lib/sandbox/commands/rm.ts +34 -2
  91. package/lib/sandbox/control/executor.ts +4 -2
  92. package/lib/sandbox/control/protocol.ts +6 -1
  93. package/lib/sandbox/control/server.ts +30 -10
  94. package/lib/sandbox/control/state.ts +9 -1
  95. package/lib/sandbox/control/task-workflow.ts +1 -1
  96. package/lib/sandbox/control/workflow-executor.ts +106 -35
  97. package/lib/sandbox/index.ts +1 -2
  98. package/lib/sandbox/removal.ts +126 -1128
  99. package/lib/sandbox/workspace-identity.ts +19 -3
  100. package/lib/server/process-state.ts +26 -5
  101. package/lib/task/artifact-command.ts +12 -29
  102. package/lib/task/artifact-operations.ts +22 -252
  103. package/lib/task/artifact-recovery.ts +572 -0
  104. package/lib/task/artifact-repair-intent.ts +126 -51
  105. package/lib/task/commit-operation.ts +35 -4
  106. package/lib/task/control-authority.ts +48 -15
  107. package/lib/task/control-recovery.ts +13 -1
  108. package/lib/task/create-service.ts +1 -10
  109. package/lib/task/delegation-receipts.ts +73 -2
  110. package/lib/task/events.ts +188 -24
  111. package/lib/task/human-override.ts +5 -8
  112. package/lib/task/intermediate-cleanup.ts +66 -12
  113. package/lib/task/lifecycle-recovery.ts +691 -0
  114. package/lib/task/lifecycle.ts +13 -4
  115. package/lib/task/local-artifact-finalization.ts +249 -131
  116. package/lib/task/manual-validation-receipt.ts +5 -1
  117. package/lib/task/orchestration.ts +65 -0
  118. package/lib/task/pr-delivery-fact.ts +15 -62
  119. package/lib/task/recovery-warning.ts +21 -0
  120. package/lib/task/review-command.ts +2 -1
  121. package/lib/task/review-finalization.ts +298 -84
  122. package/lib/{host-control → task}/secure-fs.ts +10 -32
  123. package/lib/task/short-id.ts +27 -5
  124. package/lib/task/task-execution-lock.ts +1 -1
  125. package/lib/task/verification-engine.ts +3 -17
  126. package/lib/task/verification-types.ts +0 -2
  127. package/lib/task/workflow-command.ts +2 -1
  128. package/lib/task/workflow-dispatch.ts +171 -0
  129. package/lib/version.ts +1 -15
  130. package/package.json +1 -1
  131. package/templates/.agents/rules/create-issue.en.md +1 -1
  132. package/templates/.agents/rules/create-issue.zh-CN.md +1 -1
  133. package/templates/.agents/rules/issue-sync.en.md +12 -2
  134. package/templates/.agents/rules/issue-sync.zh-CN.md +13 -5
  135. package/templates/.agents/rules/local-artifact-repair.en.md +47 -39
  136. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +45 -36
  137. package/templates/.agents/skills/analyze-task/SKILL.en.md +6 -6
  138. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
  139. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -1
  140. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -1
  141. package/templates/.agents/skills/block-task/SKILL.en.md +2 -2
  142. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
  143. package/templates/.agents/skills/block-task/config/verify.json +1 -1
  144. package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
  145. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
  146. package/templates/.agents/skills/cancel-task/config/verify.json +1 -1
  147. package/templates/.agents/skills/code-task/SKILL.en.md +7 -7
  148. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +8 -8
  149. package/templates/.agents/skills/code-task/config/verify.en.json +1 -1
  150. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -1
  151. package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
  152. package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
  153. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +1 -1
  154. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +1 -1
  155. package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
  156. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
  157. package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
  158. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
  159. package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
  160. package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
  161. package/templates/.agents/skills/create-pr/SKILL.en.md +2 -2
  162. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +2 -2
  163. package/templates/.agents/skills/create-pr/config/verify.json +1 -1
  164. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +2 -2
  165. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +2 -2
  166. package/templates/.agents/skills/create-task/SKILL.en.md +5 -5
  167. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -5
  168. package/templates/.agents/skills/create-task/config/verify.json +1 -1
  169. package/templates/.agents/skills/import-issue/SKILL.en.md +8 -8
  170. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +8 -8
  171. package/templates/.agents/skills/import-issue/config/verify.json +2 -2
  172. package/templates/.agents/skills/plan-task/SKILL.en.md +4 -4
  173. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -4
  174. package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
  175. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
  176. package/templates/.agents/skills/restore-task/SKILL.en.md +11 -6
  177. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +11 -6
  178. package/templates/.agents/skills/restore-task/config/verify.json +1 -1
  179. package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -3
  180. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -3
  181. package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -1
  182. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -1
  183. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +7 -7
  184. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +6 -6
  185. package/templates/.agents/skills/review-code/SKILL.en.md +5 -4
  186. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -4
  187. package/templates/.agents/skills/review-code/config/verify.en.json +1 -1
  188. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -1
  189. package/templates/.agents/skills/review-code/reference/report-template.en.md +12 -12
  190. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +12 -12
  191. package/templates/.agents/skills/review-plan/SKILL.en.md +3 -3
  192. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -3
  193. package/templates/.agents/skills/review-plan/config/verify.en.json +1 -1
  194. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -1
  195. package/templates/.agents/skills/review-plan/reference/report-template.en.md +7 -7
  196. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +7 -7
  197. package/templates/.agents/skills/review-pr/config/verify.en.json +1 -1
  198. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -1
  199. package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +3 -3
  200. package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +3 -3
  201. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +1 -1
  202. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +1 -1
  203. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  204. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  205. package/templates/.agents/templates/task.en.md +1 -1
  206. package/templates/.agents/templates/task.zh-CN.md +1 -1
  207. package/templates/.claude/commands/import-issue.en.md +1 -1
  208. package/templates/.claude/commands/import-issue.zh-CN.md +1 -1
  209. package/templates/.claude/commands/restore-task.en.md +1 -1
  210. package/templates/.claude/commands/restore-task.zh-CN.md +1 -1
  211. package/dist/lib/host-control/audit.js +0 -19
  212. package/dist/lib/host-control/client.js +0 -142
  213. package/dist/lib/host-control/command.js +0 -49
  214. package/dist/lib/host-control/path.js +0 -160
  215. package/dist/lib/host-control/server.js +0 -159
  216. package/dist/lib/host-control/service.js +0 -112
  217. package/dist/lib/internal/host-control.js +0 -28
  218. package/lib/host-control/audit.ts +0 -22
  219. package/lib/host-control/client.ts +0 -172
  220. package/lib/host-control/command.ts +0 -60
  221. package/lib/host-control/path.ts +0 -167
  222. package/lib/host-control/server.ts +0 -180
  223. package/lib/host-control/service.ts +0 -133
  224. package/lib/internal/host-control.ts +0 -28
@@ -16,13 +16,14 @@ import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-
16
16
  import { captureTaskWriteMetadata, writeTask } from './write.js';
17
17
  import { allowsManualOverride } from './guard-override.js';
18
18
  import { consumeLocalArtifactFinalizationIntent, readLocalArtifactFinalizationIntent, validateLocalArtifact } from './local-artifact-finalization.js';
19
- import { writeArtifactRepairIntent } from './artifact-repair-intent.js';
19
+ import { writeArtifactRecoveryIntent, readArtifactRecoveryIntent } from './artifact-repair-intent.js';
20
+ import { reconcileArtifactRecovery, recoveryContextFromIntent, sha256Content } from './artifact-recovery.js';
20
21
  import { buildLifecycleFacts, canStart } from './capabilities.js';
21
22
  import { createInvalidationOperation, invalidationMutation, parseInvalidationDocument, targetIdFor, upsertInvalidation } from './invalidation.js';
22
23
  import { consumeReworkIntents, parseReworkIntentDocument, reworkIntentMutation, supersedeReworkIntents } from './rework-intent.js';
23
24
  import { ARTIFACT_FAMILIES, expectedQualificationRelations, parseQualificationAudit, parseTaskQualification, upstreamArtifactDigest, validateQualificationAudit } from './qualification-audit.js';
24
25
  import { getArtifactSchema } from './artifact-schema.js';
25
- import { inspectArtifactContract } from './artifact-operations.js';
26
+ import { canonicalSemanticDigest, inspectArtifactContract } from './artifact-operations.js';
26
27
  import { consumeLifecycleRecoveryAttestation, lifecycleRecoveryAttestationDigest, validateLifecycleRecoveryAttestation } from './control-authority.js';
27
28
  import { readManualValidationCompletion } from './manual-validation-completion.js';
28
29
  const eventCatalog = [
@@ -314,6 +315,59 @@ function openStartedIdentity(rows, family) {
314
315
  function reviewInputFamily(family) {
315
316
  return family === 'review-analysis' ? 'analysis' : family === 'review-plan' ? 'plan' : 'code';
316
317
  }
318
+ function reconcileReviewCompletionReplay(repoRoot, taskDir, taskId, family, artifact, round) {
319
+ const stage = REVIEW_LEDGER_STAGES[family];
320
+ if (!stage)
321
+ return null;
322
+ const reviewFamily = family;
323
+ try {
324
+ let intent = readArtifactRecoveryIntent(repoRoot, taskId, reviewFamily, artifact);
325
+ if (!intent)
326
+ return null;
327
+ if (intent.state === 'commit-started') {
328
+ const reconciled = reconcileArtifactRecovery(recoveryContextFromIntent(repoRoot, taskDir, intent), { lockAlreadyHeld: true });
329
+ if (reconciled.status !== 'passed' && reconciled.status !== 'consumed') {
330
+ return {
331
+ code: 'EVENT_ARTIFACT_CONFLICT',
332
+ message: `review finalizer recovery remains ${reconciled.status} for ${artifact}`
333
+ };
334
+ }
335
+ intent = reconciled.intent;
336
+ }
337
+ if (!['passed', 'consumption-started', 'consumed'].includes(intent.state)) {
338
+ return {
339
+ code: 'EVENT_ARTIFACT_CONFLICT',
340
+ message: `review finalizer provenance is incomplete for ${artifact}`
341
+ };
342
+ }
343
+ const validated = validateCompletedArtifact(taskDir, FAMILY[family].artifact, artifact, round);
344
+ if (!validated.ok) {
345
+ return { code: 'EVENT_ARTIFACT_CONFLICT', message: validated.error.message };
346
+ }
347
+ const content = fs.readFileSync(validated.artifact.path, 'utf8');
348
+ const expectedRequestId = `review-finalize:${taskId}:${stage}:${round}`;
349
+ if (intent.round !== round
350
+ || intent.requestId !== expectedRequestId
351
+ || intent.finalArtifactSha256 !== sha256Content(content)
352
+ || intent.finalSemanticDigest !== canonicalSemanticDigest(content)) {
353
+ return {
354
+ code: 'EVENT_ARTIFACT_CONFLICT',
355
+ message: `review finalizer provenance does not match ${artifact}`
356
+ };
357
+ }
358
+ if (intent.state === 'passed' || intent.state === 'consumption-started') {
359
+ const consumed = { ...intent, state: 'consumed', updatedAt: Date.now() };
360
+ writeArtifactRecoveryIntent(repoRoot, consumed, { expected: intent });
361
+ }
362
+ return null;
363
+ }
364
+ catch (error) {
365
+ return {
366
+ code: 'EVENT_ARTIFACT_CONFLICT',
367
+ message: `review finalizer recovery could not be reconciled for ${artifact}: ${error instanceof Error ? error.message : String(error)}`
368
+ };
369
+ }
370
+ }
317
371
  function relationForStartedInput(family, input) {
318
372
  if (family.startsWith('review-')) {
319
373
  return input.family === reviewInputFamily(family) ? 'reviewed-input' : 'approval-context';
@@ -715,6 +769,8 @@ function applyTaskEventUnlocked(request, options = {}) {
715
769
  const row = manual ? openRows.at(-1) : matchingRows[0];
716
770
  let completedArtifact = null;
717
771
  let localFinalizationIntent = null;
772
+ let reviewFinalizationIntent = null;
773
+ let reviewContent = null;
718
774
  if (eventIdentity.phase === 'started' && row) {
719
775
  if (row.agent !== normalized.agent)
720
776
  return failed(normalized, { code: 'EVENT_LOG_CONFLICT', message: 'open started event has a different agent' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
@@ -729,8 +785,14 @@ function applyTaskEventUnlocked(request, options = {}) {
729
785
  if (eventIdentity.phase === 'started' && !manual && completedRows.length > 0)
730
786
  return failed(normalized, { code: 'EVENT_ALREADY_COMPLETED', message: 'event identity is already completed' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
731
787
  const done = completedRows.find((item) => item.note === eventIdentity.note);
732
- if (eventIdentity.phase === 'completed' && done)
788
+ if (eventIdentity.phase === 'completed' && done) {
789
+ if (!normalized.dryRun && eventIdentity.family.startsWith('review-') && normalized.artifact && normalized.round) {
790
+ const recoveryError = reconcileReviewCompletionReplay(resolved.repoRoot, resolved.taskDir, resolved.taskId, eventIdentity.family, normalized.artifact, normalized.round);
791
+ if (recoveryError)
792
+ return failed(normalized, recoveryError, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
793
+ }
733
794
  return successNoOp(normalized, resolved.taskId, resolved.taskMdPath, currentStep, eventIdentity, done.done, frontmatter, artifactContext);
795
+ }
734
796
  if (eventIdentity.phase === 'completed' && completedRows.length > 0 && openRows.length === 0)
735
797
  return failed(normalized, { code: 'EVENT_LOG_CONFLICT', message: 'event identity is already completed with a different payload' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
736
798
  if (eventIdentity.phase === 'completed' && !row?.started && !allowsManualOverride(options.manualOverride, 'task-event', 'EVENT_START_MISSING'))
@@ -746,7 +808,6 @@ function applyTaskEventUnlocked(request, options = {}) {
746
808
  return failed(normalized, validationError, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
747
809
  }
748
810
  if (eventIdentity.family.startsWith('review-')) {
749
- let reviewContent;
750
811
  try {
751
812
  reviewContent = fs.readFileSync(completedArtifact.path, 'utf8');
752
813
  }
@@ -775,6 +836,20 @@ function applyTaskEventUnlocked(request, options = {}) {
775
836
  const localFamily = eventIdentity.family === 'analyze'
776
837
  ? 'analysis'
777
838
  : eventIdentity.family === 'plan' ? 'plan' : 'code';
839
+ try {
840
+ localFinalizationIntent = readLocalArtifactFinalizationIntent(resolved.repoRoot, resolved.taskId, localFamily, completedArtifact.name);
841
+ if (localFinalizationIntent?.state === 'commit-started') {
842
+ const reconciled = reconcileArtifactRecovery(recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, localFinalizationIntent), { lockAlreadyHeld: true });
843
+ if (reconciled.status === 'passed' || reconciled.status === 'consumed')
844
+ localFinalizationIntent = reconciled.intent;
845
+ }
846
+ }
847
+ catch (error) {
848
+ return failed(normalized, {
849
+ code: 'EVENT_ARTIFACT_CONFLICT',
850
+ message: `local finalizer recovery could not be reconciled for ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}`
851
+ }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
852
+ }
778
853
  let artifactContent;
779
854
  try {
780
855
  artifactContent = fs.readFileSync(completedArtifact.path, 'utf8');
@@ -806,33 +881,23 @@ function applyTaskEventUnlocked(request, options = {}) {
806
881
  message: `semantic digest does not match finalizer result for ${completedArtifact.name}`
807
882
  }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
808
883
  }
809
- try {
810
- localFinalizationIntent = readLocalArtifactFinalizationIntent(resolved.repoRoot, resolved.taskId, localFamily, completedArtifact.name);
811
- }
812
- catch (error) {
813
- return failed(normalized, {
814
- code: 'EVENT_ARTIFACT_CONFLICT',
815
- message: `cannot read local finalizer provenance for ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}`
816
- }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
817
- }
818
- if (!localFinalizationIntent || !['passed', 'consumed', 'finalize-ready', 'commit-started'].includes(localFinalizationIntent.state)) {
884
+ if (!localFinalizationIntent || !['passed', 'consumed'].includes(localFinalizationIntent.state)) {
819
885
  return failed(normalized, {
820
886
  code: 'EVENT_ARTIFACT_CONFLICT',
821
887
  message: `local finalizer provenance is missing or incomplete for ${completedArtifact.name}`
822
888
  }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
823
889
  }
824
- if (localFinalizationIntent.artifactSha256 !== actualSha256 || localFinalizationIntent.semanticDigest !== local.semanticDigest) {
890
+ if (localFinalizationIntent.finalArtifactSha256 !== actualSha256 || localFinalizationIntent.finalSemanticDigest !== local.semanticDigest) {
825
891
  return failed(normalized, {
826
892
  code: 'EVENT_ARTIFACT_CONFLICT',
827
893
  message: `local finalizer provenance does not match ${completedArtifact.name}`
828
894
  }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
829
895
  }
830
- if (localFinalizationIntent.recoveryOperationId !== null) {
896
+ if (localFinalizationIntent.authorityDigest !== null) {
831
897
  if (!lifecycleAuthority
832
898
  || localFinalizationIntent.recoveryOperationId !== lifecycleAuthority.operationId
833
899
  || localFinalizationIntent.requestId !== lifecycleAuthority.lifecycleRequestId
834
- || (localFinalizationIntent.phase !== 'artifact.finalize-local'
835
- && localFinalizationIntent.authorityDigest !== lifecycleRecoveryAttestationDigest(lifecycleAuthority))) {
900
+ || localFinalizationIntent.authorityDigest !== lifecycleRecoveryAttestationDigest(lifecycleAuthority)) {
836
901
  return failed(normalized, {
837
902
  code: 'EVENT_ARTIFACT_CONFLICT',
838
903
  message: `local finalizer provenance does not match lifecycle completion authority for ${completedArtifact.name}`
@@ -862,6 +927,43 @@ function applyTaskEventUnlocked(request, options = {}) {
862
927
  const findingCountError = validateReviewFindingCounts(normalized, content, eventIdentity.family, completedArtifact?.path ?? null);
863
928
  if (findingCountError)
864
929
  return failed(normalized, findingCountError, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
930
+ if (eventIdentity.phase === 'completed' && eventIdentity.family.startsWith('review-') && completedArtifact && reviewContent !== null) {
931
+ try {
932
+ reviewFinalizationIntent = readArtifactRecoveryIntent(resolved.repoRoot, resolved.taskId, eventIdentity.family, completedArtifact.name);
933
+ if (!normalized.dryRun && reviewFinalizationIntent?.state === 'commit-started') {
934
+ const reconciled = reconcileArtifactRecovery(recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, reviewFinalizationIntent), { lockAlreadyHeld: true });
935
+ if (reconciled.status === 'passed' || reconciled.status === 'consumed')
936
+ reviewFinalizationIntent = reconciled.intent;
937
+ }
938
+ if (!reviewFinalizationIntent || !['passed', 'consumption-started', 'consumed'].includes(reviewFinalizationIntent.state)) {
939
+ return failed(normalized, {
940
+ code: 'EVENT_ARTIFACT_CONFLICT',
941
+ message: `review finalizer provenance is missing or incomplete for ${completedArtifact.name}`
942
+ }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
943
+ }
944
+ const reviewStage = eventIdentity.family === 'review-analysis'
945
+ ? 'analysis'
946
+ : eventIdentity.family === 'review-plan' ? 'plan' : 'code';
947
+ const actualSha256 = sha256File(completedArtifact.path);
948
+ const actualSemanticDigest = canonicalSemanticDigest(reviewContent);
949
+ const expectedRequestId = `review-finalize:${resolved.taskId}:${reviewStage}:${normalized.round}`;
950
+ if (reviewFinalizationIntent.round !== normalized.round
951
+ || reviewFinalizationIntent.requestId !== expectedRequestId
952
+ || reviewFinalizationIntent.finalArtifactSha256 !== actualSha256
953
+ || reviewFinalizationIntent.finalSemanticDigest !== actualSemanticDigest) {
954
+ return failed(normalized, {
955
+ code: 'EVENT_ARTIFACT_CONFLICT',
956
+ message: `review finalizer provenance does not match ${completedArtifact.name}`
957
+ }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
958
+ }
959
+ }
960
+ catch (error) {
961
+ return failed(normalized, {
962
+ code: 'EVENT_ARTIFACT_CONFLICT',
963
+ message: `review finalizer recovery could not be reconciled for ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}`
964
+ }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
965
+ }
966
+ }
865
967
  let orchestrationCompletion = null;
866
968
  if (eventIdentity.phase === 'completed' && eventIdentity.family !== 'manual-validation' && eventIdentity.family !== 'validation-run') {
867
969
  const orchestrationStage = eventIdentity.family === 'analyze' ? 'analysis' : eventIdentity.family;
@@ -1004,16 +1106,16 @@ function applyTaskEventUnlocked(request, options = {}) {
1004
1106
  const expectedIntent = localFinalizationIntent;
1005
1107
  localFinalizationIntent = {
1006
1108
  ...localFinalizationIntent,
1007
- state: 'commit-started',
1109
+ state: 'consumption-started',
1008
1110
  phase: 'task-event.completed',
1009
1111
  authorityDigest: lifecycleRecoveryAttestationDigest(lifecycleAuthority),
1010
1112
  requestId: lifecycleAuthority.lifecycleRequestId,
1011
1113
  updatedAt: Date.now()
1012
1114
  };
1013
- writeArtifactRepairIntent(resolved.repoRoot, localFinalizationIntent, { expected: expectedIntent });
1115
+ writeArtifactRecoveryIntent(resolved.repoRoot, localFinalizationIntent, { expected: expectedIntent });
1014
1116
  }
1015
1117
  else if (!lifecycleAuthority && localFinalizationIntent.state === 'passed') {
1016
- localFinalizationIntent = consumeLocalArtifactFinalizationIntent(resolved.repoRoot, localFinalizationIntent);
1118
+ localFinalizationIntent = consumeLocalArtifactFinalizationIntent(resolved.repoRoot, localFinalizationIntent, { lockAlreadyHeld: options.lockAlreadyHeld });
1017
1119
  }
1018
1120
  }
1019
1121
  catch (error) {
@@ -1031,6 +1133,32 @@ function applyTaskEventUnlocked(request, options = {}) {
1031
1133
  });
1032
1134
  }
1033
1135
  }
1136
+ if (!normalized.dryRun && reviewFinalizationIntent && completedArtifact && reviewFinalizationIntent.state === 'passed') {
1137
+ try {
1138
+ const expectedIntent = reviewFinalizationIntent;
1139
+ reviewFinalizationIntent = {
1140
+ ...reviewFinalizationIntent,
1141
+ state: 'consumption-started',
1142
+ phase: 'task-event.completed',
1143
+ updatedAt: Date.now()
1144
+ };
1145
+ writeArtifactRecoveryIntent(resolved.repoRoot, reviewFinalizationIntent, { expected: expectedIntent });
1146
+ }
1147
+ catch (error) {
1148
+ return failed(normalized, {
1149
+ code: 'EVENT_ARTIFACT_CONFLICT',
1150
+ message: `review finalizer provenance could not be advanced before task write for ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}`
1151
+ }, {
1152
+ taskId: resolved.taskId,
1153
+ taskMdPath: resolved.taskMdPath,
1154
+ fromStep: currentStep,
1155
+ toStep: step,
1156
+ action: eventIdentity.action,
1157
+ phase: eventIdentity.phase,
1158
+ artifactContext
1159
+ });
1160
+ }
1161
+ }
1034
1162
  const sourceCompletion = eventIdentity.phase === 'completed'
1035
1163
  && ['analyze', 'plan', 'code'].includes(eventIdentity.family);
1036
1164
  const result = writeTask({ taskRef: normalized.taskRef, expectedState: stateOverride ? resolved.state : 'active', dryRun: normalized.dryRun, mutations }, { ...options, invalidationContext: sourceCompletion ? 'source-completion' : 'standard', metadataProvider: () => metadata });
@@ -1040,7 +1168,7 @@ function applyTaskEventUnlocked(request, options = {}) {
1040
1168
  try {
1041
1169
  if (!options.deferLifecycleRecoveryConsumption)
1042
1170
  consumeLifecycleRecoveryAttestation(lifecycleAuthority);
1043
- writeArtifactRepairIntent(resolved.repoRoot, { ...localFinalizationIntent, state: 'consumed', updatedAt: Date.now() }, { expected: localFinalizationIntent });
1171
+ writeArtifactRecoveryIntent(resolved.repoRoot, { ...localFinalizationIntent, state: 'consumed', updatedAt: Date.now() }, { expected: localFinalizationIntent });
1044
1172
  }
1045
1173
  catch (error) {
1046
1174
  return failed(normalized, {
@@ -1049,6 +1177,20 @@ function applyTaskEventUnlocked(request, options = {}) {
1049
1177
  }, { taskId: result.taskId, taskMdPath: result.taskMdPath, fromStep: currentStep, toStep: step, action: eventIdentity.action, phase: eventIdentity.phase, timestamp: result.timestamp, agentInfraVersion: result.agentInfraVersion, operations: result.operations, artifactContext });
1050
1178
  }
1051
1179
  }
1180
+ if (!normalized.dryRun && reviewFinalizationIntent && completedArtifact) {
1181
+ try {
1182
+ if (reviewFinalizationIntent.state === 'consumption-started') {
1183
+ const consumed = { ...reviewFinalizationIntent, state: 'consumed', updatedAt: Date.now() };
1184
+ writeArtifactRecoveryIntent(resolved.repoRoot, consumed, { expected: reviewFinalizationIntent });
1185
+ }
1186
+ }
1187
+ catch (error) {
1188
+ return failed(normalized, {
1189
+ code: 'EVENT_ARTIFACT_CONFLICT',
1190
+ message: `review finalizer recovery consumption is incomplete: ${error instanceof Error ? error.message : String(error)}`
1191
+ }, { taskId: result.taskId, taskMdPath: result.taskMdPath, fromStep: currentStep, toStep: step, action: eventIdentity.action, phase: eventIdentity.phase, timestamp: result.timestamp, agentInfraVersion: result.agentInfraVersion, operations: result.operations, artifactContext });
1192
+ }
1193
+ }
1052
1194
  if (!normalized.dryRun && orchestrationCompletion) {
1053
1195
  try {
1054
1196
  (options.commitOrchestrationCompletion ?? commitOrchestrationStageCompletion)(orchestrationCompletion);
@@ -1095,7 +1237,7 @@ function applyTaskEvent(request, options = {}) {
1095
1237
  if (!resolved.ok)
1096
1238
  return applyTaskEventUnlocked(request, options);
1097
1239
  try {
1098
- return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-event.${request.event}`, () => applyTaskEventUnlocked(request, options));
1240
+ return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-event.${request.event}`, () => applyTaskEventUnlocked(request, { ...options, lockAlreadyHeld: true }));
1099
1241
  }
1100
1242
  catch (error) {
1101
1243
  if (!(error instanceof TaskExecutionLockError))
@@ -407,10 +407,7 @@ function resourceForSnapshot(policy, options, frontmatter) {
407
407
  if (policy.producerId === 'platform.issue') {
408
408
  const identity = options.probeIssueNumber !== undefined
409
409
  ? resourceIdentity(options.probeIssueNumber)
410
- : taskIssueIdentity((frontmatter || {})) || (() => {
411
- const number = toPositiveInteger(frontmatter?.issue_number);
412
- return number === null ? null : resourceIdentity(number);
413
- })();
410
+ : taskIssueIdentity((frontmatter || {}));
414
411
  return identity ? { kind: 'issue', identity, number: resourceIdentityNumber(identity) } : null;
415
412
  }
416
413
  if (policy.producerId === 'platform.pull-request') {
@@ -481,11 +478,11 @@ async function probePlatformFailure(taskRef, policy, resolved, options) {
481
478
  }
482
479
  const issueIdentity = options.probeIssueNumber !== undefined
483
480
  ? resourceIdentity(options.probeIssueNumber)
484
- : taskIssueIdentity(frontmatter) || (() => {
485
- const number = toPositiveInteger(frontmatter.issue_number);
486
- return number === null ? null : resourceIdentity(number);
487
- })();
481
+ : taskIssueIdentity(frontmatter);
488
482
  const fact = readPrDeliveryFact(frontmatter);
483
+ if (fact.status === 'invalid') {
484
+ return error(fact.error.code, fact.error.message);
485
+ }
489
486
  const pullRequestIdentity = options.probePullRequestNumber !== undefined
490
487
  ? resourceIdentity(options.probePullRequestNumber)
491
488
  : fact.status === 'valid' && fact.fact.state === 'bound' ? fact.fact.identity.resource : null;
@@ -7,11 +7,12 @@ import { checkpointCommitMatches } from './commit-identity.js';
7
7
  import { readCheckpointIntent } from './commit-intent.js';
8
8
  import { parseTaskFrontmatter } from './frontmatter.js';
9
9
  import { readTaskFinalizationReceipt } from './finalization.js';
10
- import { readLocalArtifactFinalizationIntent, semanticDigest, sha256Content } from './local-artifact-finalization.js';
10
+ import { semanticDigest, sha256Content } from './local-artifact-finalization.js';
11
+ import { artifactRecoveryRoot, readArtifactRecoveryIntent } from './artifact-repair-intent.js';
11
12
  import { parseArtifactName } from './artifact-name.js';
12
13
  import { enumerateAllTaskDirs } from './resolve-ref.js';
13
14
  const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
14
- const LOCAL_INTENT_RE = /^(TASK-\d{8}-\d{6})-(analysis|plan|code)-(.+\.md)\.json$/;
15
+ const LOCAL_INTENT_RE = /^(TASK-\d{8}-\d{6})-(analysis|review-analysis|plan|review-plan|code|review-code)-(.+\.md)\.json$/;
15
16
  const COMMIT_INTENT_RE = /^(TASK-\d{8}-\d{6})\.json$/;
16
17
  const AUXILIARY_ROOTS = [
17
18
  '.local-artifact-finalization-intents',
@@ -110,7 +111,7 @@ function localIntentCandidate(repoRoot, task, filePath, taskId, family, artifact
110
111
  }
111
112
  let intent;
112
113
  try {
113
- intent = readLocalArtifactFinalizationIntent(repoRoot, taskId, family, artifact);
114
+ intent = readArtifactRecoveryIntent(repoRoot, taskId, family, artifact);
114
115
  }
115
116
  catch {
116
117
  return item('LFAI-CONSUMED', taskId, filePath, 'protected', 'LFAI_SCHEMA_INVALID', identity);
@@ -129,7 +130,7 @@ function localIntentCandidate(repoRoot, task, filePath, taskId, family, artifact
129
130
  catch {
130
131
  return item('LFAI-CONSUMED', taskId, filePath, 'protected', 'ARTIFACT_UNREADABLE', identity);
131
132
  }
132
- if (intent.artifactSha256 !== sha256Content(content) || intent.semanticDigest !== semanticDigest(content)) {
133
+ if (intent.finalArtifactSha256 !== sha256Content(content) || intent.finalSemanticDigest !== semanticDigest(content)) {
133
134
  return item('LFAI-CONSUMED', taskId, filePath, 'protected', 'ARTIFACT_DIGEST_MISMATCH', identity);
134
135
  }
135
136
  const receiptReason = receiptGate(repoRoot, task, options);
@@ -254,7 +255,85 @@ function scanIntermediateCleanup(repoRoot, options = {}) {
254
255
  const candidates = scanInternal(repoRoot, options);
255
256
  return buildIntermediateCleanupReport(candidates, true);
256
257
  }
257
- function removeCandidate(candidate) {
258
+ function cleanupConsumedRecoveryFiles(repoRoot, task, candidate) {
259
+ if (candidate.kind !== 'LFAI-CONSUMED' || !candidate.taskId)
260
+ return true;
261
+ if (!task)
262
+ return false;
263
+ const match = LOCAL_INTENT_RE.exec(path.basename(candidate.path));
264
+ if (!match)
265
+ return false;
266
+ let intent;
267
+ try {
268
+ intent = readArtifactRecoveryIntent(repoRoot, candidate.taskId, match[2], match[3]);
269
+ }
270
+ catch {
271
+ return false;
272
+ }
273
+ if (!intent || intent.state !== 'consumed')
274
+ return false;
275
+ const recovery = artifactRecoveryRoot(task.taskDir, intent.recoveryOperationId);
276
+ let stat = null;
277
+ try {
278
+ stat = fs.lstatSync(recovery);
279
+ }
280
+ catch (error) {
281
+ if (errorCode(error) === 'ENOENT')
282
+ return true;
283
+ return false;
284
+ }
285
+ if (!stat.isDirectory() || stat.isSymbolicLink())
286
+ return false;
287
+ let entries;
288
+ try {
289
+ entries = fs.readdirSync(recovery, { withFileTypes: true });
290
+ }
291
+ catch {
292
+ return false;
293
+ }
294
+ for (const entry of entries) {
295
+ if (entry.isSymbolicLink())
296
+ return false;
297
+ if (entry.isFile()) {
298
+ if (!['baseline.md', 'candidate.md', 'final.md', 'publish.md'].includes(entry.name))
299
+ return false;
300
+ continue;
301
+ }
302
+ if (!entry.isDirectory() || entry.name !== 'generations')
303
+ return false;
304
+ let generations;
305
+ try {
306
+ generations = fs.readdirSync(path.join(recovery, entry.name), { withFileTypes: true });
307
+ }
308
+ catch {
309
+ return false;
310
+ }
311
+ if (generations.some((generation) => generation.isSymbolicLink()
312
+ || !generation.isFile() || !/^[a-f0-9]{64}\.md$/u.test(generation.name)))
313
+ return false;
314
+ }
315
+ try {
316
+ for (const entry of entries) {
317
+ const target = path.join(recovery, entry.name);
318
+ if (entry.isFile())
319
+ fs.unlinkSync(target);
320
+ else {
321
+ for (const generation of fs.readdirSync(target, { withFileTypes: true }))
322
+ fs.unlinkSync(path.join(target, generation.name));
323
+ fs.rmdirSync(target);
324
+ }
325
+ }
326
+ fs.rmdirSync(recovery);
327
+ const recoveryRoot = path.dirname(recovery);
328
+ if (fs.readdirSync(recoveryRoot).length === 0)
329
+ fs.rmdirSync(recoveryRoot);
330
+ return true;
331
+ }
332
+ catch (error) {
333
+ return errorCode(error) === 'ENOENT';
334
+ }
335
+ }
336
+ function removeCandidate(repoRoot, candidate, task) {
258
337
  if (candidate.disposition !== 'planned')
259
338
  return candidate;
260
339
  const root = candidate.kind === 'EMPTY-AUX-PARENT'
@@ -279,6 +358,9 @@ function removeCandidate(candidate) {
279
358
  return { ...candidate, disposition: 'protected', reason: 'PATH_IDENTITY_CHANGED' };
280
359
  if (!sameFileIdentity(expected, actual))
281
360
  return { ...candidate, disposition: 'protected', reason: 'PATH_IDENTITY_CHANGED' };
361
+ if (!cleanupConsumedRecoveryFiles(repoRoot, task, candidate)) {
362
+ return { ...candidate, disposition: 'protected', reason: 'RECOVERY_STAGING_NOT_SAFE_TO_REMOVE' };
363
+ }
282
364
  try {
283
365
  if (candidate.kind === 'EMPTY-AUX-PARENT') {
284
366
  if (fs.readdirSync(candidate.path).length !== 0)
@@ -317,6 +399,7 @@ function cleanupIntermediateUnderRemovalCoordinator(repoRoot, options = {}) {
317
399
  indexes.push(index);
318
400
  byTask.set(candidate.taskId, indexes);
319
401
  }
402
+ const tasks = inspectTaskRecords(repoRoot, new Set(byTask.keys()));
320
403
  for (const taskId of [...byTask.keys()].sort()) {
321
404
  const execute = () => {
322
405
  const refreshed = scanIntermediateCleanup(repoRoot, {
@@ -330,7 +413,7 @@ function cleanupIntermediateUnderRemovalCoordinator(repoRoot, options = {}) {
330
413
  continue;
331
414
  const current = refreshedByKey.get(intermediateCleanupKey(initialCandidate));
332
415
  items[index] = current
333
- ? removeCandidate(current)
416
+ ? removeCandidate(repoRoot, current, tasks.get(taskId))
334
417
  : { ...initialCandidate, disposition: 'protected', reason: 'CANDIDATE_NO_LONGER_ELIGIBLE' };
335
418
  }
336
419
  };
@@ -351,14 +434,14 @@ function cleanupIntermediateUnderRemovalCoordinator(repoRoot, options = {}) {
351
434
  for (const [index, candidate] of items.entries()) {
352
435
  if (candidate.taskId || candidate.disposition !== 'planned')
353
436
  continue;
354
- items[index] = removeCandidate(candidate);
437
+ items[index] = removeCandidate(repoRoot, candidate);
355
438
  }
356
439
  const knownPaths = new Set(items.map((candidate) => intermediateCleanupKey(candidate)));
357
440
  for (const candidate of scanIntermediateCleanup(repoRoot, scanOptions).items) {
358
441
  if (candidate.kind !== 'EMPTY-AUX-PARENT' || candidate.disposition !== 'planned'
359
442
  || knownPaths.has(intermediateCleanupKey(candidate)))
360
443
  continue;
361
- items.push(removeCandidate(candidate));
444
+ items.push(removeCandidate(repoRoot, candidate));
362
445
  }
363
446
  return buildIntermediateCleanupReport(items);
364
447
  }