@fitlab-ai/agent-infra 0.11.0 → 0.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/dist/lib/internal/cli-route-inventory.js +1 -1
  2. package/dist/lib/internal/manual-validation.js +6 -2
  3. package/dist/lib/internal/platform-comment.js +59 -0
  4. package/dist/lib/internal/sandbox-control.js +4 -131
  5. package/dist/lib/internal/task-artifact.js +1 -1
  6. package/dist/lib/internal/task-operation-registry.js +1 -0
  7. package/dist/lib/internal/task-review.js +5 -5
  8. package/dist/lib/internal/task-validate.js +0 -6
  9. package/dist/lib/platform/comment-safety.js +7 -1
  10. package/dist/lib/platform/github-provider.js +13 -3
  11. package/dist/lib/platform/issue-comments.js +120 -13
  12. package/dist/lib/platform/issues.js +3 -4
  13. package/dist/lib/platform/provider-contract.d.ts +2 -0
  14. package/dist/lib/platform/provider-validation.js +7 -2
  15. package/dist/lib/platform/verification-sync.js +161 -156
  16. package/dist/lib/sandbox/control/executor.js +0 -38
  17. package/dist/lib/sandbox/control/finalization-response.js +10 -2
  18. package/dist/lib/sandbox/control/task-view.js +3 -1
  19. package/dist/lib/sandbox/control/workflow-executor.js +32 -41
  20. package/dist/lib/task/artifact-command.js +6 -6
  21. package/dist/lib/task/artifact-lifecycle.js +15 -52
  22. package/dist/lib/task/artifact-operations.js +27 -1
  23. package/dist/lib/task/capabilities.js +0 -2
  24. package/dist/lib/task/codex-orchestration.js +10 -208
  25. package/dist/lib/task/commit-operation.js +3 -18
  26. package/dist/lib/task/control-authority.js +1 -8
  27. package/dist/lib/task/delegation-receipts.js +14 -76
  28. package/dist/lib/task/events.js +147 -278
  29. package/dist/lib/task/finalization-state.js +2 -1
  30. package/dist/lib/task/finalization.js +273 -49
  31. package/dist/lib/task/gate-policy.js +94 -0
  32. package/dist/lib/task/lifecycle-execution.js +2 -11
  33. package/dist/lib/task/local-artifact-finalization.js +31 -245
  34. package/dist/lib/task/orchestration.js +8 -24
  35. package/dist/lib/task/review-artifacts.js +12 -6
  36. package/dist/lib/task/review-command.js +0 -1
  37. package/dist/lib/task/review-finalization.js +47 -218
  38. package/dist/lib/task/verification-engine.js +28 -17
  39. package/dist/lib/task/verification.js +25 -9
  40. package/dist/lib/task/workflow-command.js +4 -1
  41. package/dist/lib/task/workflow-dispatch.js +55 -3
  42. package/dist/lifecycle-build-manifest.json +3 -3
  43. package/lib/internal/cli-route-inventory.ts +1 -1
  44. package/lib/internal/manual-validation.ts +6 -2
  45. package/lib/internal/platform-comment.ts +38 -0
  46. package/lib/internal/sandbox-control.ts +3 -145
  47. package/lib/internal/task-artifact.ts +1 -1
  48. package/lib/internal/task-operation-registry.ts +1 -0
  49. package/lib/internal/task-review.ts +5 -5
  50. package/lib/internal/task-validate.ts +0 -5
  51. package/lib/platform/comment-safety.ts +8 -0
  52. package/lib/platform/github-provider.ts +11 -2
  53. package/lib/platform/issue-comments.ts +132 -13
  54. package/lib/platform/issues.ts +3 -4
  55. package/lib/platform/provider-contract.ts +2 -0
  56. package/lib/platform/provider-validation.ts +6 -2
  57. package/lib/platform/verification-sync.ts +178 -179
  58. package/lib/sandbox/control/executor.ts +0 -44
  59. package/lib/sandbox/control/finalization-response.ts +10 -2
  60. package/lib/sandbox/control/task-view.ts +3 -1
  61. package/lib/sandbox/control/workflow-executor.ts +31 -48
  62. package/lib/task/artifact-command.ts +6 -7
  63. package/lib/task/artifact-lifecycle.ts +12 -49
  64. package/lib/task/artifact-operations.ts +27 -1
  65. package/lib/task/capabilities.ts +0 -1
  66. package/lib/task/codex-orchestration.ts +8 -241
  67. package/lib/task/commit-operation.ts +3 -23
  68. package/lib/task/control-authority.ts +1 -8
  69. package/lib/task/delegation-receipts.ts +12 -79
  70. package/lib/task/events.ts +155 -287
  71. package/lib/task/finalization-state.ts +2 -1
  72. package/lib/task/finalization.ts +277 -57
  73. package/lib/task/gate-policy.ts +133 -0
  74. package/lib/task/lifecycle-execution.ts +10 -21
  75. package/lib/task/local-artifact-finalization.ts +29 -285
  76. package/lib/task/orchestration.ts +12 -41
  77. package/lib/task/review-artifacts.ts +8 -2
  78. package/lib/task/review-command.ts +0 -1
  79. package/lib/task/review-finalization.ts +46 -200
  80. package/lib/task/verification-engine.ts +24 -14
  81. package/lib/task/verification-types.ts +7 -0
  82. package/lib/task/verification.ts +25 -9
  83. package/lib/task/workflow-command.ts +4 -1
  84. package/lib/task/workflow-dispatch.ts +46 -3
  85. package/package.json +2 -2
  86. package/templates/.agents/rules/issue-sync.en.md +2 -0
  87. package/templates/.agents/rules/issue-sync.zh-CN.md +5 -8
  88. package/templates/.agents/rules/lifecycle-orchestration.en.md +7 -7
  89. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +7 -7
  90. package/templates/.agents/rules/local-artifact-repair.en.md +14 -52
  91. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +13 -52
  92. package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -3
  93. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -3
  94. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -10
  95. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -10
  96. package/templates/.agents/skills/block-task/config/verify.json +1 -3
  97. package/templates/.agents/skills/cancel-task/config/verify.json +1 -4
  98. package/templates/.agents/skills/code-task/SKILL.en.md +3 -3
  99. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +3 -3
  100. package/templates/.agents/skills/code-task/config/verify.en.json +1 -12
  101. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -12
  102. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +1 -1
  103. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +1 -1
  104. package/templates/.agents/skills/code-task/reference/report-template.en.md +11 -0
  105. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +11 -0
  106. package/templates/.agents/skills/commit/config/verify.json +1 -5
  107. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +6 -6
  108. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +6 -6
  109. package/templates/.agents/skills/complete-task/SKILL.en.md +3 -3
  110. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
  111. package/templates/.agents/skills/complete-task/config/verify.en.json +3 -14
  112. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +3 -14
  113. package/templates/.agents/skills/create-pr/config/verify.json +1 -10
  114. package/templates/.agents/skills/create-task/config/verify.json +1 -4
  115. package/templates/.agents/skills/import-issue/config/verify.json +1 -2
  116. package/templates/.agents/skills/plan-task/SKILL.en.md +3 -3
  117. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +3 -3
  118. package/templates/.agents/skills/plan-task/config/verify.en.json +1 -10
  119. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -10
  120. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +4 -4
  121. package/templates/.agents/skills/review-analysis/SKILL.en.md +2 -2
  122. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
  123. package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -10
  124. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -10
  125. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +2 -0
  126. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +2 -0
  127. package/templates/.agents/skills/review-code/SKILL.en.md +3 -3
  128. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +3 -3
  129. package/templates/.agents/skills/review-code/config/verify.en.json +1 -10
  130. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -10
  131. package/templates/.agents/skills/review-code/reference/report-template.en.md +2 -0
  132. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +2 -0
  133. package/templates/.agents/skills/review-plan/SKILL.en.md +2 -2
  134. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
  135. package/templates/.agents/skills/review-plan/config/verify.en.json +1 -10
  136. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -10
  137. package/templates/.agents/skills/review-plan/reference/report-template.en.md +2 -0
  138. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +2 -0
  139. package/templates/.agents/skills/review-pr/config/verify.en.json +1 -8
  140. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -8
  141. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +38 -13
  142. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +38 -13
  143. package/templates/.agents/skills/run-task/SKILL.en.md +6 -6
  144. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +6 -6
  145. package/templates/.agents/skills/run-task/reference/host-validation.en.md +2 -2
  146. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +2 -2
@@ -1,8 +1,7 @@
1
1
  import {
2
2
  inspectOrchestrationStage,
3
3
  pauseOrchestration,
4
- planOrchestrationStageCompletion,
5
- readRun
4
+ planOrchestrationStageCompletion
6
5
  } from './orchestration.ts';
7
6
  import type {
8
7
  OrchestrationStageCompletion,
@@ -31,28 +30,15 @@ function failure(mode: LifecycleExecutionMode, code: string, message: string): L
31
30
  function inspectLifecycleExecution(
32
31
  taskRef: string,
33
32
  request: LifecycleExecutionRequest,
34
- options: Readonly<{ repoRoot?: string }> = {}
33
+ options: Readonly<{
34
+ repoRoot?: string;
35
+ now?: () => string;
36
+ }> = {}
35
37
  ): LifecycleExecutionResult {
36
38
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
37
39
  if (!resolved.ok) return failure(request.mode, resolved.code, resolved.message);
38
40
  if (request.mode === 'standalone') {
39
- try {
40
- const run = readRun(resolved.taskDir);
41
- if (run?.pendingDelegation) {
42
- return failure(
43
- request.mode,
44
- 'ORCHESTRATION_STANDALONE_BUSY',
45
- 'standalone lifecycle execution is blocked by a pending orchestration delegation'
46
- );
47
- }
48
- return { ok: true, mode: request.mode, completionPlan: null, error: null };
49
- } catch (error) {
50
- return failure(
51
- request.mode,
52
- 'ORCHESTRATION_STATE_INVALID',
53
- error instanceof Error ? error.message : String(error)
54
- );
55
- }
41
+ return { ok: true, mode: request.mode, completionPlan: null, error: null };
56
42
  }
57
43
 
58
44
  if (request.agent) {
@@ -84,7 +70,10 @@ function inspectLifecycleExecution(
84
70
  function validateLifecycleExecution(
85
71
  taskRef: string,
86
72
  request: LifecycleExecutionRequest,
87
- options: Readonly<{ repoRoot?: string }> = {}
73
+ options: Readonly<{
74
+ repoRoot?: string;
75
+ now?: () => string;
76
+ }> = {}
88
77
  ): LifecycleExecutionResult {
89
78
  const inspected = inspectLifecycleExecution(taskRef, request, options);
90
79
  if (inspected.ok || request.mode === 'standalone' || request.dryRun) return inspected;
@@ -1,38 +1,16 @@
1
1
  import fs from 'node:fs';
2
+ import path from 'node:path';
2
3
 
3
4
  import { inspectDecisionDetailDuplicates } from './decision-details.ts';
4
5
  import { scanVisibleMarkdown } from './markdown.ts';
5
6
  import { parseArtifactName } from './artifact-name.ts';
6
7
  import {
7
8
  hasOpenArtifactRound,
8
- parseCodePlanInputReference,
9
- validateArtifactPublication,
10
- validateCompletedArtifact
11
9
  } from './artifact-lifecycle.ts';
12
10
  import { resolveTaskRef } from './resolve-ref.ts';
13
11
  import { expectedQualificationRelations, validateQualificationAudit } from './qualification-audit.ts';
14
12
  import { canonicalSemanticDigest, inspectArtifactPatterns, inspectArtifactStructure, sha256Content } from './artifact-operations.ts';
15
13
  import { getArtifactSchema } from './artifact-schema.ts';
16
- import { readArtifactRecoveryIntent } from './artifact-repair-intent.ts';
17
- import type { ArtifactRecoveryIntent } from './artifact-repair-intent.ts';
18
- import {
19
- beginArtifactRecovery,
20
- commitArtifactRecovery,
21
- consumeArtifactRecovery,
22
- prepareArtifactRecoveryFinal,
23
- prepareArtifactRecoveryCommit,
24
- recordArtifactRecoveryPassed,
25
- reconcileArtifactRecovery,
26
- recoveryContextFromIntent,
27
- stageArtifactCandidate
28
- } from './artifact-recovery.ts';
29
- import type { ArtifactRecoveryContext } from './artifact-recovery.ts';
30
- import {
31
- consumeLifecycleRecoveryAttestation,
32
- lifecycleRecoveryAttestationDigest,
33
- validateLifecycleRecoveryAttestation
34
- } from './control-authority.ts';
35
- import type { LifecycleRecoveryAttestationV1 } from './control-authority.ts';
36
14
 
37
15
  type LocalArtifactFamily = 'analysis' | 'plan' | 'code';
38
16
 
@@ -51,8 +29,6 @@ type LocalArtifactDiagnosticCode =
51
29
  | 'LOCAL_REQUIRED_PATTERN_MISSING'
52
30
  | 'LOCAL_STRUCTURAL_INVALID'
53
31
  | 'LOCAL_SECTION_HEADING_TRAILING_PUNCTUATION'
54
- | 'LOCAL_RECOVERY_PROVENANCE_CONFLICT'
55
- | 'LOCAL_RECOVERY_BASELINE_MISMATCH'
56
32
  | 'LOCAL_QUALIFICATION_AUDIT_INVALID';
57
33
 
58
34
  type LocalArtifactDiagnostic = {
@@ -81,17 +57,9 @@ type LocalArtifactFinalizationRequest = {
81
57
  artifact: string;
82
58
  repoRoot?: string;
83
59
  requiredSections?: readonly string[];
84
- recoveryId?: string;
85
60
  lockAlreadyHeld?: boolean;
86
61
  };
87
62
 
88
- type LocalArtifactRecoveryInfo = Readonly<{
89
- recoveryId: string;
90
- candidatePath: string;
91
- baselineSha256: string;
92
- baselineSemanticDigest: string;
93
- }>;
94
-
95
63
  type LocalArtifactFinalizationResult = {
96
64
  status: 'passed' | 'failed';
97
65
  changed: boolean;
@@ -102,54 +70,9 @@ type LocalArtifactFinalizationResult = {
102
70
  artifactSha256: string | null;
103
71
  semanticDigest: string | null;
104
72
  diagnostics: readonly LocalArtifactDiagnostic[];
105
- recovery?: LocalArtifactRecoveryInfo;
106
73
  error: { code: string; message: string } | null;
107
74
  };
108
75
 
109
- type LocalArtifactFinalizationIntent = ArtifactRecoveryIntent;
110
-
111
- function authorityDigest(authority: LifecycleRecoveryAttestationV1): string {
112
- return lifecycleRecoveryAttestationDigest(authority);
113
- }
114
-
115
- function validFinalizerAuthority(
116
- authority: LifecycleRecoveryAttestationV1 | undefined,
117
- request: LocalArtifactFinalizationRequest,
118
- taskId: string
119
- ): boolean {
120
- if (!authority) return false;
121
- try { validateLifecycleRecoveryAttestation(authority); }
122
- catch { return false; }
123
- return authority.version === 1
124
- && authority.phase === 'artifact.finalize-local'
125
- && authority.taskId === taskId
126
- && authority.family === request.family
127
- && authority.artifact === request.artifact
128
- && authority.round === (parseArtifactName(request.artifact)?.round ?? 0)
129
- && authority.requestId.length > 0
130
- && authority.operationId.length > 0;
131
- }
132
-
133
- function recoveryInfo(context: ArtifactRecoveryContext): LocalArtifactRecoveryInfo {
134
- return {
135
- recoveryId: context.recoveryId,
136
- candidatePath: context.stagingPath,
137
- baselineSha256: context.baselineSha256,
138
- baselineSemanticDigest: context.baselineSemanticDigest
139
- };
140
- }
141
-
142
- function consumeLocalArtifactFinalizationIntent(
143
- repoRoot: string,
144
- intent: LocalArtifactFinalizationIntent,
145
- options: Readonly<{ lockAlreadyHeld?: boolean }> = {}
146
- ): LocalArtifactFinalizationIntent {
147
- if (intent.state === 'consumed') return intent;
148
- const resolved = resolveTaskRef(intent.taskId, { repoRoot });
149
- if (!resolved.ok) throw new Error(resolved.message);
150
- return consumeArtifactRecovery(recoveryContextFromIntent(repoRoot, resolved.taskDir, intent), options);
151
- }
152
-
153
76
  function localDiagnosticCode(code: string): LocalArtifactDiagnosticCode {
154
77
  const map: Record<string, LocalArtifactDiagnosticCode> = {
155
78
  ARTIFACT_EMPTY: 'LOCAL_ARTIFACT_EMPTY',
@@ -206,12 +129,15 @@ function validateLocalArtifact(
206
129
  if (!decisionDetails.ok) diagnostics.push({ code: 'LOCAL_DECISION_DETAIL_DUPLICATE', message: decisionDetails.message, line: null });
207
130
 
208
131
  if (options.taskContent !== undefined) {
209
- const expected = expectedQualificationRelations(options.taskContent, options.family);
210
- if (!expected.ok) diagnostics.push({ code: 'LOCAL_QUALIFICATION_AUDIT_INVALID', message: `${expected.code}: ${expected.message}`, line: null });
132
+ const identity = options.artifact ? parseArtifactName(options.artifact) : null;
133
+ const expected = identity?.family === options.family && hasOpenArtifactRound(options.taskContent, options.family, identity.round)
134
+ ? expectedQualificationRelations(options.taskContent, options.family)
135
+ : undefined;
136
+ if (expected && !expected.ok) diagnostics.push({ code: 'LOCAL_QUALIFICATION_AUDIT_INVALID', message: `${expected.code}: ${expected.message}`, line: null });
211
137
  const qualification = validateQualificationAudit(options.taskContent, content, {
212
138
  family: options.family,
213
139
  artifact: options.artifact,
214
- expectedUpstreamRelations: expected.ok ? expected.relations : undefined
140
+ expectedUpstreamRelations: expected?.ok ? expected.relations : undefined
215
141
  });
216
142
  if (!qualification.ok) diagnostics.push({ code: 'LOCAL_QUALIFICATION_AUDIT_INVALID', message: `${qualification.code}: ${qualification.message}`, line: null });
217
143
  }
@@ -243,32 +169,11 @@ type LocalArtifactPreparation = Readonly<{
243
169
  content: string;
244
170
  repoRoot?: string;
245
171
  lockAlreadyHeld?: boolean;
246
- recovery?: ArtifactRecoveryContext;
247
- authority?: LifecycleRecoveryAttestationV1;
248
172
  }>;
249
173
 
250
- function tupleFor(
251
- request: LocalArtifactFinalizationRequest,
252
- taskId: string,
253
- authority?: LifecycleRecoveryAttestationV1
254
- ) {
255
- const round = parseArtifactName(request.artifact)?.round ?? 0;
256
- return {
257
- taskId,
258
- family: request.family,
259
- artifact: request.artifact,
260
- round,
261
- requestId: authority?.lifecycleRequestId ?? `local-finalize:${taskId}:${request.family}:${round}`,
262
- phase: authority?.phase ?? 'artifact.finalize-local' as const,
263
- authorityDigest: authority ? authorityDigest(authority) : null
264
- };
265
- }
266
-
267
174
  function prepareLocalArtifact(
268
175
  request: LocalArtifactFinalizationRequest,
269
- candidate?: string,
270
- authority?: LifecycleRecoveryAttestationV1,
271
- preflightOnly = false
176
+ candidate?: string
272
177
  ): LocalArtifactPreparation {
273
178
  const failed = (code: string, message: string, extra: Partial<LocalArtifactFinalizationResult> = {}): LocalArtifactPreparation => ({
274
179
  result: failedFinalization(request, { code, message }, extra), content: candidate ?? ''
@@ -277,34 +182,17 @@ function prepareLocalArtifact(
277
182
  if (!resolved.ok) return failed(resolved.code, resolved.message);
278
183
  const parsed = parseArtifactName(request.artifact);
279
184
  if (!parsed || parsed.family !== request.family) return failed('ARTIFACT_IDENTITY_INVALID', `artifact '${request.artifact}' does not match ${request.family}`);
280
- if (authority && !validFinalizerAuthority(authority, request, resolved.taskId)) {
281
- return failed('LOCAL_EXECUTION_AUTHORITY_INVALID', 'finalizer authority does not match the artifact tuple');
282
- }
283
-
284
185
  let taskContent: string;
285
186
  let content: string;
286
- let recovery: ArtifactRecoveryContext | undefined;
287
- let recoveryState: ArtifactRecoveryIntent['state'] | undefined;
288
187
  try {
289
188
  taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
290
- if (request.recoveryId) {
291
- const intent = readArtifactRecoveryIntent(resolved.repoRoot, resolved.taskId, request.family, request.artifact);
292
- if (!intent || intent.recoveryOperationId !== request.recoveryId) return failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', 'recovery id does not match the artifact journal');
293
- if (!['awaiting-preflight-recovery', 'preflight-ready', 'preflight-commit-started', 'preflight-passed', 'full-finalizer-ready', 'commit-started'].includes(intent.state)) return failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', `recovery journal is in '${intent.state}' state`);
294
- recoveryState = intent.state;
295
- recovery = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, intent);
296
- content = fs.existsSync(recovery.stagingPath)
297
- ? fs.readFileSync(recovery.stagingPath, 'utf8')
298
- : fs.readFileSync(recovery.formalPath, 'utf8');
299
- } else {
300
- const validated = validateCompletedArtifact(resolved.taskDir, request.family, request.artifact, parsed.round);
301
- if (!validated.ok) return failed(validated.error.code, validated.error.message);
302
- content = candidate ?? fs.readFileSync(validated.artifact.path, 'utf8');
303
- }
189
+ const artifactPath = path.join(resolved.taskDir, request.artifact);
190
+ if (!fs.statSync(artifactPath).isFile()) return failed('ARTIFACT_NOT_REGULAR', `artifact '${request.artifact}' is not a regular file`);
191
+ content = candidate ?? fs.readFileSync(artifactPath, 'utf8');
304
192
  } catch (error) { return failed('ARTIFACT_NOT_READABLE', String(error)); }
305
193
 
306
- if (resolved.state !== 'active' || !hasOpenArtifactRound(taskContent, request.family, parsed.round)) {
307
- return failed('ARTIFACT_IDENTITY_INVALID', 'candidate must match one open started round in an active task');
194
+ if (resolved.state !== 'active') {
195
+ return failed('ARTIFACT_IDENTITY_INVALID', 'artifact finalization requires an active task');
308
196
  }
309
197
  const validation = validateLocalArtifact(content, {
310
198
  family: request.family, requiredSections: request.requiredSections, taskContent, artifact: request.artifact
@@ -318,186 +206,44 @@ function prepareLocalArtifact(
318
206
  semanticDigest: validation.semanticDigest, diagnostics: validation.diagnostics
319
207
  });
320
208
 
321
- if (request.recoveryId) {
322
- if (!validation.ok) return { result, content, repoRoot: resolved.repoRoot, recovery, authority, lockAlreadyHeld: request.lockAlreadyHeld };
323
- if (preflightOnly && recovery && recoveryState === 'awaiting-preflight-recovery') {
324
- const staged = stageArtifactCandidate(recovery, Buffer.from(content, 'utf8'), { lockAlreadyHeld: request.lockAlreadyHeld });
325
- prepareArtifactRecoveryCommit(recovery, staged.candidateSha256, staged.semanticDigest, { lockAlreadyHeld: request.lockAlreadyHeld });
326
- }
327
- return {
328
- result: { ...result, status: 'passed', changed: false, error: null, recovery: recoveryInfo(recovery!) },
329
- content, repoRoot: resolved.repoRoot, recovery, authority, lockAlreadyHeld: request.lockAlreadyHeld
330
- };
331
- }
332
-
333
- const existing = readArtifactRecoveryIntent(resolved.repoRoot, resolved.taskId, request.family, request.artifact);
334
- if (existing?.state === 'passed' || existing?.state === 'consumed') {
335
- if (existing.state === 'consumed' && (existing.finalArtifactSha256 !== artifactSha256 || existing.finalSemanticDigest !== validation.semanticDigest)) {
336
- return { ...failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', 'formal artifact does not match its completed recovery journal'), content, repoRoot: resolved.repoRoot, authority, lockAlreadyHeld: request.lockAlreadyHeld };
337
- }
338
- if (existing.finalArtifactSha256 === artifactSha256 && existing.finalSemanticDigest === validation.semanticDigest) {
339
- const context = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, existing);
340
- return {
341
- result: { ...result, status: 'passed', error: null, recovery: recoveryInfo(context) },
342
- content, repoRoot: resolved.repoRoot, recovery: context, authority, lockAlreadyHeld: request.lockAlreadyHeld
343
- };
344
- }
345
- }
346
-
347
- if (existing && ['preflight-ready', 'preflight-commit-started', 'preflight-passed', 'full-finalizer-ready'].includes(existing.state)) {
348
- const context = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, existing);
349
- return {
350
- result: { ...result, status: 'passed', changed: false, error: null, recovery: recoveryInfo(context) },
351
- content, repoRoot: resolved.repoRoot, recovery: context, authority, lockAlreadyHeld: request.lockAlreadyHeld
352
- };
353
- }
354
- if (existing?.state === 'commit-started') {
355
- if (existing.finalArtifactSha256 !== artifactSha256 || existing.finalSemanticDigest !== validation.semanticDigest) {
356
- return { ...failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', 'formal artifact does not match the interrupted recovery journal'), content, repoRoot: resolved.repoRoot, authority, lockAlreadyHeld: request.lockAlreadyHeld };
357
- }
358
- const context = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, existing);
359
- const reconciled = reconcileArtifactRecovery(context, {
360
- lockAlreadyHeld: request.lockAlreadyHeld,
361
- validateFinal: () => validation.semanticDigest === existing.finalSemanticDigest
362
- });
363
- if (reconciled.status !== 'passed') {
364
- return { ...failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', `interrupted recovery could not be reconciled: ${reconciled.status}`), content, repoRoot: resolved.repoRoot, recovery: context, authority, lockAlreadyHeld: request.lockAlreadyHeld };
365
- }
366
- return {
367
- result: { ...result, status: 'passed', changed: false, error: null, recovery: recoveryInfo(context) },
368
- content, repoRoot: resolved.repoRoot, recovery: context, authority, lockAlreadyHeld: request.lockAlreadyHeld
369
- };
370
- }
209
+ // Trusted local operators edit the canonical artifact directly. A
210
+ // finalization result is a fresh observation of those bytes, never a
211
+ // capability minted by an earlier recovery attempt or generation.
212
+ return {
213
+ result: validation.ok ? { ...result, status: 'passed', error: null } : result,
214
+ content,
215
+ repoRoot: resolved.repoRoot,
216
+ lockAlreadyHeld: request.lockAlreadyHeld
217
+ };
371
218
 
372
- try {
373
- if (validation.ok) {
374
- if (preflightOnly) {
375
- const context = beginArtifactRecovery(tupleFor(request, resolved.taskId, authority), Buffer.from(content, 'utf8'), {
376
- repoRoot: resolved.repoRoot, taskDir: resolved.taskDir,
377
- ...(authority ? { recoveryId: authority.operationId } : {}), lockAlreadyHeld: request.lockAlreadyHeld
378
- });
379
- const staged = stageArtifactCandidate(context, Buffer.from(content, 'utf8'), { lockAlreadyHeld: request.lockAlreadyHeld });
380
- prepareArtifactRecoveryCommit(context, staged.candidateSha256, staged.semanticDigest, { lockAlreadyHeld: request.lockAlreadyHeld });
381
- return { result: { ...result, status: 'passed', error: null, recovery: recoveryInfo(context) }, content, repoRoot: resolved.repoRoot, recovery: context, authority, lockAlreadyHeld: request.lockAlreadyHeld };
382
- }
383
- const context = recordArtifactRecoveryPassed(tupleFor(request, resolved.taskId, authority), {
384
- repoRoot: resolved.repoRoot,
385
- taskDir: resolved.taskDir,
386
- expectedFinalSha256: artifactSha256,
387
- expectedFinalSemanticDigest: validation.semanticDigest,
388
- ...(authority ? { recoveryId: authority.operationId } : {}),
389
- lockAlreadyHeld: request.lockAlreadyHeld
390
- });
391
- recovery = context;
392
- return { result: { ...result, status: 'passed', changed: false, error: null, recovery: recoveryInfo(context) }, content, repoRoot: resolved.repoRoot, recovery, authority, lockAlreadyHeld: request.lockAlreadyHeld };
393
- }
394
- const context = beginArtifactRecovery(tupleFor(request, resolved.taskId, authority), Buffer.from(content, 'utf8'), {
395
- repoRoot: resolved.repoRoot,
396
- taskDir: resolved.taskDir,
397
- ...(authority ? { recoveryId: authority.operationId } : {}),
398
- lockAlreadyHeld: request.lockAlreadyHeld
399
- });
400
- recovery = context;
401
- return {
402
- result: { ...result, recovery: recoveryInfo(context) },
403
- content, repoRoot: resolved.repoRoot, recovery, authority, lockAlreadyHeld: request.lockAlreadyHeld
404
- };
405
- } catch (error) {
406
- return { ...failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', String(error)), content, repoRoot: resolved.repoRoot, authority, lockAlreadyHeld: request.lockAlreadyHeld };
407
- }
408
219
  }
409
220
 
410
221
  function commitLocalArtifactProvenance(
411
- prepared: LocalArtifactPreparation,
412
- options: Readonly<{ afterPublish?: () => void }> = {}
222
+ prepared: LocalArtifactPreparation
413
223
  ): LocalArtifactFinalizationResult {
414
- if (!prepared.recovery || !prepared.repoRoot || prepared.result.status === 'failed' && !prepared.result.recovery) return prepared.result;
415
- if (prepared.result.status === 'failed') return prepared.result;
416
- try {
417
- const intent = readArtifactRecoveryIntent(prepared.repoRoot, prepared.recovery.taskId, prepared.recovery.family, prepared.recovery.artifact);
418
- if (!intent) throw new Error('ARTIFACT_RECOVERY_INTENT_MISSING');
419
- let committed;
420
- if (intent.state === 'passed' || intent.state === 'consumed') {
421
- return {
422
- ...prepared.result,
423
- changed: false,
424
- artifactSha256: intent.finalArtifactSha256,
425
- semanticDigest: intent.finalSemanticDigest,
426
- error: null
427
- };
428
- } else if (intent.state === 'commit-started') {
429
- const reconciled = reconcileArtifactRecovery(prepared.recovery, {
430
- lockAlreadyHeld: prepared.lockAlreadyHeld,
431
- afterPublish: options.afterPublish
432
- });
433
- committed = reconciled.intent;
434
- } else if (intent.state === 'awaiting-preflight-recovery') {
435
- const staged = stageArtifactCandidate(prepared.recovery, Buffer.from(prepared.content, 'utf8'), { lockAlreadyHeld: prepared.lockAlreadyHeld });
436
- prepareArtifactRecoveryCommit(prepared.recovery, staged.candidateSha256, staged.semanticDigest, { lockAlreadyHeld: prepared.lockAlreadyHeld });
437
- committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld });
438
- }
439
- if (intent.state === 'preflight-ready' || intent.state === 'preflight-commit-started') {
440
- committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld });
441
- }
442
- if (committed?.state === 'preflight-passed' || intent.state === 'preflight-passed') {
443
- prepareArtifactRecoveryFinal(prepared.recovery, Buffer.from(prepared.content, 'utf8'), { lockAlreadyHeld: prepared.lockAlreadyHeld });
444
- committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld, afterPublish: options.afterPublish });
445
- } else if (intent.state === 'full-finalizer-ready') {
446
- committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld, afterPublish: options.afterPublish });
447
- }
448
- if (!committed || committed.state !== 'passed') throw new Error(`ARTIFACT_RECOVERY_STATE_INVALID: commit ended in '${committed?.state ?? intent.state}'`);
449
- return {
450
- ...prepared.result,
451
- status: 'passed',
452
- changed: false,
453
- artifactSha256: committed.finalArtifactSha256,
454
- semanticDigest: committed.finalSemanticDigest,
455
- error: null
456
- };
457
- } catch (error) {
458
- if (error instanceof Error && error.message.startsWith('TASK_WORKFLOW_FAULT_INJECTED:')) throw error;
459
- return { ...prepared.result, status: 'failed', error: {
460
- code: 'LOCAL_RECOVERY_COMMIT_FAILED', message: String(error)
461
- } };
462
- }
224
+ return prepared.result;
463
225
  }
464
226
 
465
227
  function finalizeLocalArtifact(
466
- request: LocalArtifactFinalizationRequest,
467
- authority?: LifecycleRecoveryAttestationV1,
468
- options: Readonly<{ deferLifecycleRecoveryConsumption?: boolean }> = {}
228
+ request: LocalArtifactFinalizationRequest
469
229
  ): LocalArtifactFinalizationResult {
470
- const prepared = prepareLocalArtifact(request, undefined, authority);
471
- const result = commitLocalArtifactProvenance(prepared);
472
- if (authority && !options.deferLifecycleRecoveryConsumption && result.status === 'passed') {
473
- try { consumeLifecycleRecoveryAttestation(authority); }
474
- catch (error) {
475
- return { ...result, status: 'failed', error: {
476
- code: 'LOCAL_EXECUTION_AUTHORITY_CONSUME_FAILED',
477
- message: error instanceof Error ? error.message : String(error)
478
- } };
479
- }
480
- }
481
- return result;
230
+ return prepareLocalArtifact(request).result;
482
231
  }
483
232
 
484
233
  /** Validate and seal an immutable preflight generation without publishing the formal artifact. */
485
234
  function preflightLocalArtifact(
486
- request: LocalArtifactFinalizationRequest,
487
- authority?: LifecycleRecoveryAttestationV1
235
+ request: LocalArtifactFinalizationRequest
488
236
  ): LocalArtifactFinalizationResult {
489
- const prepared = prepareLocalArtifact(request, undefined, authority, true);
237
+ const prepared = prepareLocalArtifact(request);
490
238
  return prepared.result;
491
239
  }
492
240
 
493
241
  export {
494
242
  LOCAL_ARTIFACT_REQUIRED_SECTIONS,
495
- consumeLocalArtifactFinalizationIntent,
496
243
  finalizeLocalArtifact,
497
244
  preflightLocalArtifact,
498
245
  prepareLocalArtifact,
499
246
  commitLocalArtifactProvenance,
500
- readArtifactRecoveryIntent as readLocalArtifactFinalizationIntent,
501
247
  canonicalSemanticDigest as semanticDigest,
502
248
  sha256Content,
503
249
  validateLocalArtifact
@@ -508,9 +254,7 @@ export type {
508
254
  LocalArtifactFamily,
509
255
  LocalArtifactFinalizationRequest,
510
256
  LocalArtifactFinalizationResult,
511
- LocalArtifactFinalizationIntent,
512
257
  LocalArtifactPreparation,
513
258
  LocalArtifactValidationOptions,
514
- LocalArtifactValidationResult,
515
- LocalArtifactRecoveryInfo
259
+ LocalArtifactValidationResult
516
260
  };
@@ -41,7 +41,6 @@ import type {
41
41
  OrchestrationModelPolicy,
42
42
  OrchestrationRolePolicy
43
43
  } from '../agent-clients/types.ts';
44
- import type { LifecycleRecoveryAttestationV1 } from './control-authority.ts';
45
44
  import {
46
45
  captureRepositorySnapshot,
47
46
  captureWorkspaceSnapshot,
@@ -57,8 +56,7 @@ import { extractReviewBaseline, extractReviewDiffBase, extractReviewTargetHead,
57
56
  import { resolveDeliveryTarget, resolveDiffBase, resolveTargetHead } from './delivery-target.ts';
58
57
  import { buildLifecycleFacts, canStart, recommendNext } from './capabilities.ts';
59
58
  import { hasOpenLifecycleExecution } from './activity-log.ts';
60
- import { invalidationBlocks } from './invalidation.ts';
61
- import type { InvalidationDocument } from './invalidation.ts';
59
+ import { reconcileTaskInvalidation } from './invalidation-command.ts';
62
60
  import type { LifecycleAction, LifecycleFacts } from './capabilities.ts';
63
61
  import { normalizeAgentToken } from '../agent-clients/tokens.ts';
64
62
 
@@ -175,9 +173,6 @@ type OrchestrationOptions = {
175
173
  }>;
176
174
  diffWorkspace?: (repoRoot: string, before: string, after: string) => string[];
177
175
  supportsLifecycleDelegation?: (client: AgentClientId) => boolean;
178
- validateLifecycleCapability?: () => Exclude<OrchestrationResult['error'], null> | null;
179
- consumeLifecycleCapability?: () => Exclude<OrchestrationResult['error'], null> | null;
180
- lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
181
176
  token?: () => string;
182
177
  monotonicNow?: () => number;
183
178
  sleep?: (milliseconds: number) => Promise<void>;
@@ -678,13 +673,11 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
678
673
  }
679
674
  let metadata: ReturnType<typeof parseTypedTaskFrontmatter>;
680
675
  let currentLedger: LedgerDocument | null = null;
681
- let currentInvalidation: InvalidationDocument = { operations: [], targets: [] };
682
676
  if (resolved.state === 'active') {
683
677
  const contract = validateCurrentTaskContract(content);
684
678
  if (!contract.ok) return failed('ORCHESTRATION_CURRENT_CONTRACT_INVALID', contract.message, resolved.taskId);
685
679
  metadata = contract.metadata;
686
680
  currentLedger = contract.ledger;
687
- currentInvalidation = contract.invalidation;
688
681
  } else {
689
682
  try {
690
683
  metadata = parseTypedTaskFrontmatter(content);
@@ -692,9 +685,6 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
692
685
  return failed('ORCHESTRATION_TASK_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
693
686
  }
694
687
  }
695
- if (invalidationBlocks(currentInvalidation)) {
696
- return failed('ORCHESTRATION_INVALIDATION_INCOMPLETE', 'artifact invalidation is incomplete; reconcile task.md before routing downstream work', resolved.taskId);
697
- }
698
688
  let run: OrchestrationRun | null;
699
689
  try {
700
690
  run = readRun(resolved.taskDir, options);
@@ -711,6 +701,9 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
711
701
  const routed = routeFromFacts(facts.facts);
712
702
  if (!routed) return failed('ORCHESTRATION_ROUTE_UNKNOWN', 'cannot determine a unique lifecycle action', resolved.taskId);
713
703
  if ('completion' in routed) {
704
+ if (facts.facts.executionBusy && !run?.pendingDelegation) {
705
+ return failed('ORCHESTRATION_EXECUTION_BUSY', 'clean completion requires no open lifecycle execution', resolved.taskId);
706
+ }
714
707
  const reviewRound = maxArtifactRound(fs.readdirSync(resolved.taskDir), 'review-code');
715
708
  const review = parseReviewSummary(fs.readFileSync(
716
709
  path.join(resolved.taskDir, artifactName('review-code', reviewRound)),
@@ -822,7 +815,6 @@ function prepareOrchestrationDelegationUnlocked(
822
815
  requestedModel?: string;
823
816
  requestedReasoningEffort?: string;
824
817
  lifecycleProvenance?: DelegationLifecycleProvenance;
825
- lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
826
818
  }>,
827
819
  options: OrchestrationOptions = {}
828
820
  ): OrchestrationResult {
@@ -847,6 +839,14 @@ function prepareOrchestrationDelegationUnlocked(
847
839
  return failed('ORCHESTRATION_DELEGATION_BUSY', 'the repository already has a pending lifecycle delegation', resolved.taskId);
848
840
  }
849
841
  if (run.stepCount >= run.maxSteps) return pauseOrchestration(taskRef, 'ORCHESTRATION_MAX_STEPS', 'maximum orchestration steps reached', true, options);
842
+ const invalidation = reconcileTaskInvalidation(taskRef, { repoRoot: resolved.repoRoot });
843
+ if (invalidation.status === 'failed') {
844
+ return failed(
845
+ 'ORCHESTRATION_INVALIDATION_RECONCILE_FAILED',
846
+ `${invalidation.error?.code ?? 'UNKNOWN'}: ${invalidation.error?.message ?? ''}`,
847
+ resolved.taskId
848
+ );
849
+ }
850
850
  const routed = routeOrchestration(taskRef, options);
851
851
  if (!routed.next) return routed;
852
852
  const next = routed.next;
@@ -856,16 +856,6 @@ function prepareOrchestrationDelegationUnlocked(
856
856
  if (!validModel(input.requestedReasoningEffort)) {
857
857
  return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_REQUIRED', 'prepare requires the exact requested reasoning effort', resolved.taskId);
858
858
  }
859
- if (input.client === 'codex' && !input.lifecycleProvenance) {
860
- return failed('ORCHESTRATION_CODEX_PROVENANCE_REQUIRED', 'Codex prepare requires lifecycle provenance', resolved.taskId);
861
- }
862
- if (input.lifecycleRecoveryAttestation
863
- && (input.lifecycleRecoveryAttestation.phase !== 'orchestration.prepare'
864
- || input.lifecycleRecoveryAttestation.taskId !== resolved.taskId
865
- || input.lifecycleRecoveryAttestation.artifact !== next.artifact
866
- || input.lifecycleRecoveryAttestation.round !== next.round)) {
867
- return failed('LIFECYCLE_AUTHORITY_TUPLE_MISMATCH', 'orchestration authority does not match the routed stage', resolved.taskId);
868
- }
869
859
  const expectedPolicy = run.modelPolicy[next.role];
870
860
  if (input.requestedModel !== expectedPolicy.model) {
871
861
  return failed('ORCHESTRATION_REQUESTED_MODEL_MISMATCH', `requested model does not match the persisted ${next.role} model`, resolved.taskId);
@@ -873,15 +863,6 @@ function prepareOrchestrationDelegationUnlocked(
873
863
  if (input.requestedReasoningEffort !== expectedPolicy.reasoningEffort) {
874
864
  return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_MISMATCH', `requested reasoning effort does not match the persisted ${next.role} policy`, resolved.taskId);
875
865
  }
876
- const capabilityValidationError = options.validateLifecycleCapability?.();
877
- if (capabilityValidationError) {
878
- return failed(
879
- capabilityValidationError.code,
880
- capabilityValidationError.message,
881
- resolved.taskId,
882
- capabilityValidationError.detail ? { detail: capabilityValidationError.detail } : {}
883
- );
884
- }
885
866
  let beforeFingerprint: string;
886
867
  try {
887
868
  beforeFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)({
@@ -906,15 +887,6 @@ function prepareOrchestrationDelegationUnlocked(
906
887
  lifecycleProvenance: input.lifecycleProvenance ?? null,
907
888
  beforeFingerprint
908
889
  }, { id: options.id, now: options.now, monotonicNow: options.monotonicNow });
909
- const capabilityError = options.consumeLifecycleCapability?.();
910
- if (capabilityError) {
911
- return failed(
912
- capabilityError.code,
913
- capabilityError.message,
914
- resolved.taskId,
915
- capabilityError.detail ? { detail: capabilityError.detail } : {}
916
- );
917
- }
918
890
  const updated = withUpdatedRun(run, {
919
891
  nextStage: next.stage,
920
892
  pendingDelegation: receipt
@@ -930,7 +902,6 @@ function prepareOrchestrationDelegation(
930
902
  requestedModel?: string;
931
903
  requestedReasoningEffort?: string;
932
904
  lifecycleProvenance?: DelegationLifecycleProvenance;
933
- lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
934
905
  }>,
935
906
  options: OrchestrationOptions = {}
936
907
  ): OrchestrationResult {
@@ -176,19 +176,25 @@ function equalCounts(left: ReviewFindingCounts, right: ReviewFindingCounts): boo
176
176
 
177
177
  function finalizeReviewSummaryContent(
178
178
  content: string,
179
- counts: ReviewFindingCounts
179
+ counts: ReviewFindingCounts,
180
+ options: { refreshNumericCounts?: boolean } = {}
180
181
  ): ReviewSummaryFinalizeResult {
181
182
  const parsed = parseReviewSummary(content);
182
183
  if (!parsed.ok) return parsed;
183
184
  if (parsed.summary.countState === 'numeric') {
184
185
  if (equalCounts(parsed.summary.counts!, counts)) return { ok: true, changed: false, content };
185
- return {
186
+ if (!options.refreshNumericCounts) return {
186
187
  ok: false,
187
188
  code: 'REVIEW_SUMMARY_COUNT_MISMATCH',
188
189
  message: 'review summary finding counts do not match the ledger snapshot'
189
190
  };
190
191
  }
191
192
  let line = content.slice(parsed.findingsStart, parsed.findingsEnd);
193
+ if (parsed.summary.countState === 'numeric') {
194
+ const values = [counts.blocker, counts.major, counts.minor];
195
+ let ordinal = 0;
196
+ line = line.replace(/\d+(?=\s*(?:阻塞项|主要|次要|blockers?\b|majors?\b|minors?\b))/gi, () => String(values[ordinal++]));
197
+ }
192
198
  line = line
193
199
  .replace(PLACEHOLDERS[0], String(counts.blocker))
194
200
  .replace(PLACEHOLDERS[1], String(counts.major))
@@ -7,7 +7,6 @@ export function parseReviewCommand(args: readonly string[]): ReviewFinalizationR
7
7
  const values: Record<string, string | boolean> = {};
8
8
  const flags = {
9
9
  '--stage': 'stage', '--artifact': 'artifact', '--orchestrated': 'orchestrated', '--dry-run': 'dryRun',
10
- '--recovery-id': 'recoveryId',
11
10
  '--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
12
11
  } as const;
13
12
  for (let index = 2; index < args.length; index += 1) {