immune-brain 2.8.3 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/.claude-plugin/marketplace.json +16 -0
  2. package/README.md +2 -2
  3. package/README.zh-CN.md +2 -2
  4. package/package.json +9 -2
  5. package/plugins/immune-brain/.claude-plugin/plugin.json +8 -0
  6. package/plugins/immune-brain/.mcp.json +8 -0
  7. package/plugins/immune-brain/.pi-extension/imm-canary-work.ts +354 -64
  8. package/plugins/immune-brain/.pi-extension/pi-canary-assurance-progression.ts +74 -706
  9. package/plugins/immune-brain/.pi-extension/pi-canary-invocations.ts +1 -90
  10. package/plugins/immune-brain/.pi-extension/pi-canary-native-review.ts +13 -160
  11. package/plugins/immune-brain/.pi-extension/pi-canary-qa-findings.ts +1 -50
  12. package/plugins/immune-brain/.pi-extension/pi-canary-review-bundle.ts +1 -262
  13. package/plugins/immune-brain/.pi-extension/pi-canary-tool-failure.ts +3 -2
  14. package/plugins/immune-brain/.pi-extension/pi-canary-verification.ts +8 -229
  15. package/plugins/immune-brain/.pi-extension/runtime-stub.ts +23 -5
  16. package/plugins/immune-brain/agents/immune-brain-reviewer.md +11 -0
  17. package/plugins/immune-brain/dist/claude/mcp-server.mjs +7514 -0
  18. package/plugins/immune-brain/dist/docs/reference/code-quality-guard.md +58 -0
  19. package/plugins/immune-brain/dist/docs/reference/immune-brain-config.md +1 -1
  20. package/plugins/immune-brain/dist/docs/reference/planning-quality-gate.md +1 -1
  21. package/plugins/immune-brain/dist/docs/reference/subagent-dispatch-protocol.md +19 -13
  22. package/plugins/immune-brain/dist/imm-loop.md +6 -7
  23. package/plugins/immune-brain/dist/imm-planner.md +1 -1
  24. package/plugins/immune-brain/dist/imm-pr-fix.md +9 -0
  25. package/plugins/immune-brain/dist/role-prompts/code-review.md +33 -13
  26. package/plugins/immune-brain/dist/role-prompts/executor.md +14 -0
  27. package/plugins/immune-brain/dist/role-prompts/pr-fix.md +9 -0
  28. package/plugins/immune-brain/dist/role-prompts/test-fixer.md +7 -0
  29. package/plugins/immune-brain/hooks/hooks.json +55 -0
  30. package/plugins/immune-brain/runtime/assurance/coordinator.ts +836 -0
  31. package/plugins/immune-brain/runtime/assurance/enrollment.ts +6 -0
  32. package/plugins/immune-brain/runtime/assurance/host_port.ts +18 -0
  33. package/plugins/immune-brain/runtime/assurance/invocations.ts +90 -0
  34. package/plugins/immune-brain/runtime/assurance/qa_findings.ts +50 -0
  35. package/plugins/immune-brain/runtime/assurance/review_evidence.ts +596 -0
  36. package/plugins/immune-brain/runtime/assurance/verification.ts +233 -0
  37. package/plugins/immune-brain/runtime/claude/capability.ts +67 -0
  38. package/plugins/immune-brain/runtime/claude/interaction.ts +70 -0
  39. package/plugins/immune-brain/runtime/claude/kernel_ports.ts +789 -0
  40. package/plugins/immune-brain/runtime/claude/mcp_server.ts +363 -0
  41. package/plugins/immune-brain/runtime/claude/review_host.ts +645 -0
  42. package/plugins/immune-brain/runtime/commands/kernel.ts +221 -3
  43. package/plugins/immune-brain/runtime/github_issue_tracker.ts +2 -2
  44. package/plugins/immune-brain/runtime/kernel/application.ts +8 -5
  45. package/plugins/immune-brain/runtime/kernel/assurance_projection.ts +24 -13
  46. package/plugins/immune-brain/runtime/kernel/authority_port.ts +78 -115
  47. package/plugins/immune-brain/runtime/kernel/canary_application.ts +6 -4
  48. package/plugins/immune-brain/runtime/kernel/capability_registry.ts +89 -0
  49. package/plugins/immune-brain/runtime/kernel/completion.ts +64 -6
  50. package/plugins/immune-brain/runtime/kernel/enrollment.ts +189 -100
  51. package/plugins/immune-brain/runtime/kernel/enrollment_authority.ts +37 -80
  52. package/plugins/immune-brain/runtime/kernel/intent.ts +24 -0
  53. package/plugins/immune-brain/runtime/kernel/pi_canary_prepare.ts +31 -0
  54. package/plugins/immune-brain/runtime/kernel/reducer.ts +36 -13
  55. package/plugins/immune-brain/runtime/kernel/storage.ts +16 -16
  56. package/plugins/immune-brain/runtime/kernel/types.ts +32 -2
  57. package/plugins/immune-brain/runtime/kernel/validation.ts +107 -16
  58. package/plugins/immune-brain/runtime/loop_contract.ts +17 -2
  59. package/plugins/immune-brain/runtime/prompts/code-review.md +33 -13
  60. package/plugins/immune-brain/runtime/prompts/executor.md +14 -0
  61. package/plugins/immune-brain/runtime/prompts/pr-fix.md +9 -0
  62. package/plugins/immune-brain/runtime/prompts/test-fixer.md +7 -0
  63. package/plugins/immune-brain/runtime/v4_runtime.ts +5 -2
  64. package/plugins/immune-brain/runtime/workspace_scope.ts +191 -5
  65. package/plugins/immune-brain/skills/imm-loop/SKILL.md +3 -4
  66. package/plugins/immune-brain/skills/imm-planner/SKILL.md +2 -0
@@ -1,18 +1,3 @@
1
- // P2B2 verification descriptor v1 — Pi extension side.
2
- //
3
- // The strict canonical parser for `assurance_kernel/verification_descriptor/v1`
4
- // is the shared pure module `runtime/verification_descriptor.ts`, consumed
5
- // identically by Kernel intent author/validate and by Pi assurance. This file
6
- // re-exports that single implementation and keeps Pi-only runner resolution
7
- // and execution (frozen Bun runner binding, compatibility gate, fixed
8
- // execution, findings digest) extension-owned.
9
-
10
- import { createHash } from "node:crypto";
11
- import { execFileSync, spawn } from "node:child_process";
12
- import { realpathSync, statSync } from "node:fs";
13
- import { isAbsolute, join, resolve, sep, relative } from "node:path";
14
-
15
-
16
1
  export {
17
2
  VERIFICATION_DESCRIPTOR_CONTRACT,
18
3
  parseVerificationDescriptor,
@@ -21,217 +6,11 @@ export {
21
6
  VERIFICATION_DESCRIPTOR_BOUNDS,
22
7
  type VerificationDescriptor,
23
8
  } from "../runtime/verification_descriptor";
24
- // Re-import for direct references in this module.
25
- import {
26
- VERIFICATION_DESCRIPTOR_BOUNDS,
27
- VerificationDescriptorError,
28
- type VerificationDescriptor,
29
- } from "../runtime/verification_descriptor";
30
-
31
- /** Host-owned frozen runner identity: absolute realpath, device/inode, content hash, version. */
32
- export interface FrozenRunner {
33
- runner_id: "bun";
34
- path: string;
35
- dev: number;
36
- ino: number;
37
- content_hash: string;
38
- version: string;
39
- }
40
-
41
- export function resolveBunRunner(): FrozenRunner {
42
- let executable: string;
43
- try {
44
- executable = execFileSync("which", ["bun"], { encoding: "utf8" }).trim();
45
- } catch {
46
- throw new VerificationDescriptorError("bun runner is unavailable on this host");
47
- }
48
- let real: string;
49
- try {
50
- real = realpathSync(executable);
51
- } catch {
52
- throw new VerificationDescriptorError("bun runner realpath is unresolvable");
53
- }
54
- let stat: ReturnType<typeof statSync>;
55
- try {
56
- stat = statSync(real);
57
- } catch {
58
- throw new VerificationDescriptorError("bun runner is unreadable");
59
- }
60
- if (!stat.isFile())
61
- throw new VerificationDescriptorError("bun runner is not a regular file");
62
- const { readFileSync } = require("node:fs") as typeof import("node:fs");
63
- const contentHash = `sha256:${createHash("sha256").update(readFileSync(real)).digest("hex")}`;
64
- let version = "";
65
- try {
66
- version = execFileSync(real, ["--version"], { encoding: "utf8" }).trim();
67
- } catch {
68
- throw new VerificationDescriptorError("bun runner version is unreadable");
69
- }
70
- return {
71
- runner_id: "bun",
72
- path: real,
73
- dev: stat.dev,
74
- ino: stat.ino,
75
- content_hash: contentHash,
76
- version,
77
- };
78
- }
79
-
80
- /** The descriptor's claimed runner version must equal the frozen host runner version. */
81
- export function assertRunnerCompatible(
82
- descriptor: VerificationDescriptor,
83
- runner: FrozenRunner,
84
- ): void {
85
- if (descriptor.runner_version !== runner.version)
86
- throw new VerificationDescriptorError(
87
- `frozen runner version mismatch: descriptor claims ${descriptor.runner_version}, host has ${runner.version}; assurance unavailable`,
88
- );
89
- }
90
-
91
- /** Raised when the host cancels a running fixed verification. */
92
- export class VerificationAbortedError extends Error {
93
- constructor() {
94
- super("fixed verification aborted");
95
- this.name = "VerificationAbortedError";
96
- }
97
- }
98
-
99
- /** Run the frozen runner with the descriptor argv/cwd under minimal environment. */
100
- export async function runFixedVerification(
101
- root: string,
102
- descriptor: VerificationDescriptor,
103
- runner: FrozenRunner,
104
- options: { signal?: AbortSignal } = {},
105
- ): Promise<{ exit_code: number; stdout: string; stderr: string; timed_out: boolean }> {
106
- const canonicalRoot = resolve(root);
107
- const cwd = resolve(canonicalRoot, descriptor.cwd);
108
- if (cwd === canonicalRoot ? false : !relative(canonicalRoot, cwd).startsWith(".") === false)
109
- throw new VerificationDescriptorError("verification cwd escapes the repository");
110
- if (isAbsolute(descriptor.cwd) || relative(canonicalRoot, cwd).startsWith(`..${sep}`) || cwd === resolve(canonicalRoot, ".."))
111
- throw new VerificationDescriptorError("verification cwd escapes the repository");
112
- if (options.signal?.aborted) throw new VerificationAbortedError();
113
- if (process.platform === "win32")
114
- throw new VerificationDescriptorError("fixed verification process-group isolation requires a POSIX host");
115
- const maxOutput = Math.min(descriptor.max_output_bytes, VERIFICATION_DESCRIPTOR_BOUNDS.max_output_bytes);
116
- return new Promise((resolvePromise, rejectPromise) => {
117
- let settled = false;
118
- let stdout = Buffer.alloc(0);
119
- let stderr = Buffer.alloc(0);
120
- let capturedBytes = 0;
121
- const child = spawn(runner.path, descriptor.argv, {
122
- cwd,
123
- env: { PATH: process.env.PATH ?? "", HOME: process.env.HOME ?? "" },
124
- detached: process.platform !== "win32",
125
- stdio: ["ignore", "pipe", "pipe"],
126
- });
127
-
128
- const killTree = () => {
129
- if (child.pid === undefined) return;
130
- try {
131
- if (process.platform !== "win32") process.kill(-child.pid, "SIGKILL");
132
- else child.kill("SIGKILL");
133
- } catch {
134
- // The process already exited.
135
- }
136
- };
137
- const cleanup = () => {
138
- clearTimeout(timeout);
139
- options.signal?.removeEventListener("abort", onAbort);
140
- };
141
- const resolveOnce = (result: { exit_code: number; stdout: string; stderr: string; timed_out: boolean }) => {
142
- if (settled) return;
143
- settled = true;
144
- cleanup();
145
- resolvePromise(result);
146
- };
147
- const rejectOnce = (error: Error) => {
148
- if (settled) return;
149
- settled = true;
150
- cleanup();
151
- rejectPromise(error);
152
- };
153
- const appendBounded = (current: Buffer<ArrayBuffer>, chunk: Buffer<ArrayBuffer>): { content: Buffer<ArrayBuffer>; exceeded: boolean } => {
154
- const available = Math.max(0, maxOutput - capturedBytes);
155
- const accepted = chunk.subarray(0, available);
156
- capturedBytes += accepted.length;
157
- return {
158
- content: accepted.length > 0 ? Buffer.concat([current, accepted]) : current,
159
- exceeded: chunk.length > available,
160
- };
161
- };
162
- const outputLimitExceeded = () => {
163
- const marker = Buffer.from("verification output limit exceeded\n", "utf8");
164
- stdout = stdout.subarray(0, Math.max(0, maxOutput - marker.length));
165
- stderr = marker.subarray(0, maxOutput - stdout.length);
166
- capturedBytes = stdout.length + stderr.length;
167
- killTree();
168
- child.stdout?.destroy();
169
- child.stderr?.destroy();
170
- resolveOnce({
171
- exit_code: 1,
172
- stdout: stdout.toString("utf8"),
173
- stderr: stderr.toString("utf8"),
174
- timed_out: false,
175
- });
176
- };
177
- child.stdout?.on("data", (chunk: Buffer<ArrayBuffer>) => {
178
- const appended = appendBounded(stdout, chunk);
179
- stdout = appended.content;
180
- if (appended.exceeded) outputLimitExceeded();
181
- });
182
- child.stderr?.on("data", (chunk: Buffer<ArrayBuffer>) => {
183
- const appended = appendBounded(stderr, chunk);
184
- stderr = appended.content;
185
- if (appended.exceeded) outputLimitExceeded();
186
- });
187
- child.once("error", (error) => {
188
- resolveOnce({
189
- exit_code: 1,
190
- stdout: stdout.toString("utf8"),
191
- stderr: `${stderr.toString("utf8")}runner spawn failed: ${error.message}`.slice(0, maxOutput),
192
- timed_out: false,
193
- });
194
- });
195
- child.once("close", (code) => {
196
- resolveOnce({
197
- exit_code: code ?? 1,
198
- stdout: stdout.toString("utf8"),
199
- stderr: stderr.toString("utf8"),
200
- timed_out: false,
201
- });
202
- });
203
- const onAbort = () => {
204
- killTree();
205
- child.stdout?.destroy();
206
- child.stderr?.destroy();
207
- rejectOnce(new VerificationAbortedError());
208
- };
209
- options.signal?.addEventListener("abort", onAbort, { once: true });
210
- const timeout = setTimeout(() => {
211
- killTree();
212
- child.stdout?.destroy();
213
- child.stderr?.destroy();
214
- resolveOnce({
215
- exit_code: 1,
216
- stdout: stdout.toString("utf8"),
217
- stderr: stderr.toString("utf8"),
218
- timed_out: true,
219
- });
220
- }, descriptor.timeout_ms);
221
- });
222
- }
223
-
224
- /** Normalized findings digest: stable-sorted keys over id/kind/acceptance_id/summary. */
225
- export function findingsDigest(
226
- findings: Array<{ id: string; kind: string; acceptance_id: string | null; summary: string }>,
227
- ): string {
228
- const normalized = findings.map((f) =>
229
- JSON.stringify({
230
- acceptance_id: f.acceptance_id,
231
- id: f.id,
232
- kind: f.kind,
233
- summary: f.summary,
234
- }),
235
- );
236
- return `sha256:${createHash("sha256").update(`[${normalized.join(",")}]`).digest("hex")}`;
237
- }
9
+ export {
10
+ resolveBunRunner,
11
+ assertRunnerCompatible,
12
+ runFixedVerification,
13
+ findingsDigest,
14
+ VerificationAbortedError,
15
+ type FrozenRunner,
16
+ } from "../runtime/assurance/verification";
@@ -91,8 +91,10 @@ export interface CanaryApplication {
91
91
  export interface StoredTaskMutation {
92
92
  revision: string;
93
93
  record: {
94
- contract: "assurance_kernel/task_record/v3";
94
+ contract: "assurance_kernel/task_record/v3" | "assurance_kernel/task_record/v4";
95
95
  task_id: string;
96
+ /** Present only on TaskRecord v4: the immutable Enrollment commit. */
97
+ git_base_head?: string;
96
98
  intent_snapshot: { revision: number; [key: string]: unknown };
97
99
  intent_ref: { path: string; content_hash: string };
98
100
  lifecycle: "active" | "done" | "stopped";
@@ -133,11 +135,17 @@ export interface TaskTombstone {
133
135
  final_record_hash: string;
134
136
  terminalized_at: string;
135
137
  }
138
+ export type TaskRecordContract =
139
+ | "assurance_kernel/task_record/v3"
140
+ | "assurance_kernel/task_record/v4";
141
+
136
142
  export interface TaskRecordRead {
137
143
  revision: string;
138
144
  record: {
139
- contract: "assurance_kernel/task_record/v3";
145
+ contract: TaskRecordContract;
140
146
  task_id: string;
147
+ /** Present only on TaskRecord v4: the immutable Enrollment commit. */
148
+ git_base_head?: string;
141
149
  intent_snapshot: {
142
150
  task_id: string;
143
151
  revision: number;
@@ -156,6 +164,13 @@ export interface TaskRecordRead {
156
164
  diff_hash: string;
157
165
  actor_id: string;
158
166
  acceptance_results: Array<{ acceptance_id: string; status: "passed" | "failed" | "blocked"; summary: string }>;
167
+ review_revision?: {
168
+ contract: "assurance_kernel/review_revision_identity/v1";
169
+ base_head: string;
170
+ review_commit: string;
171
+ review_tree: string;
172
+ manifest_digest: string;
173
+ };
159
174
  }>;
160
175
  findings: Array<{ id: string; kind: string; status: string; summary?: string }>;
161
176
  } | null;
@@ -239,7 +254,7 @@ export async function preparePiCanary(
239
254
  root: string,
240
255
  input: PiCanaryPrepareInput,
241
256
  ): Promise<PiCanaryPreparation> {
242
- const mod = await import(/* @vite-ignore */ kernelPath("pi_canary_prepare"));
257
+ const mod = await import(/* @vite-ignore */ runtimePath("assurance/enrollment"));
243
258
  return mod.preparePiCanary(root, input) as unknown as PiCanaryPreparation;
244
259
  }
245
260
  export async function revalidatePiCanary(
@@ -247,7 +262,7 @@ export async function revalidatePiCanary(
247
262
  input: PiCanaryPrepareInput,
248
263
  previous: PiCanaryPreparation,
249
264
  ): Promise<{ unchanged: boolean; current: PiCanaryPreparation }> {
250
- const mod = await import(/* @vite-ignore */ kernelPath("pi_canary_prepare"));
265
+ const mod = await import(/* @vite-ignore */ runtimePath("assurance/enrollment"));
251
266
  return mod.revalidatePiCanary(root, input, previous as never) as unknown as { unchanged: boolean; current: PiCanaryPreparation };
252
267
  }
253
268
  export async function evaluateCanaryEligibility(input: {
@@ -399,7 +414,10 @@ export async function findingsDigestV2(findings: unknown[]): Promise<string> {
399
414
  export async function projectAssurance(
400
415
  root: string,
401
416
  taskId: string,
402
- diffProvider: (root: string, intent: { scope_hint?: unknown }) => string,
417
+ diffProvider: (root: string, record: NonNullable<TaskRecordRead["record"]>) => {
418
+ diff_hash: string;
419
+ changed_paths: readonly string[];
420
+ },
403
421
  ): Promise<AssuranceProjectionResult> {
404
422
  const mod = await import(/* @vite-ignore */ kernelPath("assurance_projection"));
405
423
  return mod.projectAssurance(root, taskId, diffProvider) as unknown as AssuranceProjectionResult;
@@ -0,0 +1,11 @@
1
+ ---
2
+ name: immune-brain-reviewer
3
+ description: Independent Immune-Brain Review authority. Read-only evidence review against an immutable snapshot.
4
+ tools: Read, Grep, Glob, Bash
5
+ ---
6
+
7
+ You are the Immune-Brain Reviewer. Do not edit files, create files, or change Git state.
8
+
9
+ Read only the immutable Review evidence identified in the request. Verify provenance before analyzing findings. Do not treat conversation text, Hook callbacks, or live worktree bytes as authority.
10
+
11
+ Reserve the final turn for exactly one strict JSON verdict. Reply with ONLY that object, without markdown fences or commentary.