immune-brain 3.6.6 → 3.6.7
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 +24 -3
- package/README.zh-CN.md +23 -3
- package/package.json +2 -1
- package/plugins/immune-brain/.claude-plugin/plugin.json +1 -1
- package/plugins/immune-brain/.pi-extension/imm-canary-work.ts +29 -19
- package/plugins/immune-brain/.pi-extension/imm-unattended-batch.ts +932 -0
- package/plugins/immune-brain/.pi-extension/runtime-stub.ts +200 -2
- package/plugins/immune-brain/dist/claude/mcp-server.mjs +3404 -227
- package/plugins/immune-brain/dist/imm-loop.md +22 -0
- package/plugins/immune-brain/dist/role-prompts/code-review.md +9 -1
- package/plugins/immune-brain/runtime/assurance/coordinator.ts +101 -3
- package/plugins/immune-brain/runtime/claude/interaction.ts +16 -3
- package/plugins/immune-brain/runtime/claude/kernel_ports.ts +840 -17
- package/plugins/immune-brain/runtime/claude/mcp_server.ts +72 -10
- package/plugins/immune-brain/runtime/kernel/canary_application.ts +9 -0
- package/plugins/immune-brain/runtime/kernel/completion.ts +19 -1
- package/plugins/immune-brain/runtime/kernel/reducer.ts +162 -11
- package/plugins/immune-brain/runtime/kernel/refutation.ts +82 -0
- package/plugins/immune-brain/runtime/kernel/types.ts +34 -1
- package/plugins/immune-brain/runtime/kernel/validation.ts +202 -9
- package/plugins/immune-brain/runtime/plugin_version.ts +1 -1
- package/plugins/immune-brain/runtime/prompts/code-review.md +9 -1
- package/plugins/immune-brain/runtime/unattended/batch_git.ts +775 -0
- package/plugins/immune-brain/runtime/unattended/batch_plan.ts +203 -0
- package/plugins/immune-brain/runtime/unattended/batch_runner.ts +1224 -0
- package/plugins/immune-brain/runtime/unattended/batch_state.ts +360 -0
- package/plugins/immune-brain/runtime/unattended/types.ts +60 -0
- package/plugins/immune-brain/skills/imm-loop/SKILL.md +1 -0
|
@@ -174,7 +174,15 @@ export interface TaskRecordRead {
|
|
|
174
174
|
manifest_digest: string;
|
|
175
175
|
};
|
|
176
176
|
}>;
|
|
177
|
-
findings: Array<{
|
|
177
|
+
findings: Array<{
|
|
178
|
+
id: string;
|
|
179
|
+
kind: string;
|
|
180
|
+
status: string;
|
|
181
|
+
summary?: string;
|
|
182
|
+
anchor?: string | null;
|
|
183
|
+
evidence?: unknown;
|
|
184
|
+
counterevidence?: { attestation_id: string; acceptance_id: string } | null;
|
|
185
|
+
}>;
|
|
178
186
|
} | null;
|
|
179
187
|
}
|
|
180
188
|
|
|
@@ -283,7 +291,12 @@ export async function readTaskRecord(
|
|
|
283
291
|
taskId: string,
|
|
284
292
|
): Promise<TaskRecordRead> {
|
|
285
293
|
const mod = await import(/* @vite-ignore */ kernelPath("storage"));
|
|
286
|
-
|
|
294
|
+
// Lock-free read only. `storage.readTaskRecord` wraps this in
|
|
295
|
+
// withKernelStoreLock, which first runs pending-transaction recovery, so a
|
|
296
|
+
// decline, a cancel, or a rejected preflight would otherwise mutate Kernel
|
|
297
|
+
// state before the literal user ever approved anything. Mutating Kernel
|
|
298
|
+
// entrypoints still take their own lock when the batch actually starts.
|
|
299
|
+
return mod.readTaskRecordRaw(root, taskId);
|
|
287
300
|
}
|
|
288
301
|
export async function withKernelStoreLock<T>(root: string, operation: () => T): Promise<T> {
|
|
289
302
|
const mod = await import(/* @vite-ignore */ kernelPath("storage"));
|
|
@@ -403,3 +416,188 @@ export async function deriveAssuranceAuthorization(input: {
|
|
|
403
416
|
const mod = await import(/* @vite-ignore */ kernelPath("assurance_projection"));
|
|
404
417
|
return mod.deriveAssuranceAuthorization(input) as AssuranceAuthorizationReadiness;
|
|
405
418
|
}
|
|
419
|
+
|
|
420
|
+
// --- Batch Authority & Unattended Runner Forwarding ---
|
|
421
|
+
function unattendedPath(module: string): string {
|
|
422
|
+
return `../runtime/unattended/${module}.ts`;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
export type {
|
|
426
|
+
BatchPlan,
|
|
427
|
+
BatchPlanChild,
|
|
428
|
+
BatchPlanBudget,
|
|
429
|
+
BatchPlanDigestChild,
|
|
430
|
+
InitiativeObservationReader,
|
|
431
|
+
} from "../runtime/unattended/types";
|
|
432
|
+
export type {
|
|
433
|
+
BatchAuthorityRegistry,
|
|
434
|
+
BatchAuthorizationBinding,
|
|
435
|
+
ValidatedBatchAuthorization,
|
|
436
|
+
} from "../runtime/kernel/batch_authority";
|
|
437
|
+
export type {
|
|
438
|
+
BatchRunnerKernelPort,
|
|
439
|
+
StartBatchInput,
|
|
440
|
+
BatchRunReport,
|
|
441
|
+
} from "../runtime/unattended/batch_runner";
|
|
442
|
+
export type {
|
|
443
|
+
BatchRunnerGitPort,
|
|
444
|
+
BatchGitPreflightResult,
|
|
445
|
+
} from "../runtime/unattended/batch_git";
|
|
446
|
+
|
|
447
|
+
export async function projectBatchPlan(
|
|
448
|
+
root: string,
|
|
449
|
+
initiativeSlug: string,
|
|
450
|
+
input: any,
|
|
451
|
+
readInitiative?: any,
|
|
452
|
+
): Promise<any> {
|
|
453
|
+
const mod = await import(/* @vite-ignore */ unattendedPath("batch_plan"));
|
|
454
|
+
return mod.projectBatchPlan(root, initiativeSlug, input, readInitiative);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export async function computeBatchPlanDigest(children: any): Promise<string> {
|
|
458
|
+
const mod = await import(/* @vite-ignore */ kernelPath("batch_authority"));
|
|
459
|
+
return mod.computeBatchPlanDigest(children);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
export async function createBatchAuthorityRegistry(): Promise<any> {
|
|
463
|
+
const mod = await import(/* @vite-ignore */ kernelPath("batch_authority"));
|
|
464
|
+
return mod.createBatchAuthorityRegistry();
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
export async function deriveChildEnrollment(root: string, registry: any, input: any): Promise<any> {
|
|
468
|
+
const mod = await import(/* @vite-ignore */ kernelPath("batch_authority"));
|
|
469
|
+
return mod.deriveChildEnrollment(root, registry, input);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
export async function startBatch(input: any): Promise<any> {
|
|
473
|
+
const mod = await import(/* @vite-ignore */ unattendedPath("batch_runner"));
|
|
474
|
+
return mod.startBatch(input);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
export async function runBatchGitPreflight(input: any): Promise<any> {
|
|
478
|
+
const mod = await import(/* @vite-ignore */ unattendedPath("batch_git"));
|
|
479
|
+
return mod.runBatchGitPreflight(input);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
export async function readGitHead(root: string): Promise<string> {
|
|
483
|
+
// The Pi extension must reach Kernel prepare only through the shared Enrollment
|
|
484
|
+
// boundary, so this reads HEAD itself rather than importing the Kernel prepare
|
|
485
|
+
// module. The unattended Git module reads HEAD the same way.
|
|
486
|
+
const { spawnSync } = await import("node:child_process");
|
|
487
|
+
const result = spawnSync("git", ["-C", root, "rev-parse", "HEAD"], { encoding: "utf8" });
|
|
488
|
+
if (result.status !== 0) throw new Error(`git rev-parse HEAD is unavailable for ${root}`);
|
|
489
|
+
return result.stdout.trim();
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Read the immutable terminal audit record for a settled child.
|
|
494
|
+
*
|
|
495
|
+
* A child reaches Kernel settlement before the batch commits it, and settlement
|
|
496
|
+
* clears the live state record, so the batch resume preflight must resolve the
|
|
497
|
+
* child's authorized scope from the audit pair instead. Read-only: neither the
|
|
498
|
+
* audit pair nor this reader mutates Kernel state.
|
|
499
|
+
*/
|
|
500
|
+
export async function readSettledTaskRecord(
|
|
501
|
+
root: string,
|
|
502
|
+
taskId: string,
|
|
503
|
+
): Promise<{ scope_hint: string[]; intent_path: string | undefined } | null> {
|
|
504
|
+
const mod = await import(/* @vite-ignore */ kernelPath("storage"));
|
|
505
|
+
const pair = mod.readAuditTaskPair(root, taskId);
|
|
506
|
+
if (!pair?.record) return null;
|
|
507
|
+
const record = pair.record as {
|
|
508
|
+
intent_snapshot?: { scope_hint?: string[] };
|
|
509
|
+
intent_ref?: { path?: string };
|
|
510
|
+
};
|
|
511
|
+
return {
|
|
512
|
+
scope_hint: record.intent_snapshot?.scope_hint ?? [],
|
|
513
|
+
intent_path: record.intent_ref?.path,
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
export async function pathMatchesScope(path: string, scopePath: string): Promise<boolean> {
|
|
518
|
+
const mod = await import(/* @vite-ignore */ runtimePath("workspace_scope"));
|
|
519
|
+
return mod.pathMatchesScope(path, scopePath);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const GLOBAL_PI_PROGRESSION_KEY = Symbol.for("immune_brain.pi_assurance_progression");
|
|
523
|
+
|
|
524
|
+
export async function getSharedPiProgression(): Promise<any> {
|
|
525
|
+
let progression = (globalThis as any)[GLOBAL_PI_PROGRESSION_KEY];
|
|
526
|
+
if (!progression) {
|
|
527
|
+
const workMod = await import(/* @vite-ignore */ "./imm-canary-work");
|
|
528
|
+
const ports = workMod.createPiAssuranceProgressionPorts();
|
|
529
|
+
const progMod = await import(/* @vite-ignore */ "./pi-canary-assurance-progression");
|
|
530
|
+
progression = new progMod.AssuranceProgression(ports);
|
|
531
|
+
(globalThis as any)[GLOBAL_PI_PROGRESSION_KEY] = progression;
|
|
532
|
+
}
|
|
533
|
+
return progression;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
export async function advancePiTask(root: string, taskId: string): Promise<any> {
|
|
537
|
+
const progression = await getSharedPiProgression();
|
|
538
|
+
const result = await progression.advance(taskId, { cwd: root });
|
|
539
|
+
if (result.state === "completed") return { state: "completed" };
|
|
540
|
+
if (result.state === "stopped") return { state: "stopped" };
|
|
541
|
+
if (result.state === "rework") return { state: "rework", operation: result.operation, summary: result.summary };
|
|
542
|
+
if (result.state === "review_ready") return { state: "review_ready", operation_id: result.operation_id, agent_params: result.agent_params };
|
|
543
|
+
if (result.state === "blocked") return { state: "blocked", reason: result.reason };
|
|
544
|
+
return { state: "failed", reason: (result as { reason?: string }).reason ?? "advance failed" };
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
export async function projectAssuranceForTask(root: string, taskId: string): Promise<any> {
|
|
548
|
+
const [assuranceMod, scopeMod] = await Promise.all([
|
|
549
|
+
import(/* @vite-ignore */ kernelPath("assurance_projection")),
|
|
550
|
+
import(/* @vite-ignore */ runtimePath("workspace_scope")),
|
|
551
|
+
]);
|
|
552
|
+
const diffSnapshotOf = (r: string, record: any) => {
|
|
553
|
+
if (record.contract === "assurance_kernel/task_record/v4") {
|
|
554
|
+
if (!record.git_base_head)
|
|
555
|
+
throw new Error("TaskRecord v4 is missing git_base_head");
|
|
556
|
+
return scopeMod.taskRevisionIdentity(r, record.intent_snapshot.scope_hint, record.git_base_head);
|
|
557
|
+
}
|
|
558
|
+
return scopeMod.taskDiffIdentity(r, record.intent_snapshot.scope_hint);
|
|
559
|
+
};
|
|
560
|
+
return assuranceMod.projectAssurance(root, taskId, diffSnapshotOf);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
export async function findExistingActiveBatch(root: string, initiativeSlug: string): Promise<any> {
|
|
564
|
+
const { join } = await import("node:path");
|
|
565
|
+
const { existsSync, readdirSync, readFileSync } = await import("node:fs");
|
|
566
|
+
const batchesDir = join(root, ".imm", "state", "batches");
|
|
567
|
+
if (!existsSync(batchesDir)) return null;
|
|
568
|
+
const files = readdirSync(batchesDir);
|
|
569
|
+
for (const file of files) {
|
|
570
|
+
if (!file.endsWith(".json")) continue;
|
|
571
|
+
let record: any;
|
|
572
|
+
try {
|
|
573
|
+
record = JSON.parse(readFileSync(join(batchesDir, file), "utf8"));
|
|
574
|
+
} catch {
|
|
575
|
+
// Unreadable Kernel batch state must fail closed: silently treating the
|
|
576
|
+
// initiative as batchless could authorize a parallel run.
|
|
577
|
+
return { corrupt: true, path: file };
|
|
578
|
+
}
|
|
579
|
+
if (record.contract === "assurance_kernel/batch_run_state/v1" && record.initiative_slug === initiativeSlug) {
|
|
580
|
+
const validStates = new Set([
|
|
581
|
+
"prepared",
|
|
582
|
+
"running",
|
|
583
|
+
"needs_human",
|
|
584
|
+
"completed",
|
|
585
|
+
"budget_stopped",
|
|
586
|
+
"failed",
|
|
587
|
+
"rejected",
|
|
588
|
+
]);
|
|
589
|
+
if (
|
|
590
|
+
typeof record.batch_id !== "string" ||
|
|
591
|
+
typeof record.base_head !== "string" ||
|
|
592
|
+
!Array.isArray(record.children) ||
|
|
593
|
+
!validStates.has(record.batch_state)
|
|
594
|
+
) {
|
|
595
|
+
return { corrupt: true, path: file };
|
|
596
|
+
}
|
|
597
|
+
return record;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
return null;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
|