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.
Files changed (28) hide show
  1. package/README.md +24 -3
  2. package/README.zh-CN.md +23 -3
  3. package/package.json +2 -1
  4. package/plugins/immune-brain/.claude-plugin/plugin.json +1 -1
  5. package/plugins/immune-brain/.pi-extension/imm-canary-work.ts +29 -19
  6. package/plugins/immune-brain/.pi-extension/imm-unattended-batch.ts +932 -0
  7. package/plugins/immune-brain/.pi-extension/runtime-stub.ts +200 -2
  8. package/plugins/immune-brain/dist/claude/mcp-server.mjs +3404 -227
  9. package/plugins/immune-brain/dist/imm-loop.md +22 -0
  10. package/plugins/immune-brain/dist/role-prompts/code-review.md +9 -1
  11. package/plugins/immune-brain/runtime/assurance/coordinator.ts +101 -3
  12. package/plugins/immune-brain/runtime/claude/interaction.ts +16 -3
  13. package/plugins/immune-brain/runtime/claude/kernel_ports.ts +840 -17
  14. package/plugins/immune-brain/runtime/claude/mcp_server.ts +72 -10
  15. package/plugins/immune-brain/runtime/kernel/canary_application.ts +9 -0
  16. package/plugins/immune-brain/runtime/kernel/completion.ts +19 -1
  17. package/plugins/immune-brain/runtime/kernel/reducer.ts +162 -11
  18. package/plugins/immune-brain/runtime/kernel/refutation.ts +82 -0
  19. package/plugins/immune-brain/runtime/kernel/types.ts +34 -1
  20. package/plugins/immune-brain/runtime/kernel/validation.ts +202 -9
  21. package/plugins/immune-brain/runtime/plugin_version.ts +1 -1
  22. package/plugins/immune-brain/runtime/prompts/code-review.md +9 -1
  23. package/plugins/immune-brain/runtime/unattended/batch_git.ts +775 -0
  24. package/plugins/immune-brain/runtime/unattended/batch_plan.ts +203 -0
  25. package/plugins/immune-brain/runtime/unattended/batch_runner.ts +1224 -0
  26. package/plugins/immune-brain/runtime/unattended/batch_state.ts +360 -0
  27. package/plugins/immune-brain/runtime/unattended/types.ts +60 -0
  28. 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<{ id: string; kind: string; status: string; summary?: string }>;
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
- return mod.readTaskRecord(root, taskId);
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
+