immune-brain 4.5.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +157 -39
  2. package/README.zh-CN.md +160 -40
  3. package/package.json +1 -1
  4. package/plugins/immune-brain/.claude-plugin/plugin.json +1 -1
  5. package/plugins/immune-brain/.pi-extension/imm-canary-enroll.ts +42 -45
  6. package/plugins/immune-brain/.pi-extension/imm-canary-work.ts +189 -476
  7. package/plugins/immune-brain/.pi-extension/imm-unattended-batch.ts +77 -102
  8. package/plugins/immune-brain/.pi-extension/pi-canary-assurance-progression.ts +7 -2
  9. package/plugins/immune-brain/.pi-extension/tsconfig.json +2 -2
  10. package/plugins/immune-brain/agents/immune-brain-advisory-reviewer.md +23 -0
  11. package/plugins/immune-brain/agents/immune-brain-qa.md +42 -0
  12. package/plugins/immune-brain/agents/immune-brain-reviewer.md +90 -1
  13. package/plugins/immune-brain/agents/immune-brain-ui-review.md +21 -0
  14. package/plugins/immune-brain/dist/BASELINE.md +4 -4
  15. package/plugins/immune-brain/dist/claude/mcp-server.mjs +9924 -9654
  16. package/plugins/immune-brain/dist/docs/reference/planning-quality-gate.md +3 -3
  17. package/plugins/immune-brain/dist/docs/reference/subagent-dispatch-protocol.md +30 -1
  18. package/plugins/immune-brain/dist/imm-brainstorm.md +1 -1
  19. package/plugins/immune-brain/dist/imm-doc-prune.md +2 -2
  20. package/plugins/immune-brain/dist/{imm-agent-doc-maintain.md → imm-doc-slim.md} +9 -9
  21. package/plugins/immune-brain/dist/imm-planner.md +60 -20
  22. package/plugins/immune-brain/dist/imm-pr-fix.md +1 -1
  23. package/plugins/immune-brain/dist/{imm-review-retro.md → imm-retro.md} +26 -13
  24. package/plugins/immune-brain/dist/{imm-loop.md → imm-run.md} +44 -11
  25. package/plugins/immune-brain/dist/registry.yaml +9 -9
  26. package/plugins/immune-brain/dist/role-prompts/code-review.md +7 -0
  27. package/plugins/immune-brain/dist/role-prompts/executor.md +35 -2
  28. package/plugins/immune-brain/runtime/assurance/coordinator.ts +138 -69
  29. package/plugins/immune-brain/runtime/assurance/host_port.ts +20 -0
  30. package/plugins/immune-brain/runtime/assurance/qa.ts +2 -2
  31. package/plugins/immune-brain/runtime/assurance/review_evidence.ts +4 -0
  32. package/plugins/immune-brain/runtime/assurance/verdict_authority.ts +347 -0
  33. package/plugins/immune-brain/runtime/claude/interaction.ts +1 -2
  34. package/plugins/immune-brain/runtime/claude/kernel_ports.ts +135 -396
  35. package/plugins/immune-brain/runtime/claude/mcp_server.ts +5 -4
  36. package/plugins/immune-brain/runtime/claude/review_host.ts +63 -16
  37. package/plugins/immune-brain/runtime/claude/role_agents.ts +83 -0
  38. package/plugins/immune-brain/runtime/commands/kernel.ts +3 -6
  39. package/plugins/immune-brain/runtime/github_issue_tracker.ts +738 -677
  40. package/plugins/immune-brain/runtime/kernel/application.ts +7 -7
  41. package/plugins/immune-brain/runtime/kernel/assurance_projection.ts +15 -8
  42. package/plugins/immune-brain/runtime/kernel/authority_port.ts +5 -14
  43. package/plugins/immune-brain/runtime/kernel/batch_authority.ts +30 -49
  44. package/plugins/immune-brain/runtime/kernel/canary_application.ts +6 -5
  45. package/plugins/immune-brain/runtime/kernel/canary_eligibility.ts +0 -1
  46. package/plugins/immune-brain/runtime/kernel/capability_registry.ts +7 -9
  47. package/plugins/immune-brain/runtime/kernel/enrollment.ts +170 -17
  48. package/plugins/immune-brain/runtime/kernel/enrollment_authority.ts +5 -11
  49. package/plugins/immune-brain/runtime/kernel/index.ts +3 -1
  50. package/plugins/immune-brain/runtime/kernel/storage.ts +170 -11
  51. package/plugins/immune-brain/runtime/kernel/storage_paths.ts +13 -0
  52. package/plugins/immune-brain/runtime/kernel/types.ts +2 -1
  53. package/plugins/immune-brain/runtime/kernel/validation.ts +3 -1
  54. package/plugins/immune-brain/runtime/loop_contract.ts +22 -22
  55. package/plugins/immune-brain/runtime/plugin_version.ts +1 -1
  56. package/plugins/immune-brain/runtime/prompts/code-review.md +7 -0
  57. package/plugins/immune-brain/runtime/prompts/executor.md +35 -2
  58. package/plugins/immune-brain/runtime/role_prompt_bridge.ts +27 -0
  59. package/plugins/immune-brain/runtime/unattended/batch_git.ts +66 -28
  60. package/plugins/immune-brain/runtime/unattended/batch_kernel_port.ts +106 -0
  61. package/plugins/immune-brain/runtime/unattended/batch_plan.ts +3 -9
  62. package/plugins/immune-brain/runtime/unattended/batch_preflight.ts +132 -45
  63. package/plugins/immune-brain/runtime/unattended/batch_reasons.ts +0 -6
  64. package/plugins/immune-brain/runtime/unattended/batch_reconfirmation.ts +40 -36
  65. package/plugins/immune-brain/runtime/unattended/batch_runner.ts +98 -209
  66. package/plugins/immune-brain/runtime/unattended/batch_state.ts +36 -14
  67. package/plugins/immune-brain/runtime/unattended/types.ts +0 -2
  68. package/plugins/immune-brain/runtime/workspace_scope.ts +78 -8
  69. package/plugins/immune-brain/skills/BASELINE.md +4 -4
  70. package/plugins/immune-brain/skills/imm-doc-prune/SKILL.md +1 -1
  71. package/plugins/immune-brain/skills/{imm-agent-doc-maintain → imm-doc-slim}/SKILL.md +9 -9
  72. package/plugins/immune-brain/skills/imm-planner/SKILL.md +4 -2
  73. package/plugins/immune-brain/skills/imm-pr-fix/SKILL.md +1 -1
  74. package/plugins/immune-brain/skills/{imm-review-retro → imm-retro}/SKILL.md +7 -7
  75. package/plugins/immune-brain/skills/{imm-review-retro → imm-retro}/scripts/review_retro.ts +46 -21
  76. package/plugins/immune-brain/skills/{imm-loop → imm-run}/SKILL.md +7 -7
  77. package/plugins/immune-brain/skills/registry.yaml +9 -9
  78. package/plugins/immune-brain/.pi-extension/runtime-stub.ts +0 -584
  79. package/plugins/immune-brain/runtime/unattended/confirmation_deadline.ts +0 -33
@@ -0,0 +1,347 @@
1
+ // Snapshot and verdict authority shared by all Assurance hosts.
2
+ import { randomUUID } from "node:crypto";
3
+ import { execFileSync } from "node:child_process";
4
+ import { existsSync } from "node:fs";
5
+ import { join, resolve } from "node:path";
6
+ import { readTaskRecordRaw, recoverKernelStoreFollowUps } from "../kernel/storage";
7
+ import { readTaskIntent } from "../kernel/intent";
8
+ import { projectAssurance, type AssuranceProjectionResult } from "../kernel/assurance_projection";
9
+ import { capabilityActionFor, createCanaryApplication } from "../kernel/canary_application";
10
+ import { createMutationAuthorityRegistry, digestOfAction } from "../kernel/authority_port";
11
+ import { findingsDigestV2 } from "../kernel/reducer";
12
+ import type { TaskRecord, TaskApprovalV2 } from "../kernel/types";
13
+ import { captureGitTaskSnapshot } from "../workspace_scope";
14
+ import { parseVerificationDescriptor } from "../verification_descriptor";
15
+ import type { VerificationDescriptor } from "./verification";
16
+ import { captureReviewBundle, captureReviewManifest, type ReviewBundle, type ReviewManifestV5, type ReviewRevision } from "./review_evidence";
17
+ import { reviewReworkFindings, reviewAdvisoryRecords, type SnapshotDescriptor, type AssuranceRole, type AssuranceVerdict, type HostContext } from "./coordinator";
18
+ import type { InvocationToken } from "./invocations";
19
+ import type { ConfirmationReferenceSource } from "./host_port";
20
+
21
+ export interface SnapshotDescriptorInput {
22
+ root: string;
23
+ task_id: string;
24
+ role: AssuranceRole;
25
+ run_id?: string | null;
26
+ record_revision: string;
27
+ workspace_revision: string;
28
+ intent_revision: number;
29
+ intent_content_hash: string;
30
+ diff_hash: string;
31
+ lifecycle: string;
32
+ artifact_state: string;
33
+ risk?: "routine" | "material" | "critical";
34
+ fresh_acceptance_ids: string[];
35
+ missing_acceptance_ids: string[];
36
+ stale_attestation_ids: string[];
37
+ acceptance: Array<{ id: string; assertion: string; verification: string }>;
38
+ dirty_files?: string[];
39
+ review_bundle_digest?: string | null;
40
+ /** Present only when Review authority binds an immutable Git revision (v4). */
41
+ review_revision?: {
42
+ contract: "assurance_kernel/review_revision_identity/v1";
43
+ base_head: string;
44
+ review_commit: string;
45
+ review_tree: string;
46
+ manifest_digest: string;
47
+ };
48
+ }
49
+
50
+ export function buildSnapshot(input: SnapshotDescriptorInput): SnapshotDescriptor {
51
+ return {
52
+ contract: "assurance_kernel/assurance_snapshot/v2",
53
+ task_id: input.task_id,
54
+ run_id: input.run_id ?? null,
55
+ role: input.role,
56
+ record_revision: input.record_revision,
57
+ workspace_revision: input.workspace_revision,
58
+ intent_revision: input.intent_revision,
59
+ intent_content_hash: input.intent_content_hash,
60
+ diff_hash: input.diff_hash,
61
+ lifecycle: input.lifecycle,
62
+ artifact_state: input.artifact_state,
63
+ risk: input.risk ?? "material",
64
+ fresh_acceptance_ids: input.fresh_acceptance_ids,
65
+ missing_acceptance_ids: input.missing_acceptance_ids,
66
+ stale_attestation_ids: input.stale_attestation_ids,
67
+ acceptance: input.acceptance,
68
+ dirty_files: [...(input.dirty_files ?? [])].sort(),
69
+ review_bundle_digest: input.review_bundle_digest ?? null,
70
+ ...(input.review_revision ? { review_revision: input.review_revision } : {}),
71
+ root: resolve(input.root),
72
+ };
73
+ }
74
+
75
+
76
+ export async function ensureReviewRevision(
77
+ root: string,
78
+ taskId: string,
79
+ projection: AssuranceProjectionResult,
80
+ ): Promise<ReviewRevision | null> {
81
+ const current = await readTaskRecordRaw(root, taskId);
82
+ const record = current.record;
83
+ if (!record) throw new Error(`task ${taskId} has no TaskRecord before Review preparation`);
84
+ if (current.revision !== projection.projection.record_revision)
85
+ throw new Error("TaskRecord changed before Review preparation");
86
+ if (record.contract !== "assurance_kernel/task_record/v4") return null;
87
+ if (!record.git_base_head)
88
+ throw new Error("Review revision requires a TaskRecord v4 git_base_head");
89
+ const manifest = captureReviewManifest(root, {
90
+ taskId,
91
+ baseHead: record.git_base_head,
92
+ scopeHint: record.intent_snapshot.scope_hint,
93
+ expectedDiffHash: projection.projection.diff_hash,
94
+ intentRevision: projection.projection.intent_revision,
95
+ intentContentHash: projection.projection.intent_content_hash,
96
+ recordRevision: projection.projection.record_revision,
97
+ workspaceRevision: projection.projection.workspace_revision,
98
+ lifecycle: projection.projection.lifecycle,
99
+ artifactState: projection.projection.artifact_state,
100
+ risk: record.intent_snapshot.risk,
101
+ // The same outcomes the Review snapshot is built from. A preflight stand-in
102
+ // only matched the settled QA attestation because deterministic QA happens to
103
+ // write that exact summary, so the submit-time digest comparison held by
104
+ // coincidence rather than by construction.
105
+ outcomes: qaOutcomes(record),
106
+ });
107
+ return {
108
+ contract: "assurance_kernel/review_revision/v1",
109
+ base_head: manifest.base_head,
110
+ review_tree: manifest.review_tree,
111
+ review_commit: manifest.review_commit,
112
+ review_ref: manifest.review_ref,
113
+ diff_hash: manifest.diff_hash,
114
+ manifest_digest: manifest.manifest_digest,
115
+ };
116
+ }
117
+
118
+ export async function buildAssuranceSnapshot(
119
+ root: string,
120
+ taskId: string,
121
+ role: AssuranceRole,
122
+ projection: AssuranceProjectionResult,
123
+ readRecord: typeof readTaskRecordRaw = readTaskRecordRaw,
124
+ ): Promise<{
125
+ snapshot: SnapshotDescriptor;
126
+ descriptors: Map<string, VerificationDescriptor>;
127
+ reviewBundle: ReviewBundle | null;
128
+ reviewManifest: ReviewManifestV5 | null;
129
+ }> {
130
+ const record = await readRecord(root, taskId);
131
+ if (
132
+ !record.record ||
133
+ record.revision !== projection.projection.record_revision ||
134
+ record.record.intent_snapshot.revision !== projection.projection.intent_revision ||
135
+ record.record.intent_ref.content_hash !== projection.projection.intent_content_hash
136
+ ) {
137
+ throw new Error("TaskRecord changed before assurance snapshot capture");
138
+ }
139
+ const intent = record.record.intent_snapshot;
140
+ const acceptance = intent.acceptance;
141
+ const descriptors = new Map<string, VerificationDescriptor>();
142
+ // Review resumes from settled outcomes without resolving verification tools.
143
+ for (const item of role === "qa" ? acceptance : []) {
144
+ const descriptor = parseVerificationDescriptor(item.verification);
145
+ descriptors.set(item.id, descriptor);
146
+ }
147
+ // `git_base_head` is optional on the read shape because v3 records carry
148
+ // none, so the contract test alone does not prove it is present.
149
+ const baseHead = record.record.contract === "assurance_kernel/task_record/v4"
150
+ ? record.record.git_base_head
151
+ : undefined;
152
+ if (record.record.contract === "assurance_kernel/task_record/v4" && !baseHead)
153
+ throw new Error("TaskRecord v4 is missing its Enrollment git_base_head");
154
+ const reviewRevision = baseHead
155
+ ? {
156
+ contract: "assurance_kernel/review_revision_identity/v1" as const,
157
+ base_head: baseHead,
158
+ review_commit: "",
159
+ review_tree: "",
160
+ manifest_digest: "",
161
+ }
162
+ : null;
163
+ const reviewBundle = role === "review" && !reviewRevision
164
+ ? captureReviewBundle(
165
+ root,
166
+ intent.scope_hint,
167
+ projection.projection.diff_hash,
168
+ qaOutcomes(record.record),
169
+ )
170
+ : null;
171
+ const reviewManifest = role === "review" && reviewRevision
172
+ ? captureReviewManifest(root, {
173
+ taskId,
174
+ baseHead: reviewRevision.base_head,
175
+ scopeHint: intent.scope_hint,
176
+ expectedDiffHash: projection.projection.diff_hash,
177
+ intentRevision: projection.projection.intent_revision,
178
+ intentContentHash: projection.projection.intent_content_hash,
179
+ recordRevision: projection.projection.record_revision,
180
+ workspaceRevision: projection.projection.workspace_revision,
181
+ lifecycle: projection.projection.lifecycle,
182
+ artifactState: projection.projection.artifact_state,
183
+ risk: intent.risk,
184
+ outcomes: qaOutcomes(record.record),
185
+ })
186
+ : null;
187
+ const taskSnapshot = !reviewBundle && !reviewManifest
188
+ ? captureGitTaskSnapshot(root, intent.scope_hint, taskId)
189
+ : null;
190
+ const dirtyFiles = reviewManifest
191
+ ? Object.keys(reviewManifest.changed_paths)
192
+ : reviewBundle
193
+ ? Object.keys(reviewBundle.dirty_files)
194
+ : Object.keys(taskSnapshot!.staged_files);
195
+ return {
196
+ snapshot: buildSnapshot({
197
+ root,
198
+ task_id: taskId,
199
+ role,
200
+ run_id: projection.projection.run_id,
201
+ record_revision: projection.projection.record_revision,
202
+ workspace_revision: projection.projection.workspace_revision,
203
+ intent_revision: projection.projection.intent_revision,
204
+ intent_content_hash: projection.projection.intent_content_hash,
205
+ diff_hash: projection.projection.diff_hash,
206
+ lifecycle: projection.projection.lifecycle,
207
+ artifact_state: projection.projection.artifact_state,
208
+ risk: intent.risk,
209
+ fresh_acceptance_ids: projection.projection.fresh_acceptance_ids,
210
+ missing_acceptance_ids: projection.projection.missing_acceptance_ids,
211
+ stale_attestation_ids: projection.projection.stale_attestation_ids,
212
+ acceptance,
213
+ dirty_files: dirtyFiles,
214
+ review_bundle_digest: reviewManifest?.manifest_digest ?? reviewBundle?.bundle_digest ?? null,
215
+ review_revision: reviewManifest
216
+ ? {
217
+ contract: "assurance_kernel/review_revision_identity/v1",
218
+ base_head: reviewManifest.base_head,
219
+ review_commit: reviewManifest.review_commit,
220
+ review_tree: reviewManifest.review_tree,
221
+ manifest_digest: reviewManifest.manifest_digest,
222
+ }
223
+ : undefined,
224
+ }),
225
+ descriptors,
226
+ reviewBundle,
227
+ reviewManifest,
228
+ };
229
+ }
230
+
231
+ function qaOutcomes(
232
+ record: TaskRecord,
233
+ ): Record<string, { status: "passed" | "failed" | "blocked"; summary: string }> {
234
+ return Object.fromEntries(
235
+ record.attestations
236
+ .filter((item) => item.kind === "qa")
237
+ .flatMap((item) => item.acceptance_results)
238
+ .map((result) => [result.acceptance_id, { status: result.status, summary: result.summary }]),
239
+ );
240
+ }
241
+
242
+ export function stagePlanningArtifactTransition(root: string, record: {
243
+ intent_ref: { path: string };
244
+ intent_snapshot: { scope_hint: string[] };
245
+ }): void {
246
+ const intentActive = record.intent_ref.path.replace("docs/plans/archive/", "docs/plans/");
247
+ const intentArchive = intentActive.replace("docs/plans/", "docs/plans/archive/");
248
+ const specActive = record.intent_snapshot.scope_hint.find((path) =>
249
+ /^docs\/specs\/(?!archive\/)[^/]+\.spec\.md$/.test(path)
250
+ && record.intent_snapshot.scope_hint.includes(path.replace("docs/specs/", "docs/specs/archive/")),
251
+ );
252
+ const candidates = [
253
+ intentActive,
254
+ intentArchive,
255
+ ...(specActive ? [specActive, specActive.replace("docs/specs/", "docs/specs/archive/")] : []),
256
+ ];
257
+ const paths = candidates.filter((path) => existsSync(join(root, path)) || execFileSync(
258
+ "git",
259
+ ["ls-files", "--cached", "--", path],
260
+ { cwd: root, encoding: "utf8" },
261
+ ).trim().length > 0);
262
+ if (paths.length === 0) return;
263
+ execFileSync("git", ["add", "--", ...paths], {
264
+ cwd: root,
265
+ stdio: ["ignore", "pipe", "pipe"],
266
+ });
267
+ }
268
+
269
+ export interface VerdictApplicationInput {
270
+ taskId: string;
271
+ snapshot: SnapshotDescriptor;
272
+ verdict: AssuranceVerdict;
273
+ invocation: InvocationToken;
274
+ actorId: string;
275
+ hooks?: { beforeCommit?: () => Promise<void>; onCommit?: () => void; afterCommit?: () => Promise<void> };
276
+ }
277
+ export interface VerdictAuthority {
278
+ buildAssurance: typeof buildAssuranceSnapshot;
279
+ ensureReviewRevision: typeof ensureReviewRevision;
280
+ applyVerdict(ctx: HostContext, input: VerdictApplicationInput): Promise<void>;
281
+ }
282
+
283
+ export function createVerdictAuthority(options: {
284
+ confirmationReference: ConfirmationReferenceSource;
285
+ commitInvocation: (token: InvocationToken) => void;
286
+ onReworkApplied?: (ctx: HostContext, taskId: string, findingsCount: number) => void;
287
+ }, registry = createMutationAuthorityRegistry()): VerdictAuthority {
288
+ const app = createCanaryApplication(registry);
289
+ return {
290
+ buildAssurance: buildAssuranceSnapshot,
291
+ ensureReviewRevision,
292
+ async applyVerdict(ctx, input) {
293
+ const { snapshot, verdict, actorId, hooks = {} } = input;
294
+ const fresh = await projectAssurance(ctx.cwd, snapshot.task_id);
295
+ const fields = ["record_revision", "workspace_revision", "intent_revision", "intent_content_hash", "diff_hash", "lifecycle", "artifact_state"] as const;
296
+ const mismatches = fields.filter(field => fresh.projection[field] !== snapshot[field]);
297
+ if (fresh.error || fresh.claim?.task_id !== snapshot.task_id || mismatches.length)
298
+ throw new Error(`assurance snapshot changed before authority application: ${[fresh.error, fresh.claim?.task_id !== snapshot.task_id ? "claim" : null, ...mismatches].filter(Boolean).join(", ")}`);
299
+ recoverKernelStoreFollowUps(ctx.cwd, snapshot.task_id);
300
+ const record = readTaskRecordRaw(ctx.cwd, snapshot.task_id).record;
301
+ if (!record) throw new Error("TaskRecord disappeared before authority application");
302
+ const priorIntentToken = readTaskIntent(ctx.cwd, snapshot.task_id, record.intent_ref.path).token;
303
+ const now = new Date().toISOString();
304
+ const findings = verdict.decision === "rework" ? reviewReworkFindings(verdict) : [];
305
+ const advisories = verdict.decision === "pass" ? reviewAdvisoryRecords(verdict) : [];
306
+ const approval: TaskApprovalV2 | undefined = verdict.decision === "pass" ? {
307
+ id: `approval-${snapshot.role}-${randomUUID().slice(0, 8)}`,
308
+ kind: snapshot.role, authority_role: snapshot.role === "qa" ? "qa" : "reviewer",
309
+ task_revision: snapshot.intent_revision, intent_content_hash: snapshot.intent_content_hash,
310
+ diff_hash: snapshot.diff_hash, actor_id: actorId, summary: verdict.approval!.summary,
311
+ ...(snapshot.role === "review" && snapshot.review_revision ? { review_revision: snapshot.review_revision } : {}),
312
+ ...(snapshot.role === "review" && advisories.length ? { advisory_findings: advisories } : {}),
313
+ } : undefined;
314
+ const op = verdict.decision === "rework" ? "request_rework" : "record_approval";
315
+ const payload = approval ? { approval } : { findings };
316
+ const action = capabilityActionFor({ op, task_id: snapshot.task_id, at: now, actor_id: actorId, ...payload });
317
+ const capability = registry.issue({
318
+ authority_kind: snapshot.role, task_id: snapshot.task_id,
319
+ ...(snapshot.run_id ? { run_id: snapshot.run_id } : {}),
320
+ action_digest: digestOfAction(action), expected_record_hash: snapshot.record_revision,
321
+ intent_revision: snapshot.intent_revision, intent_content_hash: snapshot.intent_content_hash,
322
+ diff_hash: snapshot.diff_hash, actor_id: actorId,
323
+ confirmation_ref: options.confirmationReference({ snapshot, actorId, now }),
324
+ findings_digest: verdict.decision === "rework" ? findingsDigestV2(findings) : null,
325
+ });
326
+ await hooks.beforeCommit?.();
327
+ options.commitInvocation(input.invocation);
328
+ // Preserve the Pi ordering: start settlement, run onCommit, await settlement,
329
+ // then afterCommit. A thrown hook never undoes the committed transition.
330
+ const settlement = (async () => app.execute({ root: ctx.cwd, task_id: snapshot.task_id,
331
+ operation: approval ? { op: "record_approval", capability, approval, actor_id: actorId }
332
+ : { op: "request_rework", capability, findings, actor_id: actorId }, prior_intent_token: priorIntentToken, now }))();
333
+ let hookFailed = false;
334
+ let hookError: unknown;
335
+ try { hooks.onCommit?.(); } catch (error) { hookFailed = true; hookError = error; }
336
+ const result = await settlement;
337
+ try { await hooks.afterCommit?.(); } catch (error) { if (!hookFailed) { hookFailed = true; hookError = error; } }
338
+ if (hookFailed) throw hookError;
339
+ // Staging the planning-artifact transition is a post-commit side effect, so it
340
+ // runs after the hook lifecycle: a hook error is reported untouched, and a
341
+ // staging failure can neither skip afterCommit nor mask the first error.
342
+ if (verdict.decision === "rework") stagePlanningArtifactTransition(ctx.cwd, result.record);
343
+ if (verdict.decision === "rework" && result.record.findings.some(f => f.kind === "replan_required" && f.status === "open"))
344
+ options.onReworkApplied?.(ctx, snapshot.task_id, findings.length);
345
+ },
346
+ };
347
+ }
@@ -81,8 +81,7 @@ export interface NativeConfirmationInput {
81
81
  batch_branch: string;
82
82
  children: Array<{ task_id: string; slice_id: string; risk?: string }>;
83
83
  excluded: Array<{ task_id: string; slice_id: string; reason: string }>;
84
- budget: { max_children: number; deadline_at: string; qa_failure_limit: number };
85
- expires_at: string;
84
+ budget: { max_children: number; qa_failure_limit: number };
86
85
  };
87
86
  signal?: AbortSignal;
88
87
  }