immune-brain 3.6.4 → 3.6.6
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 +45 -0
- package/package.json +1 -1
- package/plugins/immune-brain/.claude-plugin/plugin.json +1 -1
- package/plugins/immune-brain/.pi-extension/imm-canary-work.ts +76 -20
- package/plugins/immune-brain/.pi-extension/pi-canary-interaction.ts +38 -10
- package/plugins/immune-brain/dist/BASELINE.md +48 -15
- package/plugins/immune-brain/dist/claude/mcp-server.mjs +166 -57
- package/plugins/immune-brain/dist/docs/reference/planning-quality-gate.md +1 -1
- package/plugins/immune-brain/dist/docs/reference/subagent-dispatch-protocol.md +1 -1
- package/plugins/immune-brain/dist/imm-agent-doc-maintain.md +9 -1
- package/plugins/immune-brain/dist/imm-brainstorm.md +49 -35
- package/plugins/immune-brain/dist/imm-doc-prune.md +7 -1
- package/plugins/immune-brain/dist/imm-loop.md +31 -13
- package/plugins/immune-brain/dist/imm-planner.md +74 -32
- package/plugins/immune-brain/dist/imm-pr-fix.md +6 -2
- package/plugins/immune-brain/dist/role-prompts/executor.md +18 -10
- package/plugins/immune-brain/dist/role-prompts/pr-fix.md +5 -2
- package/plugins/immune-brain/runtime/assurance/coordinator.ts +18 -0
- package/plugins/immune-brain/runtime/assurance/verification.ts +13 -2
- package/plugins/immune-brain/runtime/claude/kernel_ports.ts +31 -9
- package/plugins/immune-brain/runtime/claude/mcp_server.ts +14 -1
- package/plugins/immune-brain/runtime/commands/kernel.ts +15 -13
- package/plugins/immune-brain/runtime/github_issue_tracker.ts +1112 -20
- package/plugins/immune-brain/runtime/kernel/application.ts +1 -0
- package/plugins/immune-brain/runtime/kernel/assurance_projection.ts +4 -1
- package/plugins/immune-brain/runtime/kernel/batch_authority.ts +407 -0
- package/plugins/immune-brain/runtime/kernel/canary_application.ts +24 -9
- package/plugins/immune-brain/runtime/kernel/enrollment.ts +72 -13
- package/plugins/immune-brain/runtime/kernel/intent.ts +67 -23
- package/plugins/immune-brain/runtime/kernel/reducer.ts +37 -9
- package/plugins/immune-brain/runtime/kernel/types.ts +1 -0
- package/plugins/immune-brain/runtime/kernel/validation.ts +10 -6
- package/plugins/immune-brain/runtime/plugin_version.ts +1 -1
- package/plugins/immune-brain/runtime/prompts/executor.md +18 -10
- package/plugins/immune-brain/runtime/prompts/pr-fix.md +5 -2
- package/plugins/immune-brain/skills/BASELINE.md +48 -15
- package/plugins/immune-brain/skills/imm-agent-doc-maintain/SKILL.md +20 -4
- package/plugins/immune-brain/skills/imm-brainstorm/SKILL.md +24 -64
- package/plugins/immune-brain/skills/imm-doc-prune/SKILL.md +18 -3
- package/plugins/immune-brain/skills/imm-loop/SKILL.md +20 -6
- package/plugins/immune-brain/skills/imm-planner/SKILL.md +35 -8
- package/plugins/immune-brain/skills/imm-pr-fix/SKILL.md +17 -3
|
@@ -436,6 +436,7 @@ export class AssuranceCoordinator {
|
|
|
436
436
|
sessionGenerationValue(): number { return this.sessionGeneration; }
|
|
437
437
|
|
|
438
438
|
async advance(taskId: string, ctx: HostContext, signal?: AbortSignal, onUpdate?: (update: ForegroundToolUpdate) => void): Promise<AssuranceAdvanceResult> {
|
|
439
|
+
if (this.isInvocationOpen(taskId)) return { state: "blocked", reason: "an authority invocation is already open" };
|
|
439
440
|
const active = this.active(taskId);
|
|
440
441
|
if (active?.state === "review_ready") {
|
|
441
442
|
const reservation = this.reviewReservations.get(taskId);
|
|
@@ -819,6 +820,17 @@ export class AssuranceCoordinator {
|
|
|
819
820
|
return { state: "blocked", reason: `Kernel requires ${settled.projection.next_obligation} after Review` };
|
|
820
821
|
}
|
|
821
822
|
|
|
823
|
+
isReviewVerdictValid(taskId: string, verdictInput: unknown): boolean {
|
|
824
|
+
const reservation = this.reviewReservations.get(taskId);
|
|
825
|
+
if (!reservation) return false;
|
|
826
|
+
try {
|
|
827
|
+
parseAssuranceVerdict(verdictInput, reservation.snapshot);
|
|
828
|
+
return true;
|
|
829
|
+
} catch {
|
|
830
|
+
return false;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
822
834
|
abandonReview(taskId: string, reason: string): AssuranceSubmitReviewResult {
|
|
823
835
|
const reservation = this.reviewReservations.get(taskId);
|
|
824
836
|
if (!reservation) return { state: "blocked", reason };
|
|
@@ -833,6 +845,12 @@ export class AssuranceCoordinator {
|
|
|
833
845
|
return { state: "review_ready", operation: "review", operation_id: reservation.operationId, snapshot_digest: snapshotDigest(reservation.snapshot), review_bundle_digest: reservation.snapshot.review_bundle_digest ?? "", agent_params: reservation.hostReservation.dispatch };
|
|
834
846
|
}
|
|
835
847
|
|
|
848
|
+
releaseStoppedReview(taskId: string): void {
|
|
849
|
+
const reservation = this.reviewReservations.get(taskId);
|
|
850
|
+
if (reservation) this.releaseReviewReservation(taskId, reservation);
|
|
851
|
+
this.rejectedReviewOperations.delete(taskId);
|
|
852
|
+
}
|
|
853
|
+
|
|
836
854
|
private releaseReviewReservation(taskId: string, reservation: ReviewReservation, rejectionReason?: string): void {
|
|
837
855
|
if (this.reviewReservations.get(taskId) !== reservation) return;
|
|
838
856
|
this.reviewReservations.delete(taskId);
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// runner, compatibility gate, fixed execution, and findings digest.
|
|
6
6
|
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
8
|
-
import { execFileSync, spawn } from "node:child_process";
|
|
8
|
+
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
9
9
|
import { realpathSync, statSync } from "node:fs";
|
|
10
10
|
import { isAbsolute, join, resolve, sep, relative } from "node:path";
|
|
11
11
|
|
|
@@ -43,7 +43,18 @@ export function resolveBunRunner(): FrozenRunner {
|
|
|
43
43
|
}
|
|
44
44
|
let real: string;
|
|
45
45
|
try {
|
|
46
|
-
|
|
46
|
+
// When which returns a version-manager shim (e.g. mise or asdf),
|
|
47
|
+
// realpathSync on the shim resolves to the version-manager binary rather
|
|
48
|
+
// than bun. Ask bun itself for its real process.execPath.
|
|
49
|
+
const execPath = spawnSync(executable, ["-e", "console.log(process.execPath)"], {
|
|
50
|
+
encoding: "utf8",
|
|
51
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
52
|
+
});
|
|
53
|
+
if (execPath.status === 0 && execPath.stdout.trim().length > 0) {
|
|
54
|
+
real = realpathSync(execPath.stdout.trim());
|
|
55
|
+
} else {
|
|
56
|
+
real = realpathSync(executable);
|
|
57
|
+
}
|
|
47
58
|
} catch {
|
|
48
59
|
throw new VerificationDescriptorError("bun runner realpath is unresolvable");
|
|
49
60
|
}
|
|
@@ -119,13 +119,16 @@ export async function submitClaudeReview(
|
|
|
119
119
|
if (observed.release) return coordinator.abandonReview(taskId, observed.reason);
|
|
120
120
|
return { state: "blocked", reason: observed.reason };
|
|
121
121
|
}
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
122
|
+
const parentValid = coordinator.isReviewVerdictValid(taskId, verdictInput);
|
|
123
|
+
if (!parentValid) return coordinator.submitReview(taskId, ctx, verdictInput);
|
|
124
|
+
const receiptValid = coordinator.isReviewVerdictValid(taskId, observed.receipt.result);
|
|
125
|
+
if (!receiptValid) {
|
|
126
|
+
return coordinator.abandonReview(taskId, "reviewer receipt is not a valid verdict");
|
|
126
127
|
}
|
|
127
|
-
|
|
128
|
-
|
|
128
|
+
const parentJson = extractVerdictJson(verdictInput)!;
|
|
129
|
+
const receiptJson = extractVerdictJson(observed.receipt.result)!;
|
|
130
|
+
if (verdictFingerprint(parentJson) !== verdictFingerprint(receiptJson)) {
|
|
131
|
+
return { state: "blocked", reason: "parent verdict does not match reviewer receipt" };
|
|
129
132
|
}
|
|
130
133
|
return coordinator.submitReview(taskId, ctx, verdictInput);
|
|
131
134
|
}
|
|
@@ -525,6 +528,19 @@ export class ClaudeRuntime {
|
|
|
525
528
|
return submitClaudeReview(this.host, this.coordinator, { cwd: this.cwd }, taskId, verdictInput);
|
|
526
529
|
}
|
|
527
530
|
|
|
531
|
+
/**
|
|
532
|
+
* Ordinary Kernel operation, not a privileged one: canary_application builds
|
|
533
|
+
* the action without a capability and the Pi Host lists resolve_finding in
|
|
534
|
+
* its ordinary KERNEL_OPERATIONS. The reducer owns every precondition, so
|
|
535
|
+
* this port reads no findings and tests no kind.
|
|
536
|
+
*/
|
|
537
|
+
async resolveFinding(taskId: string, findingId: string) {
|
|
538
|
+
return this.executeOrdinary({ cwd: this.cwd }, {
|
|
539
|
+
taskId,
|
|
540
|
+
operation: { op: "resolve_finding", finding_id: findingId, actor_id: "executor" },
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
|
|
528
544
|
async authorize(taskId: string, operation: string, meta: ToolMeta, extra: Record<string, unknown> = {}) {
|
|
529
545
|
if (operation === "repair_authority_state") {
|
|
530
546
|
const authority = reconcileKernelAuthority(this.cwd, taskId);
|
|
@@ -534,7 +550,7 @@ export class ClaudeRuntime {
|
|
|
534
550
|
return repairKernelAuthority(this.cwd, taskId, authority.revision);
|
|
535
551
|
}
|
|
536
552
|
if (!isPrivilegedOperation(operation) && operation !== "request_authorization") throw new Error(`unsupported privileged operation ${operation}`);
|
|
537
|
-
let op: PrivilegedOperation | "request_authorization" | "resolve_user_decision" = operation;
|
|
553
|
+
let op: PrivilegedOperation | "request_authorization" | "resolve_user_decision" | "authorize_rework" = operation;
|
|
538
554
|
let decisionOp: { finding_id: string; resolution: string } | undefined;
|
|
539
555
|
const projection = await this.status(taskId);
|
|
540
556
|
if (projection.error || !projection.claim) throw new Error(projection.error ?? "no active backend claim");
|
|
@@ -551,6 +567,8 @@ export class ClaudeRuntime {
|
|
|
551
567
|
if (open.length !== 1) throw new Error(`resolve-user-decision requires exactly one open user decision; found ${open.length}`);
|
|
552
568
|
op = "resolve_user_decision";
|
|
553
569
|
decisionOp = { finding_id: open[0].id, resolution: `resume after literal-user decision: ${open[0].summary}` };
|
|
570
|
+
} else if (readiness.state === "authorize_rework") {
|
|
571
|
+
op = "authorize_rework";
|
|
554
572
|
} else {
|
|
555
573
|
throw new Error(readiness.blocked ?? "no unique host-derived authorization operation");
|
|
556
574
|
}
|
|
@@ -668,7 +686,11 @@ export class ClaudeRuntime {
|
|
|
668
686
|
diffProvider: diffSnapshotOf,
|
|
669
687
|
now,
|
|
670
688
|
});
|
|
671
|
-
if (
|
|
689
|
+
if (
|
|
690
|
+
op === "stop" ||
|
|
691
|
+
op === "authorize_rework" ||
|
|
692
|
+
op === "approve_breaking_intent_revision"
|
|
693
|
+
) stagePlanningArtifactTransition(this.cwd, result.record);
|
|
672
694
|
return result;
|
|
673
695
|
} catch (error) {
|
|
674
696
|
if (sidecar && priorBytes && priorIndexState) {
|
|
@@ -773,7 +795,7 @@ export class ClaudeRuntime {
|
|
|
773
795
|
}));
|
|
774
796
|
}
|
|
775
797
|
|
|
776
|
-
private async executeOrdinary(ctx: HostContext, input: { taskId: string; operation: { op: string; actor_id: string; next_intent?: unknown } }) {
|
|
798
|
+
private async executeOrdinary(ctx: HostContext, input: { taskId: string; operation: { op: string; actor_id: string; next_intent?: unknown; finding_id?: string } }) {
|
|
777
799
|
const { app } = await this.authority();
|
|
778
800
|
const operation = input.operation.op === "revise_intent"
|
|
779
801
|
? { ...input.operation, next_intent: await parseTaskIntentV1(input.operation.next_intent) }
|
|
@@ -26,6 +26,7 @@ export const TOOLS = [
|
|
|
26
26
|
{ name: "approve_breaking_intent_revision", description: "Approve a breaking TaskIntent revision.", privileged: true },
|
|
27
27
|
{ name: "stop", description: "Stop the active task with literal-user authority.", privileged: true },
|
|
28
28
|
{ name: "repair_authority_state", description: "Repair a proven recoverable stale backend claim.", privileged: false },
|
|
29
|
+
{ name: "resolve_finding", description: "Resolve one open blocking or advisory finding whose cause is fixed and verified.", privileged: false },
|
|
29
30
|
] as const;
|
|
30
31
|
|
|
31
32
|
export function listMcpTools() {
|
|
@@ -39,8 +40,13 @@ export function listMcpTools() {
|
|
|
39
40
|
...(tool.name === "approve_breaking_intent_revision" ? { next_intent: { type: "object" } } : {}),
|
|
40
41
|
...(tool.name === "stop" ? { reason: { type: "string" } } : {}),
|
|
41
42
|
...(tool.name === "submit_review" ? { verdict: { type: "object" } } : {}),
|
|
43
|
+
...(tool.name === "resolve_finding" ? { finding_id: { type: "string" } } : {}),
|
|
42
44
|
},
|
|
43
|
-
required: tool.name === "submit_review"
|
|
45
|
+
required: tool.name === "submit_review"
|
|
46
|
+
? ["task_id", "verdict"]
|
|
47
|
+
: tool.name === "resolve_finding"
|
|
48
|
+
? ["task_id", "finding_id"]
|
|
49
|
+
: ["task_id"],
|
|
44
50
|
},
|
|
45
51
|
annotations: tool.privileged ? privilegedAnnotations() : { readOnlyHint: tool.name === "status" },
|
|
46
52
|
}));
|
|
@@ -119,6 +125,13 @@ export function createMcpRuntime(options: McpRuntimeOptions = {}) {
|
|
|
119
125
|
if (!Object.hasOwn(args, "verdict")) throw new Error("verdict is required");
|
|
120
126
|
return runtime.submitReview(taskId, args.verdict);
|
|
121
127
|
}
|
|
128
|
+
if (name === "resolve_finding") {
|
|
129
|
+
// Structural only. Which findings may be resolved, and when, stays
|
|
130
|
+
// the reducer's decision; duplicating it here would create a second
|
|
131
|
+
// authority that could drift from the Kernel.
|
|
132
|
+
if (typeof args.finding_id !== "string" || !args.finding_id) throw new Error("finding_id is required");
|
|
133
|
+
return runtime.resolveFinding(taskId, args.finding_id);
|
|
134
|
+
}
|
|
122
135
|
if (name === "request_authorization" || name === "approve_breaking_intent_revision" || name === "stop" || name === "repair_authority_state") {
|
|
123
136
|
return runtime.authorize(taskId, name, toolMeta, args);
|
|
124
137
|
}
|
|
@@ -248,19 +248,6 @@ function runInspect(root: string): KernelExecution {
|
|
|
248
248
|
),
|
|
249
249
|
};
|
|
250
250
|
}
|
|
251
|
-
let declared: TaskRisk;
|
|
252
|
-
let intent: TaskIntentV1;
|
|
253
|
-
try {
|
|
254
|
-
const raw = JSON.parse(
|
|
255
|
-
readSecureProjectFile(root, `docs/plans/${claim.task_id}.intent.json`),
|
|
256
|
-
) as { risk?: unknown };
|
|
257
|
-
if (raw.risk !== "routine" && raw.risk !== "material" && raw.risk !== "critical")
|
|
258
|
-
throw new Error(`intent.risk is unreadable for ${claim.task_id}`);
|
|
259
|
-
declared = raw.risk;
|
|
260
|
-
intent = parseTaskIntentV1(raw);
|
|
261
|
-
} catch (error) {
|
|
262
|
-
return sourceFailure("inspect", error);
|
|
263
|
-
}
|
|
264
251
|
let recordRead: ReturnType<typeof readTaskRecordRaw>;
|
|
265
252
|
try {
|
|
266
253
|
recordRead = readTaskRecordRaw(root, claim.task_id);
|
|
@@ -285,6 +272,21 @@ function runInspect(root: string): KernelExecution {
|
|
|
285
272
|
};
|
|
286
273
|
}
|
|
287
274
|
const record = recordRead.record;
|
|
275
|
+
let declared: TaskRisk;
|
|
276
|
+
let intent: TaskIntentV1;
|
|
277
|
+
try {
|
|
278
|
+
const raw = JSON.parse(
|
|
279
|
+
readSecureProjectFile(root, record.intent_ref.path),
|
|
280
|
+
) as { risk?: unknown };
|
|
281
|
+
if (raw.risk !== "routine" && raw.risk !== "material" && raw.risk !== "critical")
|
|
282
|
+
throw new Error(`intent.risk is unreadable for ${claim.task_id}`);
|
|
283
|
+
declared = raw.risk;
|
|
284
|
+
intent = parseTaskIntentV1(raw);
|
|
285
|
+
if (canonicalIntentHash(intent) !== record.intent_ref.content_hash)
|
|
286
|
+
throw new Error("TaskIntent sidecar does not match TaskRecord content hash");
|
|
287
|
+
} catch (error) {
|
|
288
|
+
return sourceFailure("inspect", error);
|
|
289
|
+
}
|
|
288
290
|
const workspaceState = readWorkspaceStateRaw(root);
|
|
289
291
|
let identity;
|
|
290
292
|
try {
|