immune-brain 3.6.9 → 4.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.
- package/package.json +3 -2
- package/plugins/immune-brain/.claude-plugin/plugin.json +1 -1
- package/plugins/immune-brain/.pi-extension/imm-canary-enroll.ts +18 -2
- package/plugins/immune-brain/.pi-extension/imm-canary-work.ts +76 -121
- package/plugins/immune-brain/.pi-extension/imm-unattended-batch.ts +106 -600
- package/plugins/immune-brain/.pi-extension/pi-canary-assurance-progression.ts +1 -0
- package/plugins/immune-brain/.pi-extension/pi-canary-verification.ts +3 -3
- package/plugins/immune-brain/.pi-extension/runtime-stub.ts +17 -43
- package/plugins/immune-brain/dist/claude/mcp-server.mjs +7587 -5049
- package/plugins/immune-brain/dist/docs/reference/planning-artifact-retention.md +11 -12
- package/plugins/immune-brain/dist/docs/reference/subagent-dispatch-protocol.md +1 -1
- package/plugins/immune-brain/dist/imm-loop.md +27 -25
- package/plugins/immune-brain/dist/imm-planner.md +34 -27
- package/plugins/immune-brain/dist/imm-review-retro.md +2 -2
- package/plugins/immune-brain/dist/role-prompts/code-review.md +3 -1
- package/plugins/immune-brain/dist/role-prompts/executor.md +4 -4
- package/plugins/immune-brain/runtime/assurance/coordinator.ts +183 -40
- package/plugins/immune-brain/runtime/assurance/delivery_workspace.ts +240 -0
- package/plugins/immune-brain/runtime/assurance/qa.ts +132 -58
- package/plugins/immune-brain/runtime/assurance/review_evidence.ts +15 -7
- package/plugins/immune-brain/runtime/assurance/verification.ts +246 -206
- package/plugins/immune-brain/runtime/authorization_operation.ts +20 -0
- package/plugins/immune-brain/runtime/claude/kernel_ports.ts +288 -721
- package/plugins/immune-brain/runtime/commands/kernel.ts +158 -67
- package/plugins/immune-brain/runtime/github_issue_tracker.ts +1 -1
- package/plugins/immune-brain/runtime/kernel/actor_identity.ts +33 -0
- package/plugins/immune-brain/runtime/kernel/application.ts +22 -6
- package/plugins/immune-brain/runtime/kernel/assurance_projection.ts +94 -5
- package/plugins/immune-brain/runtime/kernel/authority_port.ts +27 -6
- package/plugins/immune-brain/runtime/kernel/backend_claim.ts +43 -16
- package/plugins/immune-brain/runtime/kernel/batch_authority.ts +10 -6
- package/plugins/immune-brain/runtime/kernel/canary_application.ts +50 -63
- package/plugins/immune-brain/runtime/kernel/canary_eligibility.ts +13 -4
- package/plugins/immune-brain/runtime/kernel/completion.ts +5 -14
- package/plugins/immune-brain/runtime/kernel/enrollment.ts +124 -34
- package/plugins/immune-brain/runtime/kernel/enrollment_authority.ts +13 -5
- package/plugins/immune-brain/runtime/kernel/index.ts +3 -1
- package/plugins/immune-brain/runtime/kernel/intent.ts +7 -11
- package/plugins/immune-brain/runtime/kernel/legacy_audit.ts +4 -1
- package/plugins/immune-brain/runtime/kernel/legacy_task_record.ts +323 -0
- package/plugins/immune-brain/runtime/kernel/pi_canary_prepare.ts +10 -1
- package/plugins/immune-brain/runtime/kernel/reducer.ts +32 -31
- package/plugins/immune-brain/runtime/kernel/run_identity.ts +121 -0
- package/plugins/immune-brain/runtime/kernel/spec_binding.ts +100 -0
- package/plugins/immune-brain/runtime/kernel/sqlite_migration.ts +950 -0
- package/plugins/immune-brain/runtime/kernel/sqlite_store.ts +1193 -0
- package/plugins/immune-brain/runtime/kernel/storage.ts +1254 -1206
- package/plugins/immune-brain/runtime/kernel/storage_layout_migration.ts +129 -755
- package/plugins/immune-brain/runtime/kernel/storage_paths.ts +419 -46
- package/plugins/immune-brain/runtime/kernel/types.ts +12 -43
- package/plugins/immune-brain/runtime/kernel/validation.ts +60 -274
- package/plugins/immune-brain/runtime/managed_task_routing_policy.ts +0 -1
- package/plugins/immune-brain/runtime/plan_core.ts +27 -65
- package/plugins/immune-brain/runtime/plugin_version.ts +1 -1
- package/plugins/immune-brain/runtime/prompts/code-review.md +3 -1
- package/plugins/immune-brain/runtime/prompts/executor.md +4 -4
- package/plugins/immune-brain/runtime/staged_intent.ts +58 -0
- package/plugins/immune-brain/runtime/unattended/batch_git.ts +37 -7
- package/plugins/immune-brain/runtime/unattended/batch_plan.ts +42 -2
- package/plugins/immune-brain/runtime/unattended/batch_preflight.ts +771 -0
- package/plugins/immune-brain/runtime/unattended/batch_reasons.ts +189 -0
- package/plugins/immune-brain/runtime/unattended/batch_runner.ts +35 -0
- package/plugins/immune-brain/runtime/unattended/confirmation_deadline.ts +33 -0
- package/plugins/immune-brain/runtime/unattended/types.ts +14 -1
- package/plugins/immune-brain/runtime/v4_runtime.ts +19 -23
- package/plugins/immune-brain/runtime/verification_descriptor.ts +92 -136
- package/plugins/immune-brain/runtime/workspace_scope.ts +98 -13
- package/plugins/immune-brain/skills/imm-planner/SKILL.md +3 -3
- package/plugins/immune-brain/bin/imm-retire-stale-wrapper +0 -4
- package/plugins/immune-brain/bin/imm-retired +0 -4
- package/plugins/immune-brain/runtime/authority_commit_receipts.ts +0 -716
- package/plugins/immune-brain/runtime/kernel/automatic_observations.ts +0 -451
- package/plugins/immune-brain/runtime/kernel/legacy.ts +0 -299
- package/plugins/immune-brain/runtime/kernel/observation.ts +0 -397
- package/plugins/immune-brain/runtime/kernel/readiness.ts +0 -282
- package/plugins/immune-brain/runtime/kernel/readiness_evidence.ts +0 -132
|
@@ -3,11 +3,13 @@ import { createHash } from "node:crypto";
|
|
|
3
3
|
import {
|
|
4
4
|
existsSync,
|
|
5
5
|
lstatSync,
|
|
6
|
+
mkdirSync,
|
|
6
7
|
readFileSync,
|
|
7
8
|
readlinkSync,
|
|
8
9
|
realpathSync,
|
|
10
|
+
writeFileSync,
|
|
9
11
|
} from "node:fs";
|
|
10
|
-
import { resolve } from "node:path";
|
|
12
|
+
import { dirname, join, resolve } from "node:path";
|
|
11
13
|
|
|
12
14
|
export interface GitWorkspaceSnapshot {
|
|
13
15
|
kind: "git-workspace-v1";
|
|
@@ -34,6 +36,75 @@ function comparePaths(left: string, right: string): number {
|
|
|
34
36
|
return 0;
|
|
35
37
|
}
|
|
36
38
|
|
|
39
|
+
function isDeliveryAttachment(path: string): boolean {
|
|
40
|
+
return path === ".imm/audit" || path.startsWith(".imm/audit/");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isNonDeliveryPath(path: string): boolean {
|
|
44
|
+
return isDeliveryAttachment(path) || isRuntimeAuthorityPath(path);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function isOwnPlanningSidecar(path: string, taskId?: string): boolean {
|
|
48
|
+
return Boolean(taskId) && (path === `docs/plans/${taskId}.intent.json` || path === `docs/plans/archive/${taskId}.intent.json`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isOtherTaskSidecar(path: string, taskId?: string): boolean {
|
|
52
|
+
return /^docs\/plans\/(?:archive\/)?[^/]+\.intent\.json$/.test(path) && !isOwnPlanningSidecar(path, taskId);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function isPlanningNoise(path: string, taskId?: string): boolean {
|
|
56
|
+
return path.startsWith("docs/plans/") && !isOtherTaskSidecar(path, taskId) && !isOwnPlanningSidecar(path, taskId);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function enrollmentBaselinePath(root: string): string {
|
|
60
|
+
return join(root, ".imm/state/enrollment-baseline.json");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function writeEnrollmentBaseline(root: string): void {
|
|
64
|
+
const snapshot = captureGitWorkspaceSnapshot(root);
|
|
65
|
+
if (!snapshot) return;
|
|
66
|
+
const path = enrollmentBaselinePath(root);
|
|
67
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
68
|
+
writeFileSync(path, `${JSON.stringify(snapshot)}\n`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function readEnrollmentBaseline(root: string): GitWorkspaceSnapshot | null {
|
|
72
|
+
const baselinePath = enrollmentBaselinePath(root);
|
|
73
|
+
if (!existsSync(baselinePath)) return null;
|
|
74
|
+
try {
|
|
75
|
+
const baseline = JSON.parse(readFileSync(baselinePath, "utf8")) as GitWorkspaceSnapshot;
|
|
76
|
+
return isGitWorkspaceSnapshot(baseline) ? baseline : null;
|
|
77
|
+
} catch {
|
|
78
|
+
throw new Error("enrollment baseline is unreadable");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function assertNoEnvelopeEscape(
|
|
83
|
+
root: string,
|
|
84
|
+
stagedPaths: readonly string[],
|
|
85
|
+
scope: string[],
|
|
86
|
+
taskId?: string,
|
|
87
|
+
): void {
|
|
88
|
+
const baseline = readEnrollmentBaseline(root);
|
|
89
|
+
const current = baseline ? captureGitWorkspaceSnapshot(root) : null;
|
|
90
|
+
if (baseline && !current) throw new Error("enrollment baseline cannot be compared because Git is unavailable");
|
|
91
|
+
const escaped = [...new Set(stagedPaths)]
|
|
92
|
+
.filter((path) => !isNonDeliveryPath(path) && !isOwnPlanningSidecar(path, taskId) && !isPlanningNoise(path, taskId) && !taskPathMatchesScope(path, scope))
|
|
93
|
+
.filter((path) => !baseline || baseline.dirty_files[path] !== current?.dirty_files[path])
|
|
94
|
+
.sort(comparePaths);
|
|
95
|
+
if (escaped.length > 0)
|
|
96
|
+
throw new Error(`task delivery contains paths outside the authorization envelope: ${escaped.join(", ")}`);
|
|
97
|
+
if (!baseline || !current) return;
|
|
98
|
+
const staged = new Set(stagedPaths);
|
|
99
|
+
const mixed = [...new Set([...Object.keys(baseline.dirty_files), ...Object.keys(current.dirty_files)])]
|
|
100
|
+
.filter((path) => !isNonDeliveryPath(path) && !isOwnPlanningSidecar(path, taskId) && !isPlanningNoise(path, taskId) && !taskPathMatchesScope(path, scope))
|
|
101
|
+
.filter((path) => !isOtherTaskSidecar(path, taskId) || staged.has(path))
|
|
102
|
+
.filter((path) => baseline.dirty_files[path] !== current.dirty_files[path])
|
|
103
|
+
.sort(comparePaths);
|
|
104
|
+
if (mixed.length > 0)
|
|
105
|
+
throw new Error(`task delivery contains paths outside the authorization envelope: ${mixed.join(", ")}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
37
108
|
function isRuntimeAuthorityPath(path: string): boolean {
|
|
38
109
|
return (
|
|
39
110
|
// Kernel v2 task state, workspace coordination, and authority journals are
|
|
@@ -315,7 +386,7 @@ function taskPathMatchesScope(path: string, scope: string[]): boolean {
|
|
|
315
386
|
return scope.some((scopePath) => pathMatchesScope(path, scopePath));
|
|
316
387
|
}
|
|
317
388
|
|
|
318
|
-
function taskSnapshotOnce(root: string, scope: string[]): GitTaskSnapshot {
|
|
389
|
+
function taskSnapshotOnce(root: string, scope: string[], taskId?: string): GitTaskSnapshot {
|
|
319
390
|
const repositoryRoot = git(root, ["rev-parse", "--show-toplevel"])?.trim();
|
|
320
391
|
const head = git(root, ["rev-parse", "--verify", "HEAD^{commit}"])?.trim();
|
|
321
392
|
if (!repositoryRoot || !head || !GIT_OBJECT_ID.test(head))
|
|
@@ -343,14 +414,18 @@ function taskSnapshotOnce(root: string, scope: string[]): GitTaskSnapshot {
|
|
|
343
414
|
"untracked task paths",
|
|
344
415
|
);
|
|
345
416
|
assertNoCaseFoldCollisions([...stagedPaths, ...unstagedPaths, ...untrackedPaths], "Git task paths");
|
|
417
|
+
assertNoEnvelopeEscape(root, stagedPaths, scope, taskId);
|
|
346
418
|
const uncommittedInScope = [...new Set([...unstagedPaths, ...untrackedPaths])]
|
|
347
419
|
.filter((path) => taskPathMatchesScope(path, scope))
|
|
348
420
|
.sort(comparePaths);
|
|
349
421
|
if (uncommittedInScope.length > 0)
|
|
350
422
|
throw new Error(`task scope contains unstaged or untracked changes: ${uncommittedInScope.join(", ")}`);
|
|
351
423
|
|
|
424
|
+
const baseline = readEnrollmentBaseline(root);
|
|
425
|
+
const current = baseline ? captureGitWorkspaceSnapshot(root) : null;
|
|
352
426
|
const taskPaths = [...new Set(stagedPaths)]
|
|
353
|
-
.filter((path) => taskPathMatchesScope(path, scope))
|
|
427
|
+
.filter((path) => !isNonDeliveryPath(path) && taskPathMatchesScope(path, scope))
|
|
428
|
+
.filter((path) => !(baseline && current && baseline.dirty_files[path] === current.dirty_files[path] && baseline.dirty_files[path] !== undefined))
|
|
354
429
|
.sort(comparePaths);
|
|
355
430
|
const stagedFiles: Record<string, GitTaskIndexEntry> = {};
|
|
356
431
|
for (const path of taskPaths) {
|
|
@@ -377,6 +452,7 @@ function taskSnapshotOnce(root: string, scope: string[]): GitTaskSnapshot {
|
|
|
377
452
|
export function captureGitTaskSnapshot(
|
|
378
453
|
projectRoot: string,
|
|
379
454
|
scopeHint: unknown,
|
|
455
|
+
taskId?: string,
|
|
380
456
|
): GitTaskSnapshot {
|
|
381
457
|
const requestedRoot = resolve(projectRoot);
|
|
382
458
|
const requestedStat = lstatSync(requestedRoot);
|
|
@@ -384,9 +460,9 @@ export function captureGitTaskSnapshot(
|
|
|
384
460
|
throw new Error("task snapshot root must be a real directory");
|
|
385
461
|
const root = realpathSync(requestedRoot);
|
|
386
462
|
const scope = assertCanonicalTaskScope(scopeHint);
|
|
387
|
-
const before = taskSnapshotOnce(root, scope);
|
|
463
|
+
const before = taskSnapshotOnce(root, scope, taskId);
|
|
388
464
|
gitTaskSnapshotTestHook?.();
|
|
389
|
-
const after = taskSnapshotOnce(root, scope);
|
|
465
|
+
const after = taskSnapshotOnce(root, scope, taskId);
|
|
390
466
|
if (JSON.stringify(after) !== JSON.stringify(before))
|
|
391
467
|
throw new Error("Git task snapshot changed while being captured");
|
|
392
468
|
return before;
|
|
@@ -404,16 +480,17 @@ function hashTaskSnapshot(snapshot: object): string {
|
|
|
404
480
|
export function taskDiffIdentity(
|
|
405
481
|
projectRoot: string,
|
|
406
482
|
scopeHint: unknown,
|
|
483
|
+
taskId?: string,
|
|
407
484
|
): GitTaskDiffIdentity {
|
|
408
|
-
const snapshot = captureGitTaskSnapshot(projectRoot, scopeHint);
|
|
485
|
+
const snapshot = captureGitTaskSnapshot(projectRoot, scopeHint, taskId);
|
|
409
486
|
return {
|
|
410
487
|
diff_hash: hashTaskSnapshot(snapshot),
|
|
411
488
|
changed_paths: Object.keys(snapshot.staged_files).sort(comparePaths),
|
|
412
489
|
};
|
|
413
490
|
}
|
|
414
491
|
|
|
415
|
-
export function taskDiffHash(projectRoot: string, scopeHint: unknown): string {
|
|
416
|
-
return taskDiffIdentity(projectRoot, scopeHint).diff_hash;
|
|
492
|
+
export function taskDiffHash(projectRoot: string, scopeHint: unknown, taskId?: string): string {
|
|
493
|
+
return taskDiffIdentity(projectRoot, scopeHint, taskId).diff_hash;
|
|
417
494
|
}
|
|
418
495
|
|
|
419
496
|
function gitRequired(root: string, args: string[], failure: string): string {
|
|
@@ -441,6 +518,7 @@ function taskRevisionSnapshotOnce(
|
|
|
441
518
|
root: string,
|
|
442
519
|
scope: string[],
|
|
443
520
|
baseHead: string,
|
|
521
|
+
taskId?: string,
|
|
444
522
|
): GitTaskRevisionSnapshot {
|
|
445
523
|
const repositoryRoot = git(root, ["rev-parse", "--show-toplevel"])?.trim();
|
|
446
524
|
const head = git(root, ["rev-parse", "--verify", "HEAD^{commit}"])?.trim();
|
|
@@ -476,7 +554,11 @@ function taskRevisionSnapshotOnce(
|
|
|
476
554
|
gitBytes(root, ["ls-files", "--others", "--exclude-standard", "-z", "--"]),
|
|
477
555
|
"untracked task revision paths",
|
|
478
556
|
);
|
|
479
|
-
|
|
557
|
+
assertNoEnvelopeEscape(root, stagedPaths, scope, taskId);
|
|
558
|
+
const baseline = readEnrollmentBaseline(root);
|
|
559
|
+
const current = baseline ? captureGitWorkspaceSnapshot(root) : null;
|
|
560
|
+
const scopedStagedPaths = stagedPaths.filter((path) => !isNonDeliveryPath(path) && taskPathMatchesScope(path, scope))
|
|
561
|
+
.filter((path) => !(baseline && current && baseline.dirty_files[path] === current.dirty_files[path] && baseline.dirty_files[path] !== undefined));
|
|
480
562
|
const scopedUnstagedPaths = unstagedPaths.filter((path) => taskPathMatchesScope(path, scope));
|
|
481
563
|
const scopedUntrackedPaths = untrackedPaths.filter((path) => taskPathMatchesScope(path, scope));
|
|
482
564
|
assertNoCaseFoldCollisions(
|
|
@@ -517,6 +599,7 @@ export function captureGitTaskRevisionSnapshot(
|
|
|
517
599
|
projectRoot: string,
|
|
518
600
|
scopeHint: unknown,
|
|
519
601
|
baseHead: unknown,
|
|
602
|
+
taskId?: string,
|
|
520
603
|
): GitTaskRevisionSnapshot {
|
|
521
604
|
const requestedRoot = resolve(projectRoot);
|
|
522
605
|
const requestedStat = lstatSync(requestedRoot);
|
|
@@ -527,9 +610,9 @@ export function captureGitTaskRevisionSnapshot(
|
|
|
527
610
|
throw new Error("task revision base must be a Git commit id");
|
|
528
611
|
const scope = assertCanonicalTaskScope(scopeHint);
|
|
529
612
|
const normalizedBase = baseHead.toLowerCase();
|
|
530
|
-
const before = taskRevisionSnapshotOnce(root, scope, normalizedBase);
|
|
613
|
+
const before = taskRevisionSnapshotOnce(root, scope, normalizedBase, taskId);
|
|
531
614
|
gitTaskSnapshotTestHook?.();
|
|
532
|
-
const after = taskRevisionSnapshotOnce(root, scope, normalizedBase);
|
|
615
|
+
const after = taskRevisionSnapshotOnce(root, scope, normalizedBase, taskId);
|
|
533
616
|
if (JSON.stringify(after) !== JSON.stringify(before))
|
|
534
617
|
throw new Error("Git task revision changed while being captured");
|
|
535
618
|
return before;
|
|
@@ -539,8 +622,9 @@ export function taskRevisionIdentity(
|
|
|
539
622
|
projectRoot: string,
|
|
540
623
|
scopeHint: unknown,
|
|
541
624
|
baseHead: string,
|
|
625
|
+
taskId?: string,
|
|
542
626
|
): GitTaskDiffIdentity {
|
|
543
|
-
const snapshot = captureGitTaskRevisionSnapshot(projectRoot, scopeHint, baseHead);
|
|
627
|
+
const snapshot = captureGitTaskRevisionSnapshot(projectRoot, scopeHint, baseHead, taskId);
|
|
544
628
|
return {
|
|
545
629
|
diff_hash: hashTaskSnapshot(snapshot),
|
|
546
630
|
changed_paths: Object.keys(snapshot.changed_paths).sort(comparePaths),
|
|
@@ -552,8 +636,9 @@ export function taskRevisionDiffHash(
|
|
|
552
636
|
projectRoot: string,
|
|
553
637
|
scopeHint: unknown,
|
|
554
638
|
baseHead: string,
|
|
639
|
+
taskId?: string,
|
|
555
640
|
): string {
|
|
556
|
-
return taskRevisionIdentity(projectRoot, scopeHint, baseHead).diff_hash;
|
|
641
|
+
return taskRevisionIdentity(projectRoot, scopeHint, baseHead, taskId).diff_hash;
|
|
557
642
|
}
|
|
558
643
|
|
|
559
644
|
function isGitWorkspaceSnapshot(value: unknown): value is GitWorkspaceSnapshot {
|
|
@@ -9,8 +9,8 @@ Use [`../../dist/imm-planner.md`](../../dist/imm-planner.md) as the canonical co
|
|
|
9
9
|
index, not a whole-document read. Explicit entry only: ordinary host requests stay
|
|
10
10
|
host-native.
|
|
11
11
|
|
|
12
|
-
Mandatory constraints before any action: Planner writes
|
|
13
|
-
|
|
12
|
+
Mandatory constraints before any action: Planner writes a TaskIntent, and a Spec
|
|
13
|
+
only for complex work; it never implements, overwrites an enrolled TaskIntent, or
|
|
14
14
|
grants execution authority — only the native Enrollment gate can.
|
|
15
15
|
|
|
16
16
|
Section routes - load a section's instructions only when its branch applies.
|
|
@@ -35,7 +35,7 @@ own routes instead of these stages.
|
|
|
35
35
|
- retirement design: [Retirement Completion Contract](../../dist/imm-planner.md#retirement-completion-contract)
|
|
36
36
|
- optional research dispatch: [Research Dispatch](../../dist/imm-planner.md#research-dispatch)
|
|
37
37
|
|
|
38
|
-
Plan-only requests stop after candidate
|
|
38
|
+
Plan-only requests stop after candidate TaskIntent validation, plus Spec validation when the work is complex. Requests
|
|
39
39
|
that include execution invoke the current Host's native Enrollment gate
|
|
40
40
|
directly, without chat pre-confirmation. Native-gate failure stays fail-closed
|
|
41
41
|
in that Host: report its reason and one retry action only; never suggest
|