@fitlab-ai/agent-infra 0.11.3 → 0.11.5

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 (172) hide show
  1. package/README.md +17 -8
  2. package/README.zh-CN.md +17 -8
  3. package/dist/bin/internal-cli.js +1 -6
  4. package/dist/lib/decide.js +4 -41
  5. package/dist/lib/git/branch-worktree.js +45 -0
  6. package/dist/lib/internal/cli-route-inventory.js +1 -4
  7. package/dist/lib/internal/task-activity.js +7 -40
  8. package/dist/lib/internal/task-artifact.js +1 -1
  9. package/dist/lib/internal/task-event.js +4 -37
  10. package/dist/lib/internal/task-finalization.js +48 -1
  11. package/dist/lib/internal/task-ledger.js +14 -44
  12. package/dist/lib/internal/task-lifecycle.js +1 -1
  13. package/dist/lib/internal/task-operation-registry.js +2 -4
  14. package/dist/lib/internal/task-review.js +5 -37
  15. package/dist/lib/internal/task-verify.js +3 -47
  16. package/dist/lib/internal/task-warning.js +4 -40
  17. package/dist/lib/platform/completion-backfill.js +63 -17
  18. package/dist/lib/platform/pr-summary.js +4 -23
  19. package/dist/lib/platform/verification-sync.js +12 -49
  20. package/dist/lib/sandbox/control/client.js +4 -1
  21. package/dist/lib/sandbox/control/executor.js +12 -4
  22. package/dist/lib/sandbox/control/protocol.js +2 -1
  23. package/dist/lib/sandbox/control/server.js +26 -143
  24. package/dist/lib/sandbox/removal.js +15 -8
  25. package/dist/lib/sandbox/task-cleanup.js +13 -20
  26. package/dist/lib/task/activity-intent.js +1 -2
  27. package/dist/lib/task/artifact-command.js +11 -4
  28. package/dist/lib/task/artifact-lifecycle.js +217 -85
  29. package/dist/lib/task/artifact-operations.js +37 -28
  30. package/dist/lib/task/artifact-receipts.js +4 -1
  31. package/dist/lib/task/artifact-schema.js +8 -7
  32. package/dist/lib/task/artifact-selection.js +25 -0
  33. package/dist/lib/task/capabilities.js +158 -39
  34. package/dist/lib/task/commands/status.js +9 -2
  35. package/dist/lib/task/control-authority.js +12 -168
  36. package/dist/lib/task/decision-intents.js +14 -4
  37. package/dist/lib/task/delivery.js +1 -43
  38. package/dist/lib/task/events.js +68 -73
  39. package/dist/lib/task/finalization-handoff.js +92 -0
  40. package/dist/lib/task/finalization.js +423 -154
  41. package/dist/lib/task/intermediate-cleanup.js +9 -148
  42. package/dist/lib/task/ledger-intents.js +159 -20
  43. package/dist/lib/task/lifecycle-path.js +75 -0
  44. package/dist/lib/task/lifecycle.js +60 -11
  45. package/dist/lib/task/local-artifact-finalization.js +13 -47
  46. package/dist/lib/task/orchestration.js +17 -7
  47. package/dist/lib/task/review-command.js +1 -2
  48. package/dist/lib/task/review-finalization.js +4 -18
  49. package/dist/lib/task/rework-intent.js +70 -4
  50. package/dist/lib/task/snapshot.js +6 -6
  51. package/dist/lib/task/verification-engine.js +25 -30
  52. package/dist/lib/task/verification.js +2 -13
  53. package/dist/lib/task/workflow-warning-intents.js +23 -10
  54. package/dist/lib/task/workspace-snapshot.js +7 -2
  55. package/dist/lib/task/write.js +1 -2
  56. package/dist/lifecycle-build-manifest.json +3 -3
  57. package/lib/decide.ts +5 -35
  58. package/lib/git/branch-worktree.ts +41 -0
  59. package/lib/internal/cli-route-inventory.ts +1 -3
  60. package/lib/internal/task-activity.ts +6 -34
  61. package/lib/internal/task-artifact.ts +1 -1
  62. package/lib/internal/task-event.ts +4 -37
  63. package/lib/internal/task-finalization.ts +47 -1
  64. package/lib/internal/task-ledger.ts +15 -41
  65. package/lib/internal/task-lifecycle.ts +2 -2
  66. package/lib/internal/task-operation-registry.ts +2 -4
  67. package/lib/internal/task-review.ts +5 -31
  68. package/lib/internal/task-verify.ts +3 -39
  69. package/lib/internal/task-warning.ts +3 -32
  70. package/lib/platform/completion-backfill.ts +78 -16
  71. package/lib/platform/pr-summary.ts +4 -25
  72. package/lib/platform/verification-sync.ts +13 -53
  73. package/lib/sandbox/control/client.ts +5 -1
  74. package/lib/sandbox/control/executor.ts +13 -4
  75. package/lib/sandbox/control/protocol.ts +3 -2
  76. package/lib/sandbox/control/server.ts +21 -146
  77. package/lib/sandbox/removal.ts +23 -7
  78. package/lib/sandbox/task-cleanup.ts +12 -20
  79. package/lib/task/activity-intent.ts +1 -2
  80. package/lib/task/artifact-command.ts +14 -4
  81. package/lib/task/artifact-lifecycle.ts +236 -85
  82. package/lib/task/artifact-operations.ts +59 -28
  83. package/lib/task/artifact-receipts.ts +4 -1
  84. package/lib/task/artifact-schema.ts +10 -7
  85. package/lib/task/artifact-selection.ts +53 -0
  86. package/lib/task/capabilities.ts +154 -40
  87. package/lib/task/commands/status.ts +11 -2
  88. package/lib/task/control-authority.ts +17 -189
  89. package/lib/task/decision-intents.ts +11 -4
  90. package/lib/task/delivery.ts +1 -40
  91. package/lib/task/events.ts +73 -74
  92. package/lib/task/finalization-handoff.ts +104 -0
  93. package/lib/task/finalization.ts +388 -122
  94. package/lib/task/intermediate-cleanup.ts +9 -125
  95. package/lib/task/ledger-intents.ts +157 -20
  96. package/lib/task/lifecycle-path.ts +98 -0
  97. package/lib/task/lifecycle.ts +58 -13
  98. package/lib/task/local-artifact-finalization.ts +14 -56
  99. package/lib/task/orchestration.ts +26 -8
  100. package/lib/task/review-command.ts +2 -5
  101. package/lib/task/review-finalization.ts +4 -20
  102. package/lib/task/rework-intent.ts +75 -5
  103. package/lib/task/snapshot.ts +6 -6
  104. package/lib/task/verification-engine.ts +29 -30
  105. package/lib/task/verification.ts +3 -16
  106. package/lib/task/workflow-warning-intents.ts +20 -10
  107. package/lib/task/workspace-snapshot.ts +3 -2
  108. package/lib/task/write.ts +1 -4
  109. package/package.json +1 -1
  110. package/templates/.agents/rules/review-handshake.en.md +4 -4
  111. package/templates/.agents/rules/review-handshake.zh-CN.md +4 -4
  112. package/templates/.agents/skills/analyze-task/SKILL.en.md +21 -5
  113. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +21 -5
  114. package/templates/.agents/skills/code-task/SKILL.en.md +14 -8
  115. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +18 -13
  116. package/templates/.agents/skills/code-task/reference/code-rules.en.md +2 -2
  117. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +2 -2
  118. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +4 -4
  119. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +4 -4
  120. package/templates/.agents/skills/code-task/reference/report-template.en.md +7 -7
  121. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +7 -7
  122. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +2 -2
  123. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +2 -2
  124. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +1 -3
  125. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +1 -3
  126. package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
  127. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
  128. package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
  129. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
  130. package/templates/.agents/skills/plan-task/SKILL.en.md +13 -10
  131. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +13 -10
  132. package/templates/.agents/skills/review-analysis/SKILL.en.md +5 -1
  133. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +5 -1
  134. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +3 -3
  135. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +3 -3
  136. package/templates/.agents/skills/review-code/SKILL.en.md +5 -1
  137. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -1
  138. package/templates/.agents/skills/review-code/reference/report-template.en.md +3 -3
  139. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +3 -3
  140. package/templates/.agents/skills/review-plan/SKILL.en.md +5 -1
  141. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +5 -1
  142. package/templates/.agents/skills/review-plan/reference/report-template.en.md +3 -3
  143. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +3 -3
  144. package/templates/.agents/skills/review-pr/config/verify.en.json +3 -1
  145. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +3 -1
  146. package/templates/.agents/skills/review-pr/reference/report-template.en.md +3 -3
  147. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +3 -3
  148. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +2 -2
  149. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +2 -2
  150. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -2
  151. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -2
  152. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +18 -2
  153. package/templates/.agents/templates/task.en.md +2 -2
  154. package/templates/.agents/templates/task.zh-CN.md +2 -2
  155. package/templates/.agents/workflows/bug-fix.en.yaml +11 -6
  156. package/templates/.agents/workflows/bug-fix.zh-CN.yaml +11 -6
  157. package/templates/.agents/workflows/feature-development.en.yaml +11 -6
  158. package/templates/.agents/workflows/feature-development.zh-CN.yaml +11 -6
  159. package/templates/.agents/workflows/refactoring.en.yaml +11 -6
  160. package/templates/.agents/workflows/refactoring.zh-CN.yaml +11 -6
  161. package/dist/lib/internal/task-override.js +0 -138
  162. package/dist/lib/sandbox/control/finalization-response.js +0 -33
  163. package/dist/lib/task/artifact-recovery.js +0 -476
  164. package/dist/lib/task/artifact-repair-intent.js +0 -181
  165. package/dist/lib/task/guard-override.js +0 -31
  166. package/dist/lib/task/human-override.js +0 -1259
  167. package/lib/internal/task-override.ts +0 -144
  168. package/lib/sandbox/control/finalization-response.ts +0 -35
  169. package/lib/task/artifact-recovery.ts +0 -572
  170. package/lib/task/artifact-repair-intent.ts +0 -227
  171. package/lib/task/guard-override.ts +0 -50
  172. package/lib/task/human-override.ts +0 -1522
@@ -3,8 +3,12 @@ import {
3
3
  dispatchTaskControlOperation,
4
4
  parseTaskControlOperation
5
5
  } from '../task/control-authority.ts';
6
- import { applyTaskFinalization } from '../task/finalization.ts';
6
+ import { applyTaskFinalization, bindTaskFinalizationReceipt, prepareTaskFinalization } from '../task/finalization.ts';
7
+ import { publishTaskFinalizationHandoff } from '../task/finalization-handoff.ts';
7
8
  import { detectRepoRoot, resolveTaskRef } from '../task/resolve-ref.ts';
9
+ import { verifyTaskEvent } from '../task/verification.ts';
10
+ import { resolveSandboxControlTransport } from './task-operation-registry.ts';
11
+ import { requestSandboxTaskFinalization, SandboxControlClientError } from '../sandbox/control/client.ts';
8
12
  import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
9
13
 
10
14
  const USAGE = 'Usage: agent-infra-internal task-finalization <N | TASK-id> complete --agent <agent>\n';
@@ -73,10 +77,52 @@ async function taskFinalization(args: string[] = []): Promise<void> {
73
77
  ...operation,
74
78
  request: { ...operation.request, taskRef: resolved.taskId }
75
79
  };
80
+ const transport = resolveSandboxControlTransport(process.env, { localWorkflow: true });
81
+ if (transport.kind === 'sandbox-local') {
82
+ const prepared = await prepareTaskFinalization(boundOperation.request, {
83
+ repoRoot,
84
+ preflight: (request, options) => verifyTaskEvent({ ...request, event: 'complete-task.hard-preflight' }, options)
85
+ });
86
+ if (prepared.status !== 'prepared') {
87
+ process.stdout.write(envelope(prepared.status, prepared.changed, true, prepared, prepared.error));
88
+ process.exitCode = exitCode(prepared.status);
89
+ return;
90
+ }
91
+ try {
92
+ const response = requestSandboxTaskFinalization({
93
+ agent: boundOperation.request.agent,
94
+ prepareHandoff: (binding) => {
95
+ const receipt = bindTaskFinalizationReceipt(repoRoot, boundOperation.request.taskRef, binding);
96
+ return publishTaskFinalizationHandoff('/share/branch', receipt, binding);
97
+ }
98
+ });
99
+ process.stdout.write(response.stdout);
100
+ process.stderr.write(response.stderr);
101
+ process.exitCode = response.exitCode ?? 1;
102
+ return;
103
+ } catch (error) {
104
+ const detail = error instanceof SandboxControlClientError
105
+ ? error.detail
106
+ : { code: 'SANDBOX_CONTROL_CLIENT_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false };
107
+ process.stdout.write(envelope(detail.retryable ? 'blocked' : 'failed', false, false, null, detail));
108
+ process.exitCode = detail.retryable ? 2 : 1;
109
+ return;
110
+ }
111
+ }
76
112
  const result = await dispatchTaskControlOperation(
77
113
  createDirectHostExecutionContext({ repoRoot }),
78
114
  boundOperation
79
115
  );
116
+ if (result.status === 'prepared') {
117
+ const detail = {
118
+ code: 'TASK_FINALIZATION_DIRECT_COMMIT_REQUIRED',
119
+ message: 'direct finalization prepared external work without committing the lifecycle transition',
120
+ retryable: false
121
+ };
122
+ process.stdout.write(envelope('failed', result.changed, true, result, detail));
123
+ process.exitCode = 1;
124
+ return;
125
+ }
80
126
  process.stdout.write(envelope(result.status, result.changed, true, result, result.error));
81
127
  process.exitCode = exitCode(result.status);
82
128
  }
@@ -2,23 +2,22 @@ import fs from 'node:fs';
2
2
 
3
3
  import { applyLedgerIntent } from '../task/ledger-intents.ts';
4
4
  import type { LedgerIntent } from '../task/ledger-intents.ts';
5
- import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.ts';
6
5
  import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, isReviewStage, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from '../task/ledger.ts';
7
6
  import { resolveTaskRef } from '../task/resolve-ref.ts';
8
7
  import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
9
8
  import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
10
9
 
11
- const USAGE = `Usage: agent-infra-internal task-ledger <task-ref> <intent> [intent flags] [--dry-run]\n\nIntents: finding-upsert, finding-respond, finding-review, decision-next-id, decision-upsert, rework-intent-upsert, stage-status\n`;
10
+ const USAGE = `Usage: agent-infra-internal task-ledger <task-ref> <intent> [intent flags] [--dry-run]\n\nIntents: finding-upsert, finding-respond, finding-review, decision-next-id, decision-upsert, rework-intent-upsert, rework-intent-rebuild, stage-status\n`;
12
11
  const FLAGS: Record<string, string> = {
13
12
  '--stage': 'stage', '--review-artifact': 'reviewArtifact', '--ordinal': 'ordinal',
14
13
  '--severity': 'severity', '--evidence': 'evidence', '--id': 'id', '--round': 'round',
15
14
  '--status': 'status', '--artifact': 'artifact', '--needs-implementation': 'needsImplementation',
16
15
  '--intent-id': 'intentId', '--finding-id': 'findingId', '--source-artifact': 'sourceArtifact',
17
- '--source-sha256': 'sourceSha256', '--target': 'target',
18
- '--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
16
+ '--source-sha256': 'sourceSha256', '--classification': 'classification'
19
17
  };
20
18
  const NUMERIC = new Set(['ordinal', 'round']);
21
19
  const BOOLEAN = new Set(['needsImplementation']);
20
+ const REWORK_CLASSIFICATIONS = new Set(['scope-or-requirement', 'design', 'implementation', 'human-decision', 'insufficient-evidence']);
22
21
 
23
22
  function usageFailure(message: string): void {
24
23
  process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'LEDGER_PAYLOAD_INVALID', message } })}\n`);
@@ -38,6 +37,7 @@ async function taskLedger(args: string[] = []): Promise<void> {
38
37
  internalHandlerRoute('task-ledger', 'decision-next-id', kind),
39
38
  internalHandlerRoute('task-ledger', 'decision-upsert', kind),
40
39
  internalHandlerRoute('task-ledger', 'rework-intent-upsert', kind),
40
+ internalHandlerRoute('task-ledger', 'rework-intent-rebuild', kind),
41
41
  internalHandlerRoute('task-ledger', 'stage-status', kind)
42
42
  ].some(Boolean)) {
43
43
  usageFailure(`unknown ledger intent '${kind}'`); return;
@@ -67,23 +67,23 @@ async function taskLedger(args: string[] = []): Promise<void> {
67
67
  'finding-review': ['id', 'status', 'evidence'],
68
68
  'decision-next-id': [],
69
69
  'decision-upsert': ['id', 'stage', 'artifact'],
70
- 'rework-intent-upsert': ['intentId', 'findingId', 'sourceArtifact', 'sourceSha256', 'target'],
70
+ 'rework-intent-upsert': ['intentId', 'findingId', 'sourceArtifact', 'sourceSha256', 'classification'],
71
+ 'rework-intent-rebuild': [],
71
72
  'stage-status': ['stage']
72
73
  };
73
74
  const optional: Record<string, string[]> = {
74
75
  'finding-review': ['needsImplementation'],
75
- 'decision-upsert': ['needsImplementation']
76
+ 'decision-upsert': ['needsImplementation'],
77
+ 'rework-intent-rebuild': ['findingId', 'sourceArtifact', 'sourceSha256', 'classification']
76
78
  };
77
- const overrideFields = kind === 'stage-status' || kind === 'decision-next-id' || kind === 'rework-intent-upsert'
78
- ? []
79
- : ['overrideTicket', 'overrideTarget', 'overrideScope'];
80
- const allowed = new Set(['kind', 'taskRef', 'dryRun', ...required[kind]!, ...(optional[kind] ?? []), ...overrideFields]);
79
+ const allowed = new Set(['kind', 'taskRef', 'dryRun', ...required[kind]!, ...(optional[kind] ?? [])]);
81
80
  const unexpected = Object.keys(values).find((key) => !allowed.has(key));
82
81
  const missing = required[kind]!.find((key) => values[key] === undefined);
83
82
  if (unexpected) { usageFailure(`${kind} does not accept '${unexpected}'`); return; }
84
83
  if (missing) { usageFailure(`${kind} requires '${missing}'`); return; }
85
- const dryRunConflict = overrideDryRunConflict(values);
86
- if (dryRunConflict) { usageFailure(dryRunConflict.message); return; }
84
+ if (values.classification !== undefined && !REWORK_CLASSIFICATIONS.has(String(values.classification))) {
85
+ usageFailure(`invalid rework classification '${values.classification}'`); return;
86
+ }
87
87
  if (kind === 'stage-status') {
88
88
  if (!isReviewStage(String(values.stage))) { usageFailure(`stage-status requires a valid review stage`); return; }
89
89
  const resolved = resolveTaskRef(taskRef);
@@ -117,42 +117,16 @@ async function taskLedger(args: string[] = []): Promise<void> {
117
117
  const lockResolved = resolveTaskRef(taskRef);
118
118
  if (!lockResolved.ok) { usageFailure(lockResolved.message); return; }
119
119
  let result;
120
- let humanOverride: unknown = null;
121
120
  try {
122
- result = await withTaskExecutionLock(lockResolved.repoRoot, lockResolved.taskId, `task-ledger.${kind}`, async () => {
123
- let current = applyLedgerIntent(values as LedgerIntent);
124
- if (current.status !== 'failed' || !values.overrideTicket) return current;
125
- if (!values.overrideTarget || !values.overrideScope) { usageFailure('override ticket requires target and scope'); return current; }
126
- const consumed = await consumeHumanOverride({
127
- taskRef,
128
- ticketId: String(values.overrideTicket),
129
- failureId: failureId('ledger-intent', current.error?.code ?? 'TASK_STATE_MISMATCH'),
130
- target: String(values.overrideTarget),
131
- scope: String(values.overrideScope)
132
- }, {
133
- effectExecutor: (capability) => {
134
- const retried = applyLedgerIntent(values as LedgerIntent, { manualOverride: capability });
135
- current = retried;
136
- return retried.status === 'failed'
137
- ? { code: 'OVERRIDE_EFFECT_FAILED', message: `${retried.error?.code ?? 'LEDGER_FAILED'}: ${retried.error?.message ?? 'manual ledger effect failed'}` }
138
- : null;
139
- }
140
- });
141
- humanOverride = consumed;
142
- return current;
143
- });
121
+ result = await withTaskExecutionLock(lockResolved.repoRoot, lockResolved.taskId, `task-ledger.${kind}`, async () => applyLedgerIntent(values as LedgerIntent));
144
122
  } catch (error) {
145
123
  if (!(error instanceof TaskExecutionLockError)) throw error;
146
124
  process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: error.code, message: error.message } })}\n`);
147
125
  process.exitCode = 1;
148
126
  return;
149
127
  }
150
- process.stdout.write(`${JSON.stringify(humanOverride ? { ...result, humanOverride } : result)}\n`);
151
- const overrideFailed = Boolean(
152
- humanOverride && typeof humanOverride === 'object' &&
153
- (humanOverride as { status?: unknown }).status === 'failed'
154
- );
155
- if (result.status === 'failed' || overrideFailed) process.exitCode = 1;
128
+ process.stdout.write(`${JSON.stringify(result)}\n`);
129
+ if (result.status === 'failed') process.exitCode = 1;
156
130
  }
157
131
 
158
132
  export { taskLedger };
@@ -10,7 +10,7 @@ import type { TaskLifecycleResult } from '../task/lifecycle.ts';
10
10
  import type { AgentClientLifecycleRecoveryResult as LifecycleRecoveryResult } from '../agent-clients/adapter.ts';
11
11
  import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
12
12
 
13
- const USAGE = `Usage: agent-infra-internal task-lifecycle <N | TASK-id> <intent> --agent <agent> [intent flags] [--dry-run]\n\nIntents: ${lifecycleIntentCatalog.join(', ')}\nrecover-started: --auto\nOverride: --override-ticket <ticket> --override-target <target> --override-scope <scope>\n`;
13
+ const USAGE = `Usage: agent-infra-internal task-lifecycle <N | TASK-id> <intent> --agent <agent> [intent flags] [--dry-run]\n\nIntents: ${lifecycleIntentCatalog.join(', ')}\nrecover-started: --auto\n`;
14
14
 
15
15
  function usageFailure(message: string): void {
16
16
  process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'LIFECYCLE_PAYLOAD_INVALID', message } })}\n`);
@@ -62,7 +62,7 @@ async function taskLifecycle(args: string[] = []): Promise<void> {
62
62
  const result = await dispatchTaskControlOperation(
63
63
  createDirectHostExecutionContext({ repoRoot }),
64
64
  operation
65
- ) as (TaskLifecycleResult | LifecycleRecoveryResult) & { humanOverride?: unknown };
65
+ ) as TaskLifecycleResult | LifecycleRecoveryResult;
66
66
  process.stdout.write(`${JSON.stringify(result)}\n`);
67
67
  if (result.status === 'failed' || result.status === 'owner-unknown' || result.status === 'conflict') process.exitCode = 1;
68
68
  }
@@ -125,6 +125,7 @@ function internalTaskRoutes(): TaskOperationDescriptor[] {
125
125
  descriptor('internal', 'task-ledger', 'finding-review', 'task-bound', 'progress'),
126
126
  descriptor('internal', 'task-ledger', 'decision-upsert', 'task-bound', 'progress'),
127
127
  descriptor('internal', 'task-ledger', 'rework-intent-upsert', 'task-bound', 'progress'),
128
+ descriptor('internal', 'task-ledger', 'rework-intent-rebuild', 'task-bound', 'progress'),
128
129
  descriptor('internal', 'task-warning', 'list', 'task-bound', 'diagnostic'),
129
130
  descriptor('internal', 'task-warning', 'add', 'task-bound', 'progress'),
130
131
  descriptor('internal', 'task-warning', 'set-status', 'task-bound', 'progress'),
@@ -142,9 +143,6 @@ function internalTaskRoutes(): TaskOperationDescriptor[] {
142
143
  descriptor('internal', 'task-review', 'finalize-summary', 'task-bound', 'progress'),
143
144
  descriptor('internal', 'task-event', 'event', 'task-bound', 'progress'),
144
145
  descriptor('internal', 'task-invalidation', 'reconcile', 'task-bound', 'progress'),
145
- descriptor('internal', 'task-override', 'diagnose', 'task-bound', 'diagnostic'),
146
- descriptor('internal', 'task-override', 'issue', 'task-bound', 'progress'),
147
- descriptor('internal', 'task-override', 'consume', 'task-bound', 'progress'),
148
146
  descriptor('internal', 'task-lifecycle', 'intent', 'task-bound', 'progress'),
149
147
  descriptor('internal', 'task-finalization', 'complete', 'task-bound', 'progress'),
150
148
  descriptor('internal', 'task-short-id', 'list', 'conditional', 'diagnostic'),
@@ -223,7 +221,7 @@ export const INTERNAL_DISPATCHER_ROUTES = Object.freeze([
223
221
  'platform-comment', 'platform-issue', 'platform-pr', 'platform-pr-review', 'pr-review-grade',
224
222
  'platform-checks', 'task-context', 'task-ledger', 'task-warning', 'task-activity', 'task-artifact',
225
223
  'task-orchestration', 'task-review', 'task-event', 'task-invalidation', 'task-lifecycle',
226
- 'task-finalization', 'task-override', 'task-short-id', 'task-snapshot', 'task-verify', 'task-validate', 'manual-validation'
224
+ 'task-finalization', 'task-short-id', 'task-snapshot', 'task-verify', 'task-validate', 'manual-validation'
227
225
  ]);
228
226
  export const PUBLIC_DISPATCHER_ROUTES = Object.freeze([
229
227
  'agent-client', 'cp', 'data', 'decide', 'help', 'init', 'merge', 'run', 'sandbox', 'server', 'task', 'sync', 'update', 'version'
@@ -1,11 +1,10 @@
1
1
  import { parseReviewCommand } from '../task/review-command.ts';
2
2
  import { finalizeReviewSummary, preflightReviewSummary } from '../task/review-finalization.ts';
3
- import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.ts';
4
3
  import { resolveTaskRef } from '../task/resolve-ref.ts';
5
4
  import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
6
5
  import { ensureInternalHandlerRoute } from './cli-route-inventory.ts';
7
6
 
8
- const USAGE = 'Usage: agent-infra-internal task-review <task-ref> <preflight|finalize-summary> --stage <analysis|plan|code> --artifact <review-*.md> [--orchestrated] [--dry-run] [--override-ticket <ticket> --override-target <target> --override-scope <scope>]\n';
7
+ const USAGE = 'Usage: agent-infra-internal task-review <task-ref> <preflight|finalize-summary> --stage <analysis|plan|code> --artifact <review-*.md> [--orchestrated] [--dry-run]\n';
9
8
 
10
9
  function failUsage(message: string): void {
11
10
  process.stdout.write(`${JSON.stringify({
@@ -27,37 +26,16 @@ async function taskReview(args: string[] = []): Promise<void> {
27
26
  let request;
28
27
  try { request = parseReviewCommand(args); }
29
28
  catch (error) { failUsage(error instanceof Error ? error.message : String(error)); return; }
30
- const { stage, artifact, dryRun, orchestrated, overrideTicket, overrideTarget, overrideScope } = request;
29
+ const { stage, artifact, dryRun, orchestrated } = request;
31
30
  const operation = args[1]!;
32
- const dryRunConflict = overrideDryRunConflict({ dryRun, overrideTicket, overrideTarget, overrideScope });
33
- if (dryRunConflict) { failUsage(dryRunConflict.message); return; }
34
31
  const resolved = resolveTaskRef(args[0]!);
35
32
  if (!resolved.ok) { failUsage(resolved.message); return; }
36
33
  let result;
37
- let humanOverride: unknown = null;
38
34
  try {
39
35
  result = await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-review.${operation}`, async () => {
40
- let current = operation === 'preflight'
36
+ return operation === 'preflight'
41
37
  ? preflightReviewSummary({ taskRef: args[0]!, stage, artifact, orchestrated, dryRun }, { lockAlreadyHeld: true })
42
38
  : finalizeReviewSummary({ taskRef: args[0]!, stage, artifact, orchestrated, dryRun }, { lockAlreadyHeld: true });
43
- if (operation === 'preflight') return current;
44
- if (current.status !== 'failed' || !overrideTicket) return current;
45
- if (!overrideTarget || !overrideScope) { failUsage('override ticket requires target and scope'); return current; }
46
- const consumed = await consumeHumanOverride({
47
- taskRef: args[0]!, ticketId: overrideTicket,
48
- failureId: failureId('review-finalization', current.error?.code ?? 'TASK_STATE_MISMATCH'),
49
- target: overrideTarget, scope: overrideScope
50
- }, {
51
- effectExecutor: (capability) => {
52
- const retried = finalizeReviewSummary({ taskRef: args[0]!, stage, artifact, orchestrated, dryRun }, { lockAlreadyHeld: true, manualOverride: capability });
53
- current = retried;
54
- return retried.status === 'failed' || retried.status === 'planned'
55
- ? { code: 'OVERRIDE_EFFECT_FAILED', message: retried.status === 'planned' ? 'producer returned planned; no review effect was committed' : `${retried.error?.code ?? 'REVIEW_FAILED'}: ${retried.error?.message ?? 'manual review effect failed'}` }
56
- : null;
57
- }
58
- });
59
- humanOverride = consumed;
60
- return current;
61
39
  });
62
40
  } catch (error) {
63
41
  if (!(error instanceof TaskExecutionLockError)) throw error;
@@ -65,12 +43,8 @@ async function taskReview(args: string[] = []): Promise<void> {
65
43
  process.exitCode = 1;
66
44
  return;
67
45
  }
68
- process.stdout.write(`${JSON.stringify(humanOverride ? { ...result, humanOverride } : result)}\n`);
69
- const overrideFailed = Boolean(
70
- humanOverride && typeof humanOverride === 'object' &&
71
- (humanOverride as { status?: unknown }).status === 'failed'
72
- );
73
- if (result.status === 'failed' || overrideFailed) process.exitCode = 1;
46
+ process.stdout.write(`${JSON.stringify(result)}\n`);
47
+ if (result.status === 'failed') process.exitCode = 1;
74
48
  }
75
49
 
76
50
  export { taskReview };
@@ -1,10 +1,9 @@
1
1
  import { renderTaskVerification, verifyTaskEvent } from '../task/verification.ts';
2
- import { consumeHumanOverride, failureId } from '../task/human-override.ts';
3
2
  import { resolveTaskRef } from '../task/resolve-ref.ts';
4
3
  import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
5
4
  import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
6
5
 
7
- const USAGE = 'Usage: agent-infra-internal task-verify <N | TASK-id> <verification-event> [--artifact <canonical.md>] [--format json|text] [--override-ticket <ticket> --override-target <target> --override-scope <scope>]\n';
6
+ const USAGE = 'Usage: agent-infra-internal task-verify <N | TASK-id> <verification-event> [--artifact <canonical.md>] [--format json|text]\n';
8
7
 
9
8
  function fail(message: string): void {
10
9
  process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'VERIFY_PAYLOAD_INVALID', message } })}\n`);
@@ -21,63 +20,28 @@ async function taskVerify(args: string[] = []): Promise<void> {
21
20
  const event = args[1];
22
21
  let artifact: string | undefined;
23
22
  let format: 'json' | 'text' = 'json';
24
- let overrideTicket: string | undefined;
25
- let overrideTarget: string | undefined;
26
- let overrideScope: string | undefined;
27
23
  const seen = new Set<string>();
28
24
  for (let index = 2; index < args.length; index += 1) {
29
25
  const flag = args[index]!;
30
- if (!['--artifact', '--format', '--override-ticket', '--override-target', '--override-scope'].includes(flag)) { fail(`unknown option '${flag}'`); return; }
26
+ if (!['--artifact', '--format'].includes(flag)) { fail(`unknown option '${flag}'`); return; }
31
27
  if (seen.has(flag)) { fail(`duplicate option '${flag}'`); return; }
32
28
  const value = args[++index];
33
29
  if (!value || value.startsWith('--')) { fail(`option '${flag}' requires a value`); return; }
34
30
  seen.add(flag);
35
31
  if (flag === '--artifact') artifact = value;
36
- else if (flag === '--override-ticket') overrideTicket = value;
37
- else if (flag === '--override-target') overrideTarget = value;
38
- else if (flag === '--override-scope') overrideScope = value;
39
32
  else if (value === 'json' || value === 'text') format = value;
40
33
  else { fail("option '--format' requires 'json' or 'text'"); return; }
41
34
  }
42
35
  const resolved = resolveTaskRef(taskRef);
43
36
  if (!resolved.ok) { fail(resolved.message); return; }
44
37
  let result;
45
- let humanOverride: unknown = null;
46
38
  try {
47
- result = await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-verify.${event}`, async () => {
48
- let current = await verifyTaskEvent({ taskRef, event, ...(artifact ? { artifact } : {}) });
49
- if (current.status === 'pass' || !overrideTicket) return current;
50
- if (!overrideTarget || !overrideScope) { fail('override ticket requires target and scope'); return current; }
51
- const failureProducer = current.status === 'blocked' || current.status === 'fail'
52
- ? 'verification-engine'
53
- : 'task-verify';
54
- const failureCode = current.status === 'blocked'
55
- ? 'CHECK_BLOCKED'
56
- : current.status === 'fail'
57
- ? 'CHECK_FAILED'
58
- : current.error?.code ?? 'VERIFY_TASK_STATE_MISMATCH';
59
- const consumed = await consumeHumanOverride({
60
- taskRef, ticketId: overrideTicket,
61
- failureId: failureId(failureProducer, failureCode),
62
- target: overrideTarget, scope: overrideScope
63
- }, {
64
- effectExecutor: async (capability) => {
65
- const retried = await verifyTaskEvent({ taskRef, event, ...(artifact ? { artifact } : {}) }, { manualOverride: capability });
66
- current = retried;
67
- return retried.status !== 'pass'
68
- ? { code: 'OVERRIDE_EFFECT_FAILED', message: `${retried.error?.code ?? 'VERIFY_FAILED'}: ${retried.error?.message ?? 'manual verification effect failed'}` }
69
- : null;
70
- }
71
- });
72
- humanOverride = consumed;
73
- return current;
74
- });
39
+ result = await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-verify.${event}`, async () => verifyTaskEvent({ taskRef, event, ...(artifact ? { artifact } : {}) }));
75
40
  } catch (error) {
76
41
  if (!(error instanceof TaskExecutionLockError)) throw error;
77
42
  fail(`${error.code}: ${error.message}`);
78
43
  return;
79
44
  }
80
- if (humanOverride) result = { ...result, humanOverride } as typeof result;
81
45
  process.stdout.write(format === 'text' ? renderTaskVerification(result) : `${JSON.stringify(result)}\n`);
82
46
  process.exitCode = result.status === 'pass' ? 0 : result.status === 'blocked' ? 2 : 1;
83
47
  }
@@ -1,6 +1,5 @@
1
1
  import { applyWorkflowWarningIntent } from '../task/workflow-warning-intents.ts';
2
2
  import type { WorkflowWarningIntent } from '../task/workflow-warning-intents.ts';
3
- import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.ts';
4
3
  import { resolveTaskRef } from '../task/resolve-ref.ts';
5
4
  import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
6
5
  import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
@@ -9,7 +8,6 @@ const USAGE = `Usage: agent-infra-internal task-warning <task-ref> <add|set-stat
9
8
  const FLAGS: Record<string, string> = {
10
9
  '--step': 'step', '--severity': 'severity', '--code': 'code', '--target': 'target',
11
10
  '--message': 'message', '--action': 'action', '--id': 'id', '--status': 'status', '--resolution': 'resolution'
12
- , '--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
13
11
  };
14
12
 
15
13
  function usageFailure(message: string): void {
@@ -48,50 +46,23 @@ async function taskWarning(args: string[] = []): Promise<void> {
48
46
  };
49
47
  const optional = kind === 'list' ? ['status'] : [];
50
48
  const allowed = new Set(['kind', 'taskRef', 'dryRun', ...required[kind]!, ...optional]);
51
- allowed.add('overrideTicket'); allowed.add('overrideTarget'); allowed.add('overrideScope');
52
49
  const unexpected = Object.keys(values).find((key) => !allowed.has(key));
53
50
  const missing = required[kind]!.find((key) => values[key] === undefined);
54
51
  if (unexpected) { usageFailure(`${kind} does not accept '${unexpected}'`); return; }
55
52
  if (missing) { usageFailure(`${kind} requires '${missing}'`); return; }
56
- const dryRunConflict = overrideDryRunConflict(values);
57
- if (dryRunConflict) { usageFailure(dryRunConflict.message); return; }
58
53
  const resolved = resolveTaskRef(taskRef);
59
54
  if (!resolved.ok) { usageFailure(resolved.message); return; }
60
55
  let result;
61
- let humanOverride: unknown = null;
62
56
  try {
63
- result = await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-warning.${kind}`, async () => {
64
- let current = applyWorkflowWarningIntent(values as WorkflowWarningIntent);
65
- if (current.status !== 'failed' || !values.overrideTicket) return current;
66
- if (!values.overrideTarget || !values.overrideScope) { usageFailure('override ticket requires target and scope'); return current; }
67
- const consumed = await consumeHumanOverride({
68
- taskRef, ticketId: String(values.overrideTicket),
69
- failureId: failureId('workflow-warning', current.error?.code ?? 'TASK_STATE_MISMATCH'),
70
- target: String(values.overrideTarget), scope: String(values.overrideScope)
71
- }, {
72
- effectExecutor: (capability) => {
73
- const retried = applyWorkflowWarningIntent(values as WorkflowWarningIntent, { manualOverride: capability });
74
- current = retried;
75
- return retried.status === 'failed' || retried.status === 'planned'
76
- ? { code: 'OVERRIDE_EFFECT_FAILED', message: retried.status === 'planned' ? 'producer returned planned; no workflow warning effect was committed' : `${retried.error?.code ?? 'WARNING_FAILED'}: ${retried.error?.message ?? 'manual warning effect failed'}` }
77
- : null;
78
- }
79
- });
80
- humanOverride = consumed;
81
- return current;
82
- });
57
+ result = await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-warning.${kind}`, async () => applyWorkflowWarningIntent(values as WorkflowWarningIntent));
83
58
  } catch (error) {
84
59
  if (!(error instanceof TaskExecutionLockError)) throw error;
85
60
  process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: error.code, message: error.message } })}\n`);
86
61
  process.exitCode = 1;
87
62
  return;
88
63
  }
89
- process.stdout.write(`${JSON.stringify(humanOverride ? { ...result, humanOverride } : result)}\n`);
90
- const overrideFailed = Boolean(
91
- humanOverride && typeof humanOverride === 'object' &&
92
- (humanOverride as { status?: unknown }).status === 'failed'
93
- );
94
- if (result.status === 'failed' || overrideFailed) process.exitCode = 1;
64
+ process.stdout.write(`${JSON.stringify(result)}\n`);
65
+ if (result.status === 'failed') process.exitCode = 1;
95
66
  }
96
67
 
97
68
  export { taskWarning };
@@ -19,6 +19,24 @@ type CompletionBackfillResult = PlatformResult & {
19
19
  artifacts: Array<{ artifact: string; status: PlatformResult['status'] }>;
20
20
  warnings: Array<{ id: string; status: string }>;
21
21
  };
22
+ type CompletionBackfillEligibility = Readonly<{
23
+ status: 'ready' | 'failed';
24
+ eligible: boolean;
25
+ error: { code: string; message: string; retryable: boolean } | null;
26
+ }>;
27
+ type BackfillArtifactReference = Readonly<
28
+ | { status: 'ignored'; artifact: null; error: null }
29
+ | { status: 'ready'; artifact: string; error: null }
30
+ | { status: 'failed'; artifact: null; error: { code: string; message: string; retryable: false } }
31
+ >;
32
+ type BackfillWarningMatch = Readonly<{
33
+ warning: ReturnType<typeof getOpenWorkflowWarnings>[number];
34
+ artifact: string;
35
+ }>;
36
+ type BackfillWarningMatches = Readonly<
37
+ | { status: 'ready'; matches: BackfillWarningMatch[]; error: null }
38
+ | { status: 'failed'; matches: []; error: { code: string; message: string; retryable: false } }
39
+ >;
22
40
 
23
41
  function result(
24
42
  base: PlatformResult,
@@ -28,18 +46,65 @@ function result(
28
46
  return { ...base, artifacts, warnings };
29
47
  }
30
48
 
31
- function excludedPrReview(taskDir: string, message: string): string | null {
32
- const names = [...message.matchAll(/(?:^|[^A-Za-z0-9_-])(pr-review(?:-r[1-9]\d*)?\.md)(?=$|[^A-Za-z0-9_.-])/g)]
49
+ function referencedBackfillArtifact(taskDir: string, message: string): BackfillArtifactReference {
50
+ const names = [...message.matchAll(/(?:^|[^A-Za-z0-9_-])([a-z][a-z-]*(?:-r[1-9]\d*)?\.md)(?=$|[^A-Za-z0-9_.-])/g)]
33
51
  .map((match) => match[1]!);
34
- if (names.length !== 1) return null;
52
+ if (names.length !== 1) return { status: 'ignored', artifact: null, error: null };
35
53
  const parsed = parseArtifactName(names[0]!);
36
- if (!parsed || parsed.family !== 'pr-review') return null;
54
+ if (!parsed || (parsed.family !== 'pr-review' && !COMPLETION_BACKFILL_FAMILIES.includes(parsed.family as typeof COMPLETION_BACKFILL_FAMILIES[number]))) {
55
+ return { status: 'ignored', artifact: null, error: null };
56
+ }
37
57
  const artifactPath = path.join(taskDir, parsed.name);
38
58
  try {
39
59
  const stat = fs.lstatSync(artifactPath);
40
- return stat.isFile() && !stat.isSymbolicLink() ? parsed.name : null;
41
- } catch {
42
- return null;
60
+ if (stat.isSymbolicLink()) return {
61
+ status: 'failed', artifact: null,
62
+ error: { code: 'ARTIFACT_TOPOLOGY_CONFLICT', message: `SYMBOLIC_LINK: symbolic links are not workflow artifacts: ${parsed.name}`, retryable: false }
63
+ };
64
+ if (!stat.isFile()) return {
65
+ status: 'failed', artifact: null,
66
+ error: { code: 'ARTIFACT_TOPOLOGY_CONFLICT', message: `NON_REGULAR_FILE: artifact is not a regular file: ${parsed.name}`, retryable: false }
67
+ };
68
+ fs.accessSync(artifactPath, fs.constants.R_OK);
69
+ return { status: 'ready', artifact: parsed.name, error: null };
70
+ } catch (error) {
71
+ return {
72
+ status: 'failed', artifact: null,
73
+ error: { code: 'ARTIFACT_READ_FAILED', message: `Unable to inspect referenced artifact '${parsed.name}': ${error instanceof Error ? error.message : String(error)}`, retryable: false }
74
+ };
75
+ }
76
+ }
77
+
78
+ function matchingBackfillWarnings(taskDir: string, content: string): BackfillWarningMatches {
79
+ const matches: BackfillWarningMatch[] = [];
80
+ for (const warning of getOpenWorkflowWarnings(content)) {
81
+ if (warning.step !== 'complete-task' || warning.code !== 'COMMENT_SYNC_FAILED' || warning.target !== 'artifact') continue;
82
+ const reference = referencedBackfillArtifact(taskDir, warning.message);
83
+ if (reference.status === 'failed') return { status: 'failed', matches: [], error: reference.error };
84
+ if (reference.status === 'ready') matches.push({ warning, artifact: reference.artifact });
85
+ }
86
+ return { status: 'ready', matches, error: null };
87
+ }
88
+
89
+ function inspectCompletionBackfillEligibility(
90
+ taskRef: string,
91
+ options: Pick<CompletionBackfillOptions, 'cwd'> = {}
92
+ ): CompletionBackfillEligibility {
93
+ const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
94
+ if (!resolved.ok) return {
95
+ status: 'failed', eligible: false,
96
+ error: { code: resolved.code, message: resolved.message, retryable: false }
97
+ };
98
+ try {
99
+ const content = fs.readFileSync(resolved.taskMdPath, 'utf8');
100
+ const matching = matchingBackfillWarnings(resolved.taskDir, content);
101
+ if (matching.status === 'failed') return { status: 'failed', eligible: false, error: matching.error };
102
+ return { status: 'ready', eligible: matching.matches.length > 0, error: null };
103
+ } catch (error) {
104
+ return {
105
+ status: 'failed', eligible: false,
106
+ error: { code: 'TASK_READ_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false }
107
+ };
43
108
  }
44
109
  }
45
110
 
@@ -60,6 +125,8 @@ async function backfillCompletionComments(
60
125
  }
61
126
  }), [], []);
62
127
  const initialContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
128
+ const matching = matchingBackfillWarnings(resolved.taskDir, initialContent);
129
+ if (matching.status === 'failed') return result(platformResult('failed', { error: matching.error }), [], []);
63
130
  const issueIdentity = taskIssueIdentity(parseTaskFrontmatter(initialContent));
64
131
  if (!issueIdentity) return result(platformResult('no-op', {
65
132
  error: { code: 'ISSUE_NOT_LINKED', message: 'Task has no valid platform issue identity', retryable: false }
@@ -92,17 +159,12 @@ async function backfillCompletionComments(
92
159
  }), artifacts, []);
93
160
  }
94
161
 
95
- const matching = getOpenWorkflowWarnings(initialContent).flatMap((warning) => {
96
- if (warning.step !== 'complete-task' || warning.code !== 'COMMENT_SYNC_FAILED' || warning.target !== 'artifact') return [];
97
- const excluded = excludedPrReview(resolved.taskDir, warning.message);
98
- return excluded ? [{ warning, excluded }] : [];
99
- });
100
162
  const warnings: CompletionBackfillResult['warnings'] = [];
101
163
  let metadata: ReturnType<typeof captureTaskWriteMetadata> | null = null;
102
- for (const { warning, excluded } of matching) {
164
+ for (const { warning, artifact } of matching.matches) {
103
165
  metadata ??= captureTaskWriteMetadata();
104
166
  const resolution = [
105
- `excluded=${excluded}`,
167
+ `artifact=${artifact}`,
106
168
  `completionFamilies=${COMPLETION_BACKFILL_FAMILIES.join(',')}`,
107
169
  `synchronized=${inventory.artifacts.map((artifact) => artifact.name).join(',') || 'none'}`,
108
170
  `completedAt=${metadata.timestamp}`
@@ -131,5 +193,5 @@ async function backfillCompletionComments(
131
193
  }), artifacts, warnings);
132
194
  }
133
195
 
134
- export { backfillCompletionComments };
135
- export type { CompletionBackfillOptions, CompletionBackfillResult };
196
+ export { backfillCompletionComments, inspectCompletionBackfillEligibility };
197
+ export type { CompletionBackfillEligibility, CompletionBackfillOptions, CompletionBackfillResult };
@@ -3,7 +3,6 @@ import path from 'node:path';
3
3
 
4
4
  import { enumerateArtifacts } from '../task/artifacts.ts';
5
5
  import { parseTypedTaskFrontmatter } from '../task/frontmatter.ts';
6
- import { renderHumanOverrideAudit } from '../task/human-override.ts';
7
6
  import { resolveTaskRef } from '../task/resolve-ref.ts';
8
7
  import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
9
8
  import { readPrDeliveryFact } from '../task/pr-delivery-fact.ts';
@@ -11,7 +10,7 @@ import type { PrDeliveryFact } from '../task/pr-delivery-fact.ts';
11
10
  import { resolvePlatformProviderContext } from './context.ts';
12
11
  import type { LoadedContext, PlatformClient } from './context.ts';
13
12
  import { normalizeCommentContent } from './issue-comments.ts';
14
- import { CONTROL_MARKER_PATTERN, escapeHtmlText, sanitizeMarkdownDocument, splitDocumentPlaceholder } from './comment-safety.ts';
13
+ import { CONTROL_MARKER_PATTERN, sanitizeMarkdownDocument, splitDocumentPlaceholder } from './comment-safety.ts';
15
14
  import { platformResult } from './types.ts';
16
15
  import type { PlatformResult } from './types.ts';
17
16
  import type { OperationWarning } from '../task/operation-outcome.ts';
@@ -74,25 +73,6 @@ function summaryMarker(taskId: string): string {
74
73
  return `<!-- sync-pr:${taskId}:summary -->`;
75
74
  }
76
75
 
77
- function escapeSummaryAuditText(value: string): string {
78
- return escapeHtmlText(value).replace(/\r\n?/g, '\n');
79
- }
80
-
81
- function renderSummaryAuditLine(value: string): string {
82
- const safe = escapeSummaryAuditText(value);
83
- const longestRun = Math.max(0, ...(safe.match(/`+/g) || []).map((run) => run.length));
84
- const delimiter = '`'.repeat(longestRun + 1);
85
- return `${delimiter} ${safe} ${delimiter}`;
86
- }
87
-
88
- function renderSafeHumanOverrideAudit(value: string): string {
89
- const normalized = value.trim().replace(/\r\n?/g, '\n');
90
- if (!normalized) return '';
91
- const [heading, ...rows] = normalized.split('\n');
92
- const safeHeading = heading === '## Human Override Audit' ? heading : renderSummaryAuditLine(heading || '');
93
- return [safeHeading, ...rows.map((row) => row ? renderSummaryAuditLine(row) : '')].join('\n');
94
- }
95
-
96
76
  function isSafeSummaryEnvelope(value: string, taskId: string): boolean {
97
77
  return value.includes(summaryMarker(taskId))
98
78
  && (value.match(/<!--\s*sync-pr:/gi) || []).length === 1
@@ -136,9 +116,8 @@ function platformError(error: { code: string; message: string; retryable?: boole
136
116
  return { code: error.code, message: error.message, retryable: error.retryable ?? false };
137
117
  }
138
118
 
139
- function buildPullRequestSummary(taskId: string, body: string, headSha: string, humanOverrideAudit = ''): string {
140
- const sections = [body.replace(/\s+$/, ''), renderSafeHumanOverrideAudit(humanOverrideAudit)].filter(Boolean).join('\n\n');
141
- return normalizeCommentContent(`${summaryMarker(taskId)}\n<!-- last-commit: ${headSha} -->\n\n${sections}\n`);
119
+ function buildPullRequestSummary(taskId: string, body: string, headSha: string): string {
120
+ return normalizeCommentContent(`${summaryMarker(taskId)}\n<!-- last-commit: ${headSha} -->\n\n${body.replace(/\s+$/, '')}\n`);
142
121
  }
143
122
 
144
123
  function reconcileSummaryComment(comments: SummaryComment[], taskId: string, desired: string):
@@ -643,7 +622,7 @@ async function syncPullRequestSummary(
643
622
  `<!-- manual-validation-receipt: transaction=${manual.transactionId}; receipt=${manual.receiptDigest}; evidence=${manualEvidenceDigest}; head=${manual.prHeadSha} -->`
644
623
  )
645
624
  : replaced.value;
646
- const desired = buildPullRequestSummary(resolved.taskId, renderedBody, initial.value.head.sha, renderHumanOverrideAudit(taskContent));
625
+ const desired = buildPullRequestSummary(resolved.taskId, renderedBody, initial.value.head.sha);
647
626
  if (!isSafeSummaryEnvelope(desired, resolved.taskId)) return fail('failed', context, {
648
627
  code: 'PR_SUMMARY_RENDER_INVALID',
649
628
  message: 'Summary contains an invalid or duplicated reserved control marker',