@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
@@ -2,7 +2,7 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { parseArtifactName } from './artifact-name.js';
4
4
  import { invalidationBlocks, isArtifactInvalidated, parseInvalidationDocument } from './invalidation.js';
5
- import { receiptForOutput } from './artifact-receipts.js';
5
+ import { parseArtifactReceipts, receiptForOutput } from './artifact-receipts.js';
6
6
  import { parseTypedTaskFrontmatter } from './frontmatter.js';
7
7
  import { parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
8
8
  import { parseReviewSummary, resolveCanonicalVerdict } from './review-artifacts.js';
@@ -10,6 +10,8 @@ import { parseReworkIntentDocument } from './rework-intent.js';
10
10
  import { sha256File } from './artifact-receipts.js';
11
11
  import { hasOpenLifecycleExecution } from './activity-log.js';
12
12
  import { parseQualificationAudit, parseTaskQualification } from './qualification-audit.js';
13
+ import { parseLifecyclePathDecision, pathIncludes } from './lifecycle-path.js';
14
+ import { parseImplementationInputs } from './implementation-inputs.js';
13
15
  const ARTIFACT_AUDIT_FAMILIES = new Set(['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code']);
14
16
  const REASONS = new Set([
15
17
  'user-request', 'new-requirement', 'upstream-fact-doubt', 'review-finding', 'retry', 'validation-rerun'
@@ -23,6 +25,11 @@ function allow(...evidence) {
23
25
  function hasArtifact(facts, action) {
24
26
  return (facts.artifacts[action]?.length ?? 0) > 0;
25
27
  }
28
+ function effectiveReworkTarget(target, pathState) {
29
+ if (target === 'plan' && pathState?.status === 'valid' && !pathIncludes(pathState, 'plan'))
30
+ return 'analysis';
31
+ return target;
32
+ }
26
33
  function canStart(action, facts, trigger) {
27
34
  if (!trigger || trigger.requestedAction !== action)
28
35
  return deny('TRIGGER_ACTION_MISMATCH', `requested=${trigger?.requestedAction ?? 'missing'}`);
@@ -45,11 +52,30 @@ function canStart(action, facts, trigger) {
45
52
  return deny('TASK_NOT_ACTIVE', `state=${facts.taskState}`);
46
53
  if (invalidationBlocks(facts.invalidation))
47
54
  return deny('INVALIDATION_INCOMPLETE');
48
- if (facts.qualificationStale && facts.recommendedAction !== action) {
49
- return deny('QUALIFICATION_STALE', ...(facts.qualificationStaleArtifacts ?? ['qualification audit is stale']));
50
- }
51
55
  if (facts.executionBusy)
52
56
  return deny('EXECUTION_BUSY');
57
+ const pendingIntent = (facts.reworkIntents ?? []).find((intent) => intent.status === 'pending');
58
+ if (pendingIntent) {
59
+ const target = effectiveReworkTarget(pendingIntent.target, facts.pathState);
60
+ const requirementRestart = action === 'analysis' && trigger.reasonCode === 'new-requirement';
61
+ if (!requirementRestart && (target === 'pause' || target !== action)) {
62
+ return deny(target === 'pause' ? 'REWORK_PAUSED' : 'REWORK_INTENT_TARGET_MISMATCH', pendingIntent.intentId);
63
+ }
64
+ }
65
+ if (facts.pathState && facts.pathState.status !== 'valid') {
66
+ return action === 'analysis'
67
+ ? allow(`lifecycle-path-${facts.pathState.status}`)
68
+ : deny('LIFECYCLE_PATH_INVALID', facts.pathState.message);
69
+ }
70
+ if (facts.pathState?.status === 'valid'
71
+ && ['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code'].includes(action)
72
+ && !pathIncludes(facts.pathState, action)) {
73
+ return deny('ARTIFACT_STAGE_NOT_SELECTED', `path=${facts.pathState.decision.path}`, `stage=${action}`);
74
+ }
75
+ if (facts.reworkClassificationRequired?.includes(action)
76
+ && !(facts.reworkIntents ?? []).some((intent) => intent.status === 'pending')) {
77
+ return deny('REWORK_CLASSIFICATION_REQUIRED', `stage=${action}`);
78
+ }
53
79
  const implicit = trigger.explicitRequest === false;
54
80
  if (action === 'analysis') {
55
81
  if (!implicit || hasArtifact(facts, 'analysis') || facts.recommendedAction === 'analysis')
@@ -60,6 +86,9 @@ function canStart(action, facts, trigger) {
60
86
  return hasArtifact(facts, 'analysis') ? allow('analysis-artifact') : deny('ANALYSIS_ARTIFACT_REQUIRED');
61
87
  }
62
88
  if (action === 'plan') {
89
+ if (facts.pathState?.status === 'valid' && facts.pathState.decision.path === 'standard') {
90
+ return hasArtifact(facts, 'analysis') ? allow('analysis-artifact') : deny('ANALYSIS_ARTIFACT_REQUIRED');
91
+ }
63
92
  if (!hasArtifact(facts, 'review-analysis'))
64
93
  return deny('ANALYSIS_REVIEW_REQUIRED');
65
94
  if (!reviewMatchesLatest(facts, 'analysis', 'review-analysis'))
@@ -75,6 +104,8 @@ function canStart(action, facts, trigger) {
75
104
  }
76
105
  if (action === 'code') {
77
106
  if (trigger.implementationInput) {
107
+ if (facts.resolvedHumanDecisions?.code === 'implementation')
108
+ return allow('human-decision-implementation-input');
78
109
  if (!hasArtifact(facts, 'review-code'))
79
110
  return deny('CODE_REVIEW_REQUIRED');
80
111
  if (!reviewMatchesLatest(facts, 'code', 'review-code'))
@@ -83,6 +114,20 @@ function canStart(action, facts, trigger) {
83
114
  return deny('CODE_REVIEW_NOT_APPROVED');
84
115
  return allow('implementation-input');
85
116
  }
117
+ if (facts.pathState?.status === 'valid' && facts.pathState.decision.path === 'streamlined') {
118
+ if (!hasArtifact(facts, 'analysis'))
119
+ return deny('ANALYSIS_ARTIFACT_REQUIRED');
120
+ if (facts.unresolvedLedger.analysis > 0)
121
+ return deny('ANALYSIS_LEDGER_BLOCKED', `unresolved=${facts.unresolvedLedger.analysis}`);
122
+ return allow('analysis-path-approved');
123
+ }
124
+ if (facts.pathState?.status === 'valid' && facts.pathState.decision.path === 'standard') {
125
+ if (!hasArtifact(facts, 'plan'))
126
+ return deny('PLAN_ARTIFACT_REQUIRED');
127
+ if (facts.unresolvedLedger.plan > 0)
128
+ return deny('PLAN_LEDGER_BLOCKED', `unresolved=${facts.unresolvedLedger.plan}`);
129
+ return allow('plan-path-approved');
130
+ }
86
131
  if (!hasArtifact(facts, 'review-plan'))
87
132
  return deny('PLAN_REVIEW_REQUIRED');
88
133
  if (!reviewMatchesLatest(facts, 'plan', 'review-plan'))
@@ -113,14 +158,6 @@ function latestArtifact(names) {
113
158
  return names.map(parseArtifactName).filter((identity) => identity !== null)
114
159
  .sort((left, right) => right.round - left.round || left.name.localeCompare(right.name))[0]?.name ?? null;
115
160
  }
116
- function reviewedInputName(content, expectedFamily) {
117
- const header = content.split(/\r?\n/).findIndex((line) => /\*\*(?:审查输入|Review Input)\*\*[::]/.test(line));
118
- if (header < 0)
119
- return null;
120
- const referenceBlock = content.split(/\r?\n/).slice(header, header + 12).join('\n');
121
- const expected = new RegExp('`' + expectedFamily + '(?:-r[2-9]|-r[1-9]\\d+)?\\.md`');
122
- return expected.exec(referenceBlock)?.[0].slice(1, -1) ?? null;
123
- }
124
161
  function reviewMatchesLatest(facts, source, review) {
125
162
  const latestSource = latestArtifact(facts.artifacts[source] ?? []);
126
163
  return Boolean(latestSource && facts.reviewedInputs?.[review] === latestSource);
@@ -133,30 +170,56 @@ function qualificationCandidateSnapshotMatches(audit, qualification) {
133
170
  && candidate.evidence === row.evidence && candidate.constraintIds.join(',') === row.constraintIds.join(','));
134
171
  });
135
172
  }
136
- const QUALIFICATION_RECOVERY_ORDER = [
137
- { action: 'analysis', pattern: /^analysis(?:-r\d+)?\.md$/ },
138
- { action: 'review-analysis', pattern: /^review-analysis(?:-r\d+)?\.md$/ },
139
- { action: 'plan', pattern: /^plan(?:-r\d+)?\.md$/ },
140
- { action: 'review-plan', pattern: /^review-plan(?:-r\d+)?\.md$/ },
141
- { action: 'code', pattern: /^code(?:-r\d+)?\.md$/ },
142
- { action: 'review-code', pattern: /^review-code(?:-r\d+)?\.md$/ }
143
- ];
144
- function qualificationRecoveryAction(facts) {
145
- const stale = facts.qualificationStaleArtifacts ?? [];
146
- return QUALIFICATION_RECOVERY_ORDER.find(({ pattern }) => stale.some((name) => pattern.test(name)))?.action ?? null;
147
- }
148
173
  function recommendNext(facts) {
149
- if (facts.qualificationStale) {
150
- const action = qualificationRecoveryAction(facts);
151
- return {
152
- action,
153
- reasonCode: 'QUALIFICATION_RECOVERY_REQUIRED',
154
- evidence: facts.qualificationStaleArtifacts ?? ['qualification audit is stale']
155
- };
174
+ if ((facts.reworkClassificationRequired?.length ?? 0) > 0
175
+ && !(facts.reworkIntents ?? []).some((intent) => intent.status === 'pending')) {
176
+ return { action: null, reasonCode: 'REWORK_CLASSIFICATION_REQUIRED', evidence: facts.reworkClassificationRequired ?? [] };
156
177
  }
157
178
  const pendingIntent = (facts.reworkIntents ?? []).find((intent) => intent.status === 'pending');
158
- if (pendingIntent)
159
- return { action: pendingIntent.target, reasonCode: 'REWORK_INTENT_PENDING', evidence: [pendingIntent.intentId, pendingIntent.findingId] };
179
+ if (pendingIntent) {
180
+ const target = effectiveReworkTarget(pendingIntent.target, facts.pathState);
181
+ return { action: target === 'pause' ? null : target, reasonCode: target === 'pause' ? 'REWORK_PAUSED' : 'REWORK_INTENT_PENDING', evidence: [pendingIntent.intentId, pendingIntent.findingId] };
182
+ }
183
+ if (facts.pathState && facts.pathState.status !== 'valid') {
184
+ return { action: 'analysis', reasonCode: facts.pathState.status === 'missing' ? 'LIFECYCLE_PATH_MISSING' : 'LIFECYCLE_PATH_INVALID', evidence: [facts.pathState.message] };
185
+ }
186
+ if (facts.pathState?.status === 'valid') {
187
+ for (const stage of facts.pathState.decision.stages) {
188
+ if (stage === 'analysis' && !hasArtifact(facts, stage))
189
+ return { action: stage, reasonCode: 'ANALYSIS_ARTIFACT_MISSING', evidence: ['analysis artifact is absent'] };
190
+ if (stage === 'review-analysis' && (!reviewMatchesLatest(facts, 'analysis', stage) || facts.reviews[stage] !== 'approved')) {
191
+ if (facts.reviews[stage] === 'changes-requested' && facts.resolvedHumanDecisions?.analysis === 'review') {
192
+ return { action: stage, reasonCode: 'HUMAN_DECISION_REVIEW_REQUIRED', evidence: ['analysis decision was resolved'] };
193
+ }
194
+ return facts.reviews[stage] === 'changes-requested'
195
+ ? { action: 'analysis', reasonCode: 'ANALYSIS_REWORK_REQUIRED', evidence: ['analysis review is not approved'] }
196
+ : { action: stage, reasonCode: 'ANALYSIS_REVIEW_MISSING', evidence: ['analysis review does not bind the latest analysis artifact'] };
197
+ }
198
+ if (stage === 'plan' && !hasArtifact(facts, stage))
199
+ return { action: stage, reasonCode: 'PLAN_ARTIFACT_MISSING', evidence: ['plan artifact is absent'] };
200
+ if (stage === 'review-plan' && (!reviewMatchesLatest(facts, 'plan', stage) || facts.reviews[stage] !== 'approved')) {
201
+ if (facts.reviews[stage] === 'changes-requested' && facts.resolvedHumanDecisions?.plan === 'review') {
202
+ return { action: stage, reasonCode: 'HUMAN_DECISION_REVIEW_REQUIRED', evidence: ['plan decision was resolved'] };
203
+ }
204
+ return facts.reviews[stage] === 'changes-requested'
205
+ ? { action: 'plan', reasonCode: 'PLAN_REWORK_REQUIRED', evidence: ['plan review is not approved'] }
206
+ : { action: stage, reasonCode: 'PLAN_REVIEW_MISSING', evidence: ['plan review does not bind the latest plan artifact'] };
207
+ }
208
+ if (stage === 'code' && !hasArtifact(facts, stage))
209
+ return { action: stage, reasonCode: 'CODE_ARTIFACT_MISSING', evidence: ['code artifact is absent'] };
210
+ if (stage === 'review-code' && (!reviewMatchesLatest(facts, 'code', stage) || facts.reviews[stage] !== 'approved')) {
211
+ if (facts.reviews[stage] === 'changes-requested' && facts.resolvedHumanDecisions?.code) {
212
+ return facts.resolvedHumanDecisions.code === 'implementation'
213
+ ? { action: 'code', reasonCode: 'HUMAN_DECISION_IMPLEMENTATION_REQUIRED', evidence: ['code decision requires implementation'] }
214
+ : { action: stage, reasonCode: 'HUMAN_DECISION_REVIEW_REQUIRED', evidence: ['code decision requires no implementation'] };
215
+ }
216
+ return facts.reviews[stage] === 'changes-requested'
217
+ ? { action: 'code', reasonCode: 'CODE_REWORK_REQUIRED', evidence: ['code review is not approved'] }
218
+ : { action: stage, reasonCode: 'CODE_REVIEW_MISSING', evidence: ['code review does not bind the latest code artifact'] };
219
+ }
220
+ }
221
+ return { action: null, reasonCode: 'LIFECYCLE_REVIEWED', evidence: ['selected lifecycle path is complete'] };
222
+ }
160
223
  if (!hasArtifact(facts, 'analysis'))
161
224
  return { action: 'analysis', reasonCode: 'ANALYSIS_ARTIFACT_MISSING', evidence: ['analysis artifact is absent'] };
162
225
  if (!hasArtifact(facts, 'review-analysis') || !reviewMatchesLatest(facts, 'analysis', 'review-analysis')) {
@@ -213,9 +276,12 @@ function buildLifecycleFacts(taskDir, content, taskState = 'active', executionBu
213
276
  const family = familyFor(name);
214
277
  return !family || !isArtifactInvalidated(invalidation.document, family, name);
215
278
  });
279
+ const allArtifactHashes = {};
280
+ for (const name of files)
281
+ allArtifactHashes[name] = sha256File(path.join(taskDir, name));
216
282
  const artifactHashes = {};
217
283
  for (const name of activeFiles)
218
- artifactHashes[name] = sha256File(path.join(taskDir, name));
284
+ artifactHashes[name] = allArtifactHashes[name];
219
285
  const artifacts = Object.fromEntries(artifactFamilies.map((family) => [
220
286
  family, activeFiles.filter((name) => familyFor(name) === family)
221
287
  ]));
@@ -264,9 +330,12 @@ function buildLifecycleFacts(taskDir, content, taskState = 'active', executionBu
264
330
  continue;
265
331
  const reviewContent = fs.readFileSync(path.join(taskDir, latest), 'utf8');
266
332
  const expectedFamily = family === 'review-analysis' ? 'analysis' : family === 'review-plan' ? 'plan' : 'code';
267
- const input = reviewedInputName(reviewContent, expectedFamily);
268
- const receipt = input ? receiptForOutput(content, latest) : null;
269
- if (input && receipt?.input === input && artifactHashes[input] === receipt.inputSha256)
333
+ const receipt = receiptForOutput(content, latest);
334
+ const input = receipt?.input;
335
+ if (input
336
+ && parseArtifactName(input)?.family === expectedFamily
337
+ && (artifacts[expectedFamily] ?? []).includes(input)
338
+ && artifactHashes[input] === receipt.inputSha256)
270
339
  reviewedInputs[family] = input;
271
340
  const parsed = parseReviewSummary(reviewContent);
272
341
  if (!parsed.ok)
@@ -275,11 +344,58 @@ function buildLifecycleFacts(taskDir, content, taskState = 'active', executionBu
275
344
  if (verdict.ok)
276
345
  reviews[family] = verdict.verdict === 'Approved' ? 'approved' : verdict.verdict === 'Changes Requested' ? 'changes-requested' : 'rejected';
277
346
  }
347
+ const reworkClassificationRequired = [];
348
+ const receipts = parseArtifactReceipts(content).rows;
349
+ for (const [reviewFamily, stage] of [['review-analysis', 'analysis'], ['review-plan', 'plan'], ['review-code', 'code']]) {
350
+ const cycles = receipts.filter((receipt) => receipt.event === `${reviewFamily}.completed` && files.includes(receipt.output)
351
+ && allArtifactHashes[receipt.input] === receipt.inputSha256).map((receipt) => {
352
+ const parsed = parseReviewSummary(fs.readFileSync(path.join(taskDir, receipt.output), 'utf8'));
353
+ const verdict = parsed.ok ? resolveCanonicalVerdict(parsed.summary) : null;
354
+ return {
355
+ input: receipt.input,
356
+ output: receipt.output,
357
+ outputRound: parseArtifactName(receipt.output)?.round ?? 0,
358
+ inputRound: parseArtifactName(receipt.input)?.round ?? 0,
359
+ verdict: verdict?.ok ? verdict.verdict : null
360
+ };
361
+ }).sort((left, right) => left.inputRound - right.inputRound || left.outputRound - right.outputRound || left.output.localeCompare(right.output));
362
+ const latest = cycles.at(-1);
363
+ const trailingInputRounds = new Set();
364
+ for (let index = cycles.length - 1; index >= 0 && cycles[index].verdict === 'Changes Requested'; index -= 1) {
365
+ trailingInputRounds.add(cycles[index].inputRound);
366
+ }
367
+ const handled = latest && (rework.intents ?? []).some((intent) => intent.sourceArtifact === latest.output
368
+ && intent.sourceSha256 === allArtifactHashes[latest.output]);
369
+ const latestActiveReview = latestArtifact(artifacts[reviewFamily] ?? []);
370
+ const latestActiveInput = latestArtifact(artifacts[stage] ?? []);
371
+ const latestCycleIsActive = latest?.output === latestActiveReview && latest.input === latestActiveInput;
372
+ if (latestCycleIsActive && latest.verdict === 'Changes Requested' && trailingInputRounds.size >= 2 && !handled)
373
+ reworkClassificationRequired.push(stage);
374
+ }
278
375
  const unresolvedLedger = { analysis: 0, plan: 0, code: 0 };
279
376
  if (ledger.present) {
280
377
  for (const stage of ['analysis', 'plan', 'code'])
281
378
  unresolvedLedger[stage] = summarizeLedgerStage(ledger.rows, stage).unresolved.length;
282
379
  }
380
+ const latestAnalysis = latestArtifact(artifacts.analysis ?? []);
381
+ const pathState = latestAnalysis
382
+ ? parseLifecyclePathDecision(fs.readFileSync(path.join(taskDir, latestAnalysis), 'utf8'), latestAnalysis, artifactHashes[latestAnalysis] ?? '')
383
+ : { status: 'missing', decision: null, message: 'analysis artifact is missing' };
384
+ const resolvedHumanDecisions = {};
385
+ const implementationInputs = parseImplementationInputs(content).rows;
386
+ for (const stage of ['analysis', 'plan', 'code']) {
387
+ const reviewFamily = `review-${stage}`;
388
+ const latestReview = latestArtifact(artifacts[reviewFamily] ?? []);
389
+ if (!latestReview || reviews[reviewFamily] !== 'changes-requested')
390
+ continue;
391
+ const latestHash = artifactHashes[latestReview];
392
+ const decision = rework.intents.find((intent) => intent.status === 'consumed'
393
+ && intent.classification === 'human-decision' && intent.sourceArtifact === latestReview && intent.sourceSha256 === latestHash);
394
+ if (!decision)
395
+ continue;
396
+ const implementationInput = implementationInputs.find((input) => input.ledgerId === decision.findingId);
397
+ resolvedHumanDecisions[stage] = stage === 'code' && implementationInput?.status === 'pending' ? 'implementation' : 'review';
398
+ }
283
399
  const facts = {
284
400
  taskState, currentStep: String(metadata.current_step ?? ''), artifacts, reviews,
285
401
  staleArtifacts, reviewedInputs, artifactHashes,
@@ -288,7 +404,10 @@ function buildLifecycleFacts(taskDir, content, taskState = 'active', executionBu
288
404
  executionBusy: executionBusy || hasOpenLifecycleExecution(content),
289
405
  recommendedAction: null,
290
406
  qualificationStale: qualificationStaleArtifacts.length > 0,
291
- qualificationStaleArtifacts
407
+ qualificationStaleArtifacts,
408
+ pathState,
409
+ reworkClassificationRequired,
410
+ resolvedHumanDecisions
292
411
  };
293
412
  facts.recommendedAction = recommendNext(facts).action;
294
413
  return { ok: true, facts };
@@ -297,5 +416,5 @@ function buildLifecycleFacts(taskDir, content, taskState = 'active', executionBu
297
416
  return { ok: false, code: 'TASK_CAPABILITY_FACTS_INVALID', message: error instanceof Error ? error.message : String(error) };
298
417
  }
299
418
  }
300
- export { buildLifecycleFacts, canStart, recommendNext };
419
+ export { buildLifecycleFacts, canStart, effectiveReworkTarget, recommendNext };
301
420
  //# sourceMappingURL=capabilities.js.map
@@ -325,7 +325,11 @@ function renderStatus(model) {
325
325
  ['processed', `${model.invalidation?.processed ?? 0}/${model.invalidation?.total ?? 0}`],
326
326
  ['pending_intents', String(model.invalidation?.pendingIntents ?? 0)]
327
327
  ]));
328
- lines.push('', 'Recommendation', ...renderPairs([['action', model.recommendation ?? DASH]]));
328
+ lines.push('', 'Recommendation', ...renderPairs([
329
+ ['path', model.lifecyclePath ?? DASH],
330
+ ['action', model.recommendation ?? DASH],
331
+ ['reason', model.recommendationReason ?? DASH]
332
+ ]));
329
333
  lines.push('', 'Orchestration', ...renderPairs([
330
334
  ['status', model.orchestration.status],
331
335
  ['run_id', model.orchestration.runId],
@@ -411,7 +415,8 @@ function buildFromResolved(input) {
411
415
  pendingIntents
412
416
  };
413
417
  const facts = buildLifecycleFacts(input.taskDir, content, fm.status ?? 'active');
414
- const recommendation = facts.ok ? (recommendNext(facts.facts).action ?? DASH) : DASH;
418
+ const next = facts.ok ? recommendNext(facts.facts) : null;
419
+ const recommendation = next?.action ?? DASH;
415
420
  return {
416
421
  taskId: input.taskId,
417
422
  shortId: input.shortId ?? loadShortIdByTaskId(input.repoRoot).get(input.taskId) ?? DASH,
@@ -424,6 +429,8 @@ function buildFromResolved(input) {
424
429
  orchestration: collectOrchestration(input.taskDir),
425
430
  invalidation: invalidationInfo,
426
431
  recommendation,
432
+ recommendationReason: next?.reasonCode ?? 'TASK_CAPABILITY_FACTS_INVALID',
433
+ lifecyclePath: facts.ok && facts.facts.pathState?.status === 'valid' ? facts.facts.pathState.decision.path : facts.ok ? facts.facts.pathState?.status ?? DASH : DASH,
427
434
  git: collectGit(fm.branch ?? '', run)
428
435
  };
429
436
  }
@@ -1,20 +1,16 @@
1
1
  import path from 'node:path';
2
2
  import crypto from 'node:crypto';
3
- import fs from 'node:fs';
4
3
  import { normalizeAgentToken } from '../agent-clients/tokens.js';
5
4
  import { isAgentClientId } from '../agent-clients/types.js';
6
5
  import { getAgentClientAdapter } from '../agent-clients/registry.js';
7
- import { consumeHumanOverride, failureId, overrideDryRunConflict } from './human-override.js';
8
6
  import { activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, advanceOrchestration, awaitOrchestrationDelegationActivation, beginOrResumeOrchestration, dispatchOrchestrationDelegation, OrchestrationStateError, pauseOrchestration, prepareOrchestrationDelegation, recoverPreparedOrchestrationDelegation, routeOrchestration, sealMatchingOrchestrationDelegation, sealOrchestrationDelegation, statusOrchestration } from './orchestration.js';
9
- import { applyTaskFinalization } from './finalization.js';
7
+ import { applyTaskFinalization, commitPreparedTaskFinalization } from './finalization.js';
10
8
  import { applyTaskLifecycle, taskLifecycleFailure } from './lifecycle.js';
11
- import { locateActivityLog } from './activity-log.js';
12
9
  import { resolveTaskRef, TASK_ID_RE } from './resolve-ref.js';
13
10
  import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
14
11
  import { verifyTaskEvent } from './verification.js';
15
12
  import { createCodexCapabilityStore } from '../agent-clients/adapters/codex-lifecycle/capability-store.js';
16
- import { findArtifactRecoveryIntentsByOperation, writeArtifactRecoveryIntent } from './artifact-repair-intent.js';
17
- import { computeLifecycleBuildIdentity, verifyLifecycleBuildIdentity } from '../agent-clients/adapters/codex-lifecycle/build-identity.js';
13
+ import { computeLifecycleBuildIdentity } from '../agent-clients/adapters/codex-lifecycle/build-identity.js';
18
14
  const lifecycleAuthorityPhases = [
19
15
  'orchestration.prepare', 'artifact.finalize-local', 'task-event.completed'
20
16
  ];
@@ -184,7 +180,6 @@ export function queryLifecycleRecoveryOperation(operationId, options = {}) {
184
180
  const store = options.capabilityStore ?? createCodexCapabilityStore();
185
181
  const registry = authorityOperationMap.get(store);
186
182
  const known = [...(registry?.values() ?? [])].filter((value) => value.operationId === operationId);
187
- const intents = options.repoRoot ? findArtifactRecoveryIntentsByOperation(options.repoRoot, operationId) : [];
188
183
  const capabilities = store.findByRecoveryOperation(operationId);
189
184
  const phaseMap = new Map();
190
185
  for (const capability of capabilities) {
@@ -208,23 +203,10 @@ export function queryLifecycleRecoveryOperation(operationId, options = {}) {
208
203
  ? 'consumed' : 'issued'
209
204
  });
210
205
  }
211
- for (const intent of intents) {
212
- if (!intent.phase)
213
- continue;
214
- const key = `${intent.phase}\0${intent.requestId}`;
215
- if (phaseMap.has(key))
216
- continue;
217
- phaseMap.set(key, {
218
- phase: intent.phase,
219
- requestId: intent.requestId,
220
- attestationId: null,
221
- state: intent.state === 'consumed' ? 'consumed' : 'observed'
222
- });
223
- }
224
206
  const capabilityState = capabilities.some((record) => record.recoveryState === 'consumed')
225
207
  ? 'consumed'
226
208
  : capabilities.some((record) => record.recoveryState === 'reserved') ? 'reserved' : 'unknown';
227
- const committed = capabilityState === 'consumed' || intents.some((intent) => intent.state === 'consumed');
209
+ const committed = capabilityState === 'consumed';
228
210
  return {
229
211
  version: 1,
230
212
  operationId,
@@ -233,120 +215,6 @@ export function queryLifecycleRecoveryOperation(operationId, options = {}) {
233
215
  phases: Object.freeze([...phaseMap.values()])
234
216
  };
235
217
  }
236
- function lifecycleRecoveryCompensationError(code, message) {
237
- const error = new Error(`${code}: ${message}`);
238
- error.name = code;
239
- return error;
240
- }
241
- function lifecycleRecoveryEventCommitted(selector, repoRoot) {
242
- const resolved = resolveTaskRef(selector.taskId, { repoRoot });
243
- if (!resolved.ok || resolved.taskId !== selector.taskId)
244
- return false;
245
- let content;
246
- try {
247
- content = fs.readFileSync(resolved.taskMdPath, 'utf8');
248
- }
249
- catch {
250
- return false;
251
- }
252
- const section = locateActivityLog(content);
253
- if (!section)
254
- return false;
255
- const labels = { analysis: 'Analyze Task', plan: 'Plan Task', code: 'Code Task' };
256
- const prefix = `${labels[selector.family]} (Round ${selector.round}`;
257
- return section.entries.some((entry) => {
258
- if (entry.step.endsWith(' [started]') || !entry.step.startsWith(prefix))
259
- return false;
260
- const delimiter = entry.step[prefix.length];
261
- return (delimiter === ',' || delimiter === ')') && entry.note.endsWith(`→ ${selector.artifact}`);
262
- });
263
- }
264
- function lifecycleRecoveryIntentMatches(intent, selector) {
265
- return intent.taskId === selector.taskId
266
- && intent.family === selector.family
267
- && intent.artifact === selector.artifact
268
- && (intent.state === 'commit-started' || intent.state === 'consumption-started' || intent.state === 'consumed');
269
- }
270
- /**
271
- * Completes only the durable tail of a committed task event after a process
272
- * restart. The task event itself is the commit boundary and is never replayed.
273
- */
274
- export function recoverLifecycleRecoveryOperation(input, options) {
275
- const selector = validateLifecycleAuthorityRequest(input);
276
- if (selector.phase !== 'task-event.completed') {
277
- throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PHASE_UNSUPPORTED', 'only task-event.completed supports durable compensation');
278
- }
279
- const buildIdentity = options.buildIdentity ?? computeLifecycleBuildIdentity(options.repoRoot);
280
- if (selector.expectedBuildIdentityDigest !== lifecycleAuthorityBuildDigest(buildIdentity)) {
281
- throw lifecycleRecoveryCompensationError('LIFECYCLE_AUTHORITY_BUILD_MISMATCH', 'current lifecycle build does not match the recovery request');
282
- }
283
- if (selector.expectedControlGeneration !== options.controllerBinding.controlGeneration
284
- || selector.expectedControllerInstanceDigest !== options.controllerBinding.instanceDigest) {
285
- throw lifecycleRecoveryCompensationError('LIFECYCLE_AUTHORITY_CONTROLLER_MISMATCH', 'current controller binding does not match the recovery request');
286
- }
287
- const store = options.capabilityStore ?? createCodexCapabilityStore();
288
- const expected = {
289
- taskId: selector.taskId,
290
- hookDefinitionHash: selector.expectedHookDefinitionHash,
291
- buildIdentity,
292
- controller: options.controllerBinding
293
- };
294
- const capabilities = store.findByRecoveryOperation(selector.operationId);
295
- if (capabilities.length !== 1 || capabilities[0].capabilityRefDigest !== lifecycleAuthorityDigest(selector.authorityRef)) {
296
- throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PROVENANCE_INVALID', 'durable capability provenance does not identify this operation');
297
- }
298
- const capability = capabilities[0];
299
- const build = verifyLifecycleBuildIdentity(capability.buildIdentity, buildIdentity);
300
- const expiredReserved = capability.status === 'expired' && capability.recoveryState === 'reserved';
301
- if (!build.ok
302
- || capability.taskId !== selector.taskId
303
- || capability.hookDefinitionHash !== selector.expectedHookDefinitionHash
304
- || capability.controller?.controlGeneration !== options.controllerBinding.controlGeneration
305
- || capability.controller?.instanceDigest !== options.controllerBinding.instanceDigest
306
- || capability.recoveryState === 'unreserved'
307
- || capability.recoveryOperationId !== selector.operationId
308
- || (!['attested', 'consumed'].includes(capability.status) && !expiredReserved)) {
309
- throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PROVENANCE_INVALID', 'durable capability provenance does not match the recovery request');
310
- }
311
- const phase = capability.recoveryPhases.find((entry) => entry.phase === selector.phase);
312
- if (!phase) {
313
- throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PHASE_MISSING', 'durable task completion phase is missing');
314
- }
315
- const intents = findArtifactRecoveryIntentsByOperation(options.repoRoot, selector.operationId)
316
- .filter((intent) => intent.taskId === selector.taskId
317
- && intent.family === selector.family
318
- && intent.artifact === selector.artifact);
319
- if (intents.length !== 1) {
320
- throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_INTENT_INVALID', 'durable artifact recovery intent does not identify this task event');
321
- }
322
- const intent = intents[0];
323
- if (!lifecycleRecoveryIntentMatches(intent, selector)
324
- || intent.phase !== selector.phase
325
- || intent.requestId !== selector.lifecycleRequestId
326
- || intent.authorityDigest === null) {
327
- throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_INTENT_INVALID', 'durable artifact recovery intent does not match the recovery request');
328
- }
329
- if (!lifecycleRecoveryEventCommitted(selector, options.repoRoot)) {
330
- throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_COMMIT_UNCONFIRMED', 'task completion is not durably present; compensation is not allowed');
331
- }
332
- if (capability.recoveryState === 'reserved') {
333
- if (phase.state === 'issued') {
334
- store.consumeRecoveryPhase(selector.authorityRef, selector.operationId, selector.phase, phase.requestId, expected, { allowExpiredReserved: true });
335
- }
336
- store.consumeReference(selector.authorityRef, selector.operationId, expected, { allowExpiredReserved: true });
337
- }
338
- else if (phase.state !== 'consumed') {
339
- throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_STATE_INVALID', 'consumed capability has an unconsumed completion phase');
340
- }
341
- if (intent.state !== 'consumed') {
342
- writeArtifactRecoveryIntent(options.repoRoot, {
343
- ...intent,
344
- state: 'consumed',
345
- updatedAt: (options.now ?? Date.now)()
346
- }, { expected: intent });
347
- }
348
- return queryLifecycleRecoveryOperation(selector.operationId, { repoRoot: options.repoRoot, capabilityStore: store });
349
- }
350
218
  function authorityRejected(request, code, message) {
351
219
  return { version: 1, requestId: request.requestId, operationId: request.operationId, phase: request.phase, status: 'rejected', attestation: null, error: { code, message } };
352
220
  }
@@ -406,6 +274,7 @@ export function createSandboxExecutorExecutionContext(params) {
406
274
  generation: params.generation,
407
275
  manifestPath: absolute('manifestPath', params.manifestPath),
408
276
  requestId: params.requestId,
277
+ handoffDirectory: absolute('handoffDirectory', params.handoffDirectory ?? path.join(params.worktreeRoot, '.agent-infra-handoff')),
409
278
  ...(params.controllerBinding === undefined ? {} : { controllerBinding: binding(params.controllerBinding) }),
410
279
  ...(params.lifecycleRecoveryAttestation === undefined ? {} : {
411
280
  lifecycleRecoveryAttestation: params.lifecycleRecoveryAttestation === null
@@ -433,6 +302,7 @@ export function assertTaskControlExecutionContext(context) {
433
302
  absolute('worktreeRoot', context.worktreeRoot);
434
303
  absolute('runtimeDir', context.runtimeDir);
435
304
  absolute('manifestPath', context.manifestPath);
305
+ absolute('handoffDirectory', context.handoffDirectory);
436
306
  if (path.resolve(context.runtimeDir) !== path.join(path.dirname(path.resolve(context.manifestPath)), 'runtime')) {
437
307
  invalidContext('runtimeDir is not bound to the manifest control root');
438
308
  }
@@ -463,10 +333,6 @@ function lifecycleTaskId(request, repoRoot) {
463
333
  }
464
334
  }
465
335
  async function applyLifecycleWithAuthority(context, request) {
466
- const conflict = overrideDryRunConflict(request);
467
- if (conflict) {
468
- return taskLifecycleFailure(request, { code: 'LIFECYCLE_PAYLOAD_INVALID', message: conflict.message });
469
- }
470
336
  const execute = async () => {
471
337
  if (request.intent === 'recover-started') {
472
338
  const adapter = isAgentClientId(request.agent)
@@ -481,29 +347,7 @@ async function applyLifecycleWithAuthority(context, request) {
481
347
  }
482
348
  return adapter.recoverStarted(request, { repoRoot: context.repoRoot });
483
349
  }
484
- const lifecycleResult = applyTaskLifecycle(request, { repoRoot: context.repoRoot });
485
- if (lifecycleResult.status !== 'failed' || !request.overrideTicket)
486
- return lifecycleResult;
487
- if (!request.overrideTarget || !request.overrideScope) {
488
- return {
489
- ...lifecycleResult,
490
- error: { code: 'OVERRIDE_PAYLOAD_INVALID', message: 'override ticket requires --override-target and --override-scope' }
491
- };
492
- }
493
- const override = await consumeHumanOverride({
494
- taskRef: request.taskRef,
495
- ticketId: request.overrideTicket,
496
- failureId: failureId('lifecycle.apply', lifecycleResult.error?.code ?? 'LIFECYCLE_FAILED'),
497
- target: request.overrideTarget,
498
- scope: request.overrideScope,
499
- intent: request.intent,
500
- ...('alertNumber' in request && request.alertNumber ? { alertNumber: request.alertNumber } : {}),
501
- ...('issueNumber' in request && request.issueNumber ? { issueNumber: request.issueNumber } : {}),
502
- ...('stagingDir' in request && request.stagingDir ? { stagingDir: request.stagingDir } : {})
503
- }, { repoRoot: context.repoRoot });
504
- if (override.status === 'failed')
505
- return { ...lifecycleResult, humanOverride: override };
506
- return { ...lifecycleResult, ...override, humanOverride: override, error: null };
350
+ return applyTaskLifecycle(request, { repoRoot: context.repoRoot });
507
351
  };
508
352
  const taskId = lifecycleTaskId(request, context.repoRoot);
509
353
  if (!taskId)
@@ -590,10 +434,13 @@ export function dispatchTaskControlOperation(context, operation) {
590
434
  }
591
435
  if (operation.family === 'task-finalization') {
592
436
  operationTaskId(context, operation.request.taskRef);
593
- return applyTaskFinalization(operation.request, {
437
+ const finalize = context.source === 'sandbox-executor' ? commitPreparedTaskFinalization : applyTaskFinalization;
438
+ return finalize(operation.request, {
594
439
  repoRoot: context.repoRoot,
595
440
  ...(context.source === 'sandbox-executor' ? {
596
- controlBinding: { generation: context.generation, requestId: context.requestId }
441
+ controlBinding: { generation: context.generation, requestId: context.requestId },
442
+ handoffDirectory: context.handoffDirectory,
443
+ manifestPath: context.manifestPath
597
444
  } : {}),
598
445
  preflight: (request, options) => verifyTaskEvent({ ...request, event: 'complete-task.hard-preflight' }, options)
599
446
  });
@@ -628,7 +475,7 @@ function operationInvalid(message) {
628
475
  }
629
476
  const LIFECYCLE_FLAGS = new Set([
630
477
  '--agent', '--reason', '--unblock-condition', '--note', '--alert-number', '--staging-dir', '--issue-number',
631
- '--auto', '--override-ticket', '--override-target', '--override-scope', '--dry-run'
478
+ '--auto', '--dry-run'
632
479
  ]);
633
480
  const FINALIZATION_FLAGS = new Set(['--agent']);
634
481
  const ORCHESTRATION_FLAGS = new Set([
@@ -699,9 +546,6 @@ export function parseTaskControlOperation(family, args) {
699
546
  ...(value(values, '--unblock-condition') ? { unblockCondition: value(values, '--unblock-condition') } : {}),
700
547
  ...(value(values, '--note') ? { note: value(values, '--note') } : {}),
701
548
  ...(value(values, '--staging-dir') ? { stagingDir: value(values, '--staging-dir') } : {}),
702
- ...(value(values, '--override-ticket') ? { overrideTicket: value(values, '--override-ticket') } : {}),
703
- ...(value(values, '--override-target') ? { overrideTarget: value(values, '--override-target') } : {}),
704
- ...(value(values, '--override-scope') ? { overrideScope: value(values, '--override-scope') } : {}),
705
549
  ...(value(values, '--alert-number') ? { alertNumber: Number(value(values, '--alert-number')) } : {}),
706
550
  ...(value(values, '--issue-number') ? { issueNumber: Number(value(values, '--issue-number')) } : {}),
707
551
  ...(values['--auto'] === true ? { auto: true } : {}),
@@ -6,7 +6,7 @@ import { LEDGER_COLUMNS, LEDGER_HEADINGS, LEDGER_SECTION_MISSING_CODE, LEDGER_SE
6
6
  import { resolveTaskContext } from './resolve-ref.js';
7
7
  import { extractSection, extractSubSection, findSectionHeading } from './sections.js';
8
8
  import { captureTaskWriteMetadata, writeTask } from './write.js';
9
- import { allowsManualOverride } from './guard-override.js';
9
+ import { consumeHumanDecisionReworkIntents, parseReworkIntentDocument, reworkIntentMutation } from './rework-intent.js';
10
10
  const DECISION_ALIASES = ['人工裁决', 'Human Rulings', 'Human Decisions', 'Human Decision'];
11
11
  const ACTIVITY_ALIASES = ['活动日志', 'Activity Log'];
12
12
  function failed(code, message, taskId = null, ledgerId = null) {
@@ -57,8 +57,7 @@ function applyHumanDecision(request, options = {}) {
57
57
  const resolved = resolveTaskContext(request.taskRef, { repoRoot: options.repoRoot });
58
58
  if (!resolved.ok)
59
59
  return failed(resolved.code, resolved.message, resolved.taskId);
60
- const stateOverride = allowsManualOverride(options.manualOverride, 'decision-intent', 'TASK_STATE_MISMATCH');
61
- if (resolved.state !== 'active' && !stateOverride)
60
+ if (resolved.state !== 'active')
62
61
  return failed('TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
63
62
  let content;
64
63
  let rows;
@@ -160,7 +159,18 @@ function applyHumanDecision(request, options = {}) {
160
159
  kind: 'section', aliases: ACTIVITY_ALIASES, heading: activity.heading,
161
160
  body: appendActivityEntry(activity, { time: metadata.timestamp, step: 'Human Decision', agent: 'human', note: `${row.id} decided → ${recordId}` })
162
161
  });
163
- const writeResult = writeTask({ taskRef: resolved.taskId, expectedState: stateOverride ? resolved.state : 'active', mutations, dryRun: request.dryRun }, {
162
+ try {
163
+ const parsedRework = parseReworkIntentDocument(content);
164
+ if (!parsedRework.ok)
165
+ return failed('DECISION_DOCUMENT_INVALID', parsedRework.message, resolved.taskId, row.id);
166
+ const consumed = consumeHumanDecisionReworkIntents(parsedRework.intents, row.id, metadata.timestamp);
167
+ if (consumed.changed)
168
+ mutations.push(reworkIntentMutation(content, consumed.intents));
169
+ }
170
+ catch (error) {
171
+ return failed('DECISION_DOCUMENT_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId, row.id);
172
+ }
173
+ const writeResult = writeTask({ taskRef: resolved.taskId, expectedState: 'active', mutations, dryRun: request.dryRun }, {
164
174
  ...options,
165
175
  taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state },
166
176
  metadataProvider: () => metadata