immune-brain 5.3.0 → 5.5.1
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.
- package/README.md +4 -4
- package/README.zh-CN.md +4 -4
- package/package.json +2 -1
- package/plugins/immune-brain/.claude-plugin/plugin.json +1 -1
- package/plugins/immune-brain/.pi-extension/imm-canary-enroll.ts +54 -70
- package/plugins/immune-brain/.pi-extension/imm-canary-work.ts +163 -28
- package/plugins/immune-brain/.pi-extension/imm-unattended-batch.ts +71 -10
- package/plugins/immune-brain/.pi-extension/pi-canary-tool-failure.ts +1 -1
- package/plugins/immune-brain/.pi-extension/pi-run-continuation.ts +161 -0
- package/plugins/immune-brain/agents/immune-brain-reviewer.md +26 -2
- package/plugins/immune-brain/dist/BASELINE.md +13 -1
- package/plugins/immune-brain/dist/claude/mcp-server.mjs +16130 -12734
- package/plugins/immune-brain/dist/docs/reference/code-quality-guard.md +3 -1
- package/plugins/immune-brain/dist/docs/reference/immune-brain-config.md +41 -1
- package/plugins/immune-brain/dist/docs/reference/planning-quality-gate.md +1 -1
- package/plugins/immune-brain/dist/imm-planner.md +57 -16
- package/plugins/immune-brain/dist/imm-run-batch.md +398 -0
- package/plugins/immune-brain/dist/imm-run.md +89 -344
- package/plugins/immune-brain/dist/role-prompts/code-review.md +26 -2
- package/plugins/immune-brain/dist/role-prompts/executor.md +35 -10
- package/plugins/immune-brain/hooks/hooks.json +21 -1
- package/plugins/immune-brain/runtime/assurance/coordinator.ts +203 -13
- package/plugins/immune-brain/runtime/assurance/delivery_workspace.ts +2 -1
- package/plugins/immune-brain/runtime/assurance/enrollment_git_base.ts +99 -14
- package/plugins/immune-brain/runtime/assurance/phase_entry.ts +1 -7
- package/plugins/immune-brain/runtime/assurance/qa.ts +126 -43
- package/plugins/immune-brain/runtime/assurance/qa_findings.ts +8 -0
- package/plugins/immune-brain/runtime/assurance/review_evidence.ts +48 -0
- package/plugins/immune-brain/runtime/assurance/run_continuation.ts +284 -0
- package/plugins/immune-brain/runtime/assurance/verdict_authority.ts +46 -4
- package/plugins/immune-brain/runtime/assurance/verification.ts +83 -35
- package/plugins/immune-brain/runtime/assurance/workflow_autocommit.ts +222 -0
- package/plugins/immune-brain/runtime/claude/interaction.ts +11 -2
- package/plugins/immune-brain/runtime/claude/kernel_ports.ts +199 -48
- package/plugins/immune-brain/runtime/claude/lane_guard.ts +8 -15
- package/plugins/immune-brain/runtime/claude/mcp_server.ts +105 -9
- package/plugins/immune-brain/runtime/claude/run_continuation.ts +274 -0
- package/plugins/immune-brain/runtime/commands/kernel.ts +41 -103
- package/plugins/immune-brain/runtime/github_issue_tracker.ts +60 -2
- package/plugins/immune-brain/runtime/kernel/application.ts +4 -1
- package/plugins/immune-brain/runtime/kernel/assurance_projection.ts +103 -20
- package/plugins/immune-brain/runtime/kernel/canary_application.ts +57 -7
- package/plugins/immune-brain/runtime/kernel/completion.ts +28 -4
- package/plugins/immune-brain/runtime/kernel/enrollment.ts +48 -8
- package/plugins/immune-brain/runtime/kernel/intent.ts +169 -104
- package/plugins/immune-brain/runtime/kernel/reducer.ts +136 -6
- package/plugins/immune-brain/runtime/kernel/risk_floor.ts +286 -0
- package/plugins/immune-brain/runtime/kernel/storage_layout_migration.ts +83 -1
- package/plugins/immune-brain/runtime/kernel/storage_paths.ts +1 -1
- package/plugins/immune-brain/runtime/kernel/types.ts +49 -3
- package/plugins/immune-brain/runtime/kernel/validation.ts +147 -7
- package/plugins/immune-brain/runtime/loop_contract.ts +5 -0
- package/plugins/immune-brain/runtime/plan_core.ts +15 -8
- package/plugins/immune-brain/runtime/plugin_source_skew.ts +58 -0
- package/plugins/immune-brain/runtime/plugin_version.ts +1 -1
- package/plugins/immune-brain/runtime/prompts/code-review.md +26 -2
- package/plugins/immune-brain/runtime/prompts/executor.md +35 -10
- package/plugins/immune-brain/runtime/unattended/batch_delegation.ts +5 -3
- package/plugins/immune-brain/runtime/unattended/batch_disposition.ts +40 -10
- package/plugins/immune-brain/runtime/unattended/batch_final_verification.ts +57 -3
- package/plugins/immune-brain/runtime/unattended/batch_git.ts +424 -66
- package/plugins/immune-brain/runtime/unattended/batch_integration.ts +2 -1
- package/plugins/immune-brain/runtime/unattended/batch_kernel_port.ts +13 -0
- package/plugins/immune-brain/runtime/unattended/batch_lanes.ts +43 -24
- package/plugins/immune-brain/runtime/unattended/batch_plan.ts +6 -2
- package/plugins/immune-brain/runtime/unattended/batch_preflight.ts +102 -41
- package/plugins/immune-brain/runtime/unattended/batch_reasons.ts +9 -3
- package/plugins/immune-brain/runtime/unattended/batch_reconfirmation.ts +9 -9
- package/plugins/immune-brain/runtime/unattended/batch_runner.ts +90 -32
- package/plugins/immune-brain/runtime/unattended/batch_state.ts +102 -4
- package/plugins/immune-brain/runtime/unattended/parent_delivery_consolidation.ts +248 -0
- package/plugins/immune-brain/runtime/workspace_scope.ts +126 -18
- package/plugins/immune-brain/skills/BASELINE.md +13 -1
- package/plugins/immune-brain/skills/imm-planner/SKILL.md +5 -0
- package/plugins/immune-brain/skills/imm-run/SKILL.md +12 -8
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
// open. No Batch Authorization capability is minted, because retiring a record
|
|
21
21
|
// consumes no child and enrolls nothing — the capability exists to authorize
|
|
22
22
|
// child consumption, and minting one here would be ceremony without a property.
|
|
23
|
-
import { readSecureProjectBytes } from "../kernel/storage";
|
|
23
|
+
import { readSecureProjectBytes, readSettledRunEvidence } from "../kernel/storage";
|
|
24
24
|
import { batchStatePath } from "../kernel/storage_paths";
|
|
25
25
|
import { batchReason, type BatchReason } from "./batch_reasons";
|
|
26
|
-
import { findExistingActiveBatch, findSettledBatchRecord, type BatchGateDecision } from "./batch_preflight";
|
|
26
|
+
import { findExistingActiveBatch, findSettledBatchRecord, readActiveClaimTaskId, type BatchGateDecision } from "./batch_preflight";
|
|
27
27
|
import {
|
|
28
28
|
isTerminalBatchState,
|
|
29
29
|
replaceAnyBatchRunState,
|
|
@@ -34,7 +34,12 @@ import {
|
|
|
34
34
|
type BatchRunState,
|
|
35
35
|
} from "./batch_state";
|
|
36
36
|
|
|
37
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* Child states the batch runner has not finished absorbing. Such a child is
|
|
39
|
+
* mid-flight unless the Kernel store proves its run already ended outside the
|
|
40
|
+
* batch (see `kernelLifecycleOf`): the record alone cannot tell a live run from
|
|
41
|
+
* one a manual imm-run settled while the drifted batch could no longer resume.
|
|
42
|
+
*/
|
|
38
43
|
const SERIAL_IN_FLIGHT: ReadonlySet<string> = new Set(["enrolled", "settled"]);
|
|
39
44
|
const LANE_IN_FLIGHT: ReadonlySet<string> = new Set(["lane_admitted", "enrolled", "settled", "lane_committed"]);
|
|
40
45
|
|
|
@@ -45,6 +50,8 @@ export interface StaleBatchChildEvidence {
|
|
|
45
50
|
state: string;
|
|
46
51
|
commit: string | null;
|
|
47
52
|
lane_branch: string | null;
|
|
53
|
+
/** The Kernel store's terminal lifecycle for this task; null while live or unknown. */
|
|
54
|
+
kernel_lifecycle: "done" | "stopped" | null;
|
|
48
55
|
}
|
|
49
56
|
|
|
50
57
|
/** The exact record identity the literal user is asked to retire. */
|
|
@@ -74,10 +81,27 @@ const RETIRE_REASON =
|
|
|
74
81
|
"Recorded child commits, child states and the batch branch are preserved as evidence. " +
|
|
75
82
|
"Children this batch never executed carry no commit, no batch trailer and no approval; nothing infers their delivery from this record.";
|
|
76
83
|
|
|
84
|
+
const RETIRE_REASON_ENDED_OUTSIDE =
|
|
85
|
+
" A child this record still holds mid-flight had already ended in the Kernel store when the record was retired; its own TaskRecord, not this record, is the evidence of that run.";
|
|
86
|
+
|
|
77
87
|
const RETIRE_NEXT_ACTION =
|
|
78
88
|
"The Initiative has no active batch. Run each remaining child with imm-run under its own authorization, or start a new batch for this Initiative after checking out its batch branch.";
|
|
79
89
|
|
|
80
|
-
|
|
90
|
+
/**
|
|
91
|
+
* The terminal lifecycle the coordinator's Kernel store holds for a task, or
|
|
92
|
+
* null. Fail closed: no run, a live run, a task that still owns the workspace
|
|
93
|
+
* claim, or an unreadable store all read as null, so the child stays mid-flight.
|
|
94
|
+
*/
|
|
95
|
+
function kernelLifecycleOf(root: string, taskId: string): "done" | "stopped" | null {
|
|
96
|
+
try {
|
|
97
|
+
if (readActiveClaimTaskId(root) === taskId) return null;
|
|
98
|
+
return readSettledRunEvidence(root, taskId)?.lifecycle ?? null;
|
|
99
|
+
} catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function childEvidenceOf(root: string, record: AnyBatchRunStateRecord): StaleBatchChildEvidence[] {
|
|
81
105
|
if (record.contract === "assurance_kernel/batch_run_state/v2")
|
|
82
106
|
return record.children.map((child) => ({
|
|
83
107
|
task_id: child.task_id,
|
|
@@ -85,6 +109,7 @@ function childEvidenceOf(record: AnyBatchRunStateRecord): StaleBatchChildEvidenc
|
|
|
85
109
|
state: child.state,
|
|
86
110
|
commit: child.commit,
|
|
87
111
|
lane_branch: child.lane?.branch ?? null,
|
|
112
|
+
kernel_lifecycle: kernelLifecycleOf(root, child.task_id),
|
|
88
113
|
}));
|
|
89
114
|
return record.children.map((child) => ({
|
|
90
115
|
task_id: child.task_id,
|
|
@@ -92,12 +117,13 @@ function childEvidenceOf(record: AnyBatchRunStateRecord): StaleBatchChildEvidenc
|
|
|
92
117
|
state: child.state,
|
|
93
118
|
commit: child.commit,
|
|
94
119
|
lane_branch: null,
|
|
120
|
+
kernel_lifecycle: kernelLifecycleOf(root, child.task_id),
|
|
95
121
|
}));
|
|
96
122
|
}
|
|
97
123
|
|
|
98
|
-
function inFlightChildOf(record: AnyBatchRunStateRecord): { task_id: string } | null {
|
|
124
|
+
function inFlightChildOf(root: string, record: AnyBatchRunStateRecord): { task_id: string } | null {
|
|
99
125
|
const inFlight = record.contract === "assurance_kernel/batch_run_state/v2" ? LANE_IN_FLIGHT : SERIAL_IN_FLIGHT;
|
|
100
|
-
return record.children.find((child) => inFlight.has(child.state)) ?? null;
|
|
126
|
+
return record.children.find((child) => inFlight.has(child.state) && kernelLifecycleOf(root, child.task_id) === null) ?? null;
|
|
101
127
|
}
|
|
102
128
|
|
|
103
129
|
/**
|
|
@@ -114,7 +140,7 @@ export function readStaleBatchLookup(root: string, initiativeSlug: string): Stal
|
|
|
114
140
|
if (found.corrupt) return { kind: "corrupt", path: found.path };
|
|
115
141
|
const record = found.record;
|
|
116
142
|
if (isTerminalBatchState(record.batch_state)) return { kind: "terminal", batch_id: record.batch_id };
|
|
117
|
-
const inFlight = inFlightChildOf(record);
|
|
143
|
+
const inFlight = inFlightChildOf(root, record);
|
|
118
144
|
if (inFlight) return { kind: "in_flight", batch_id: record.batch_id, task_id: inFlight.task_id };
|
|
119
145
|
return {
|
|
120
146
|
kind: "retirable",
|
|
@@ -128,7 +154,7 @@ export function readStaleBatchLookup(root: string, initiativeSlug: string): Stal
|
|
|
128
154
|
base_head: record.base_head,
|
|
129
155
|
confirmation_time: record.confirmation_time,
|
|
130
156
|
recorded_commits: [...record.commits],
|
|
131
|
-
children: childEvidenceOf(record),
|
|
157
|
+
children: childEvidenceOf(root, record),
|
|
132
158
|
},
|
|
133
159
|
};
|
|
134
160
|
}
|
|
@@ -188,6 +214,10 @@ export async function retireStaleBatch<HostRejection>(options: {
|
|
|
188
214
|
// Children, commits and branch are carried over untouched: the disposition
|
|
189
215
|
// records what the batch did and did not do, and invents neither.
|
|
190
216
|
const retired: AnyBatchRunStateRecord = { ...record, batch_state: "superseded" };
|
|
217
|
+
const inFlightStates = record.contract === "assurance_kernel/batch_run_state/v2" ? LANE_IN_FLIGHT : SERIAL_IN_FLIGHT;
|
|
218
|
+
const reason = record.children.some((child) => inFlightStates.has(child.state))
|
|
219
|
+
? RETIRE_REASON + RETIRE_REASON_ENDED_OUTSIDE
|
|
220
|
+
: RETIRE_REASON;
|
|
191
221
|
const stored = replaceAnyBatchRunState(root, expected, retired, () => {
|
|
192
222
|
if (!readSecureProjectBytes(root, batchStatePath(record.batch_id)).equals(expected))
|
|
193
223
|
throw new Error("batch state changed before the disposition was applied");
|
|
@@ -202,7 +232,7 @@ export async function retireStaleBatch<HostRejection>(options: {
|
|
|
202
232
|
max_parallel: stored.max_parallel,
|
|
203
233
|
children: stored.children,
|
|
204
234
|
commits: stored.commits,
|
|
205
|
-
reason
|
|
235
|
+
reason,
|
|
206
236
|
handoffs: [],
|
|
207
237
|
next_action: RETIRE_NEXT_ACTION,
|
|
208
238
|
created_at: now,
|
|
@@ -214,7 +244,7 @@ export async function retireStaleBatch<HostRejection>(options: {
|
|
|
214
244
|
batch_state: stored.batch_state,
|
|
215
245
|
children: stored.children,
|
|
216
246
|
commits: stored.commits,
|
|
217
|
-
reason
|
|
247
|
+
reason,
|
|
218
248
|
next_action: RETIRE_NEXT_ACTION,
|
|
219
249
|
created_at: now,
|
|
220
250
|
} satisfies BatchRunReport);
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
// the completing tick runs exactly what the literal user saw at confirmation.
|
|
5
5
|
// A failure marks the completion report as not passed; integrated commits are
|
|
6
6
|
// never rolled back. Only exit status and timing are recorded, never output.
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
8
|
+
import { parseTaskIntentV1 } from "../kernel/intent";
|
|
7
9
|
import { spawn, spawnSync } from "node:child_process";
|
|
8
10
|
|
|
9
11
|
export const MAX_FINAL_VERIFICATION_COMMANDS = 8;
|
|
@@ -38,6 +40,24 @@ export function parseFinalVerification(value: unknown): string[] | undefined {
|
|
|
38
40
|
});
|
|
39
41
|
}
|
|
40
42
|
|
|
43
|
+
export function parseFinalVerificationReport(value: unknown, commands?: readonly string[]): FinalVerificationReport {
|
|
44
|
+
if (!value || typeof value !== "object") throw new Error("invalid final verification result");
|
|
45
|
+
const report = value as FinalVerificationReport;
|
|
46
|
+
if (typeof report.passed !== "boolean" || !(report.head === null || typeof report.head === "string") ||
|
|
47
|
+
!Array.isArray(report.results) || !report.results.length || report.results.some(result =>
|
|
48
|
+
!result || typeof result.command !== "string" || typeof result.passed !== "boolean" ||
|
|
49
|
+
!(result.exit_code === null || Number.isInteger(result.exit_code)) ||
|
|
50
|
+
!(result.signal === null || typeof result.signal === "string") ||
|
|
51
|
+
!Number.isFinite(result.duration_ms) || result.duration_ms < 0 ||
|
|
52
|
+
result.passed !== (result.exit_code === 0 && result.signal === null)))
|
|
53
|
+
throw new Error("invalid final verification result");
|
|
54
|
+
parseFinalVerification(report.results.map(result => result.command));
|
|
55
|
+
if (report.passed && report.results.some(result => !result.passed)) throw new Error("contradictory final verification result");
|
|
56
|
+
if (commands && JSON.stringify(report.results.map(result => result.command)) !== JSON.stringify(commands))
|
|
57
|
+
throw new Error("final verification commands changed");
|
|
58
|
+
return report;
|
|
59
|
+
}
|
|
60
|
+
|
|
41
61
|
/** One command, asynchronously, so a long suite never blocks the Host's event loop. */
|
|
42
62
|
function runOne(root: string, command: string, timeoutMs: number): Promise<FinalVerificationResult> {
|
|
43
63
|
const [executable, ...argv] = command.split(" ");
|
|
@@ -48,7 +68,7 @@ function runOne(root: string, command: string, timeoutMs: number): Promise<Final
|
|
|
48
68
|
if (settled) return;
|
|
49
69
|
settled = true;
|
|
50
70
|
clearTimeout(timer);
|
|
51
|
-
resolve({ command, exit_code: exitCode, signal, passed: exitCode === 0, duration_ms: Date.now() - started });
|
|
71
|
+
resolve({ command, exit_code: exitCode, signal, passed: exitCode === 0 && signal === null, duration_ms: Date.now() - started });
|
|
52
72
|
};
|
|
53
73
|
const child = spawn(executable!, argv, { cwd: root, stdio: "ignore", env: process.env });
|
|
54
74
|
const timer = setTimeout(() => {
|
|
@@ -60,13 +80,47 @@ function runOne(root: string, command: string, timeoutMs: number): Promise<Final
|
|
|
60
80
|
});
|
|
61
81
|
}
|
|
62
82
|
|
|
83
|
+
export function prepareFinalRepairCandidate(input: {
|
|
84
|
+
initiative_slug: string; original_goal: string; scope_hint: string[];
|
|
85
|
+
failure: FinalVerificationReport; prior_repairs: number;
|
|
86
|
+
}) {
|
|
87
|
+
const failure = parseFinalVerificationReport(input.failure);
|
|
88
|
+
if (failure.passed || !failure.head) throw new Error("repair candidate requires a failed final verification bound to a delivery HEAD");
|
|
89
|
+
if (!failure.results.some(result => !result.passed)) throw new Error("reconcile the verification environment or delivery HEAD before preparing a code repair");
|
|
90
|
+
if (!Number.isInteger(input.prior_repairs) || input.prior_repairs < 0 || input.prior_repairs >= 2)
|
|
91
|
+
throw new Error("Parent coordinator correction budget exhausted");
|
|
92
|
+
const identity = createHash("sha256").update(JSON.stringify({ head: failure.head,
|
|
93
|
+
results: failure.results.map(({ command, exit_code, signal }) => ({ command, exit_code, signal })) })).digest("hex");
|
|
94
|
+
const taskId = `${input.initiative_slug.slice(0, 96)}-repair-${identity.slice(0, 12)}`;
|
|
95
|
+
const candidate = parseTaskIntentV1({ contract: "assurance_kernel/task_intent/v1", task_id: taskId,
|
|
96
|
+
goal: input.original_goal, owner: "user", revision: 1, risk: "material", scope_hint: [...new Set([...input.scope_hint, `docs/plans/${taskId}.intent.json`])],
|
|
97
|
+
acceptance: failure.results.map((result, index) => {
|
|
98
|
+
const [executable, ...argv] = result.command.split(" ");
|
|
99
|
+
return { id: `final-${index + 1}`, assertion: `Restore the original goal and pass final command ${index + 1}`,
|
|
100
|
+
verification: JSON.stringify({ contract: "assurance_kernel/verification_descriptor/v2",
|
|
101
|
+
command: { executable, argv, cwd: ".", timeout_ms: 120000, max_output_bytes: 131072 },
|
|
102
|
+
environment: { prepare: null, writable_paths: [] } }, null, 2) + "\n" };
|
|
103
|
+
}) });
|
|
104
|
+
return { failure_id: `sha256:${identity}`, delivery_head: failure.head, candidate };
|
|
105
|
+
}
|
|
106
|
+
|
|
63
107
|
export async function runFinalVerification(
|
|
64
108
|
root: string,
|
|
65
109
|
commands: readonly string[],
|
|
66
110
|
options: { timeoutMs?: number } = {},
|
|
67
111
|
): Promise<FinalVerificationReport> {
|
|
112
|
+
const verifiedCommands = parseFinalVerification([...commands])!;
|
|
68
113
|
const head = spawnSync("git", ["-C", root, "rev-parse", "HEAD"], { encoding: "utf8" });
|
|
114
|
+
const clean = () => {
|
|
115
|
+
const tracked = spawnSync("git", ["-C", root, "diff", "--quiet", "HEAD"]);
|
|
116
|
+
const untracked = spawnSync("git", ["-C", root, "ls-files", "--others", "--exclude-standard", "-z"], { encoding: "utf8" });
|
|
117
|
+
return tracked.status === 0 && untracked.status === 0 && untracked.stdout.length === 0;
|
|
118
|
+
};
|
|
119
|
+
const cleanBefore = clean();
|
|
69
120
|
const results: FinalVerificationResult[] = [];
|
|
70
|
-
for (const command of
|
|
71
|
-
|
|
121
|
+
for (const command of verifiedCommands) results.push(await runOne(root, command, options.timeoutMs ?? DEFAULT_TIMEOUT_MS));
|
|
122
|
+
const after = spawnSync("git", ["-C", root, "rev-parse", "HEAD"], { encoding: "utf8" });
|
|
123
|
+
const unchangedHead = head.status === 0 && after.status === 0 && head.stdout.trim() === after.stdout.trim();
|
|
124
|
+
const cleanAfter = clean();
|
|
125
|
+
return { passed: results.every(result => result.passed) && unchangedHead && cleanBefore && cleanAfter, head: head.status === 0 ? head.stdout.trim() : null, results };
|
|
72
126
|
}
|