immune-brain 3.6.5 → 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 +69 -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 +60 -21
- package/plugins/immune-brain/.pi-extension/imm-unattended-batch.ts +932 -0
- package/plugins/immune-brain/.pi-extension/pi-canary-interaction.ts +38 -10
- package/plugins/immune-brain/.pi-extension/runtime-stub.ts +200 -2
- package/plugins/immune-brain/dist/BASELINE.md +48 -15
- package/plugins/immune-brain/dist/claude/mcp-server.mjs +3415 -228
- 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 +45 -11
- package/plugins/immune-brain/dist/imm-planner.md +65 -28
- package/plugins/immune-brain/dist/imm-pr-fix.md +6 -2
- package/plugins/immune-brain/dist/role-prompts/code-review.md +9 -1
- 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 +101 -3
- package/plugins/immune-brain/runtime/assurance/verification.ts +13 -2
- 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/github_issue_tracker.ts +1018 -17
- package/plugins/immune-brain/runtime/kernel/canary_application.ts +16 -1
- package/plugins/immune-brain/runtime/kernel/completion.ts +19 -1
- package/plugins/immune-brain/runtime/kernel/reducer.ts +165 -12
- 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/prompts/executor.md +18 -10
- package/plugins/immune-brain/runtime/prompts/pr-fix.md +5 -2
- 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/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 +21 -6
- package/plugins/immune-brain/skills/imm-planner/SKILL.md +35 -8
- package/plugins/immune-brain/skills/imm-pr-fix/SKILL.md +17 -3
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
# Internal role: executor
|
|
2
2
|
|
|
3
|
-
You are the Immune-Brain Executor role inside Loop. Implement exactly
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
You are the Immune-Brain Executor role inside Loop. Implement exactly the
|
|
4
|
+
enrolled TaskIntent acceptance and `scope_hint` (or one accepted
|
|
5
|
+
same-boundary follow-up) in the current Parent conversation. Use workspace
|
|
6
|
+
tools only for the supplied target and keep every edit inside the
|
|
7
|
+
authoritative Scope. Do not discover or load a Pi Skill.
|
|
7
8
|
|
|
8
|
-
Before handoff,
|
|
9
|
-
|
|
10
|
-
action
|
|
9
|
+
Before handoff, run the permitted diagnostic checks and return commands and
|
|
10
|
+
outcomes to the Parent as structured diagnostic evidence. The read-only Loop
|
|
11
|
+
runtime action only constructs the dispatch envelope; it does not store
|
|
12
|
+
evidence or change task state. Preserve failed and blocked attempts. Do not
|
|
13
|
+
perform QA,
|
|
11
14
|
review, plan mutation, successor approval, Compounder work, or authority
|
|
12
15
|
writes. If the requested change needs scope expansion, stop and return an
|
|
13
16
|
`imm-planner` route with the concrete missing scope and verification reason.
|
|
@@ -22,6 +25,11 @@ Do not weaken tests or hide an incomplete result to make Verification pass.
|
|
|
22
25
|
Treat naming, function length, parameter count, nesting, and abstraction taste
|
|
23
26
|
as contextual signals, never as automatic failure thresholds.
|
|
24
27
|
|
|
25
|
-
Fix in-scope integrity defects before Verification.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
Fix in-scope integrity defects before Verification. Autonomously diagnose,
|
|
29
|
+
repair, and rerun failing ordinary local checks within the authorized scope;
|
|
30
|
+
do not stop for a repair round that stays inside the TaskIntent boundary. If
|
|
31
|
+
fixing requires
|
|
32
|
+
behavior, scope, or authority beyond the enrolled TaskIntent, stop and route the
|
|
33
|
+
concrete reason to `imm-planner`. An unavailable or still-failing required
|
|
34
|
+
check is an explicit blocker: report it to the Parent; never present failed
|
|
35
|
+
verification as completion.
|
|
@@ -71,8 +71,11 @@ decision, stop and report it to the Parent.
|
|
|
71
71
|
|
|
72
72
|
## Boundary
|
|
73
73
|
|
|
74
|
-
Work only inside the supplied
|
|
75
|
-
|
|
74
|
+
Work only inside the supplied repair boundary: the current TaskIntent
|
|
75
|
+
acceptance and `scope_hint` when operating under one, otherwise the legacy
|
|
76
|
+
supplied Plan, `plan_id`, changed-file boundary, review feedback, and
|
|
77
|
+
verification commands from the delegation context. Do not create a second
|
|
78
|
+
Plan or TaskIntent, silently
|
|
76
79
|
widen scope, push to unrelated branches, merge, approve a successor, or invoke
|
|
77
80
|
another role. Do not discover or load a Pi Skill.
|
|
78
81
|
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
// evidence reservation.
|
|
8
8
|
|
|
9
9
|
import { createHash, randomUUID } from "node:crypto";
|
|
10
|
+
import type { FindingEvidence, TaskFinding } from "../kernel/types";
|
|
11
|
+
import { anchorForEvidence } from "../kernel/refutation";
|
|
10
12
|
import {
|
|
11
13
|
findingsDigest,
|
|
12
14
|
type FrozenRunner,
|
|
@@ -88,10 +90,40 @@ export interface AssuranceVerdict {
|
|
|
88
90
|
kind: "blocking" | "advisory";
|
|
89
91
|
acceptance_id: string | null;
|
|
90
92
|
summary: string;
|
|
93
|
+
/**
|
|
94
|
+
* Derived by `parseAssuranceVerdict` for Review-role rework findings. QA
|
|
95
|
+
* findings and pass verdicts never carry it.
|
|
96
|
+
*/
|
|
97
|
+
anchor?: string;
|
|
98
|
+
evidence?: FindingEvidence;
|
|
99
|
+
/** Digest this plugin's verdict parser derives for one finding. */
|
|
91
100
|
findings_digest: string;
|
|
92
101
|
}>;
|
|
93
102
|
}
|
|
94
103
|
|
|
104
|
+
/**
|
|
105
|
+
* The single Review-verdict → Kernel-finding mapping both Host adapters use.
|
|
106
|
+
* Keeping it here is what makes the two Hosts produce the same
|
|
107
|
+
* `findingsDigestV2` for the same verdict: the reviewer's anchor and evidence
|
|
108
|
+
* are forwarded unchanged into the capability binding and the Kernel action.
|
|
109
|
+
*/
|
|
110
|
+
export function reviewReworkFindings(verdict: AssuranceVerdict): TaskFinding[] {
|
|
111
|
+
if (verdict.decision !== "rework" || !verdict.findings?.length)
|
|
112
|
+
throw new Error("review rework findings require a rework verdict");
|
|
113
|
+
return verdict.findings.map((finding) => ({
|
|
114
|
+
id: finding.id,
|
|
115
|
+
kind: finding.kind,
|
|
116
|
+
status: "open" as const,
|
|
117
|
+
acceptance_id: finding.acceptance_id,
|
|
118
|
+
source: "review" as const,
|
|
119
|
+
review_round: null,
|
|
120
|
+
summary: finding.summary,
|
|
121
|
+
anchor: finding.anchor ?? null,
|
|
122
|
+
evidence: finding.evidence ?? null,
|
|
123
|
+
counterevidence: null,
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
|
|
95
127
|
export interface ReviewRevisionIdentity {
|
|
96
128
|
contract: "assurance_kernel/review_revision_identity/v1";
|
|
97
129
|
base_head: string;
|
|
@@ -314,12 +346,57 @@ export function buildReviewPrompt(snapshot: SnapshotDescriptor, evidencePath?: s
|
|
|
314
346
|
: `Intent revision ${snapshot.intent_revision} (hash ${snapshot.intent_content_hash}), diff ${snapshot.diff_hash}, review bundle ${snapshot.review_bundle_digest}, state ${snapshot.lifecycle}:${snapshot.artifact_state}.`,
|
|
315
347
|
"Acceptance assertions:", acceptance,
|
|
316
348
|
"Reserve the final turn for exactly one strict JSON verdict. Reply with ONLY that object, without markdown fences or commentary.",
|
|
349
|
+
`Every rework finding must carry machine-checkable provenance: evidence.trigger (the concrete inputs or state that reach the defect), a non-empty evidence.caller_chain (ordered repository paths or symbols), and evidence.violated {kind: "acceptance"|"security_boundary", ref}. The anchor is derived from that evidence; a finding without it is rejected and the correction must be resubmitted.`,
|
|
317
350
|
`PASS shape: {"contract":"assurance_kernel/assurance_verdict/v2","role":"review","task_id":"${snapshot.task_id}","snapshot_digest":"${digest}","decision":"pass","approval":{"kind":"review","authority_role":"reviewer","summary":"<one line>"}}`,
|
|
318
|
-
`REWORK shape: {"contract":"assurance_kernel/assurance_verdict/v2","role":"review","task_id":"${snapshot.task_id}","snapshot_digest":"${digest}","decision":"rework","findings":[{"id":"review-1","kind":"blocking|advisory","acceptance_id":"<id|null>","summary":"<one line>"}]}`,
|
|
351
|
+
`REWORK shape: {"contract":"assurance_kernel/assurance_verdict/v2","role":"review","task_id":"${snapshot.task_id}","snapshot_digest":"${digest}","decision":"rework","findings":[{"id":"review-1","kind":"blocking|advisory","acceptance_id":"<id|null>","summary":"<one line>","evidence":{"trigger":"<concrete inputs or state>","caller_chain":["<path-or-symbol>"],"violated":{"kind":"acceptance|security_boundary","ref":"<acceptance id or boundary>"}}}]}`,
|
|
319
352
|
`REWORK verdicts must omit the approval field entirely; do not emit "approval": null.`,
|
|
320
353
|
].join("\n");
|
|
321
354
|
}
|
|
322
355
|
|
|
356
|
+
function parseVerdictEvidence(value: unknown, index: number): FindingEvidence {
|
|
357
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
358
|
+
throw new Error(`finding ${index} evidence is required`);
|
|
359
|
+
const evidence = value as Record<string, unknown>;
|
|
360
|
+
const unknown = Object.keys(evidence).find(
|
|
361
|
+
(key) => !["trigger", "caller_chain", "violated"].includes(key),
|
|
362
|
+
);
|
|
363
|
+
if (unknown) throw new Error(`finding ${index} evidence has unknown field: ${unknown}`);
|
|
364
|
+
if (typeof evidence.trigger !== "string" || !evidence.trigger.trim())
|
|
365
|
+
throw new Error(`finding ${index} evidence.trigger must be a non-empty string`);
|
|
366
|
+
const chain = evidence.caller_chain;
|
|
367
|
+
if (
|
|
368
|
+
!Array.isArray(chain) ||
|
|
369
|
+
chain.length === 0 ||
|
|
370
|
+
chain.some((entry) => typeof entry !== "string" || !entry.trim())
|
|
371
|
+
)
|
|
372
|
+
throw new Error(
|
|
373
|
+
`finding ${index} evidence.caller_chain must be a non-empty list of paths or symbols`,
|
|
374
|
+
);
|
|
375
|
+
const violated = evidence.violated;
|
|
376
|
+
if (!violated || typeof violated !== "object" || Array.isArray(violated))
|
|
377
|
+
throw new Error(`finding ${index} evidence.violated is required`);
|
|
378
|
+
const violatedRecord = violated as Record<string, unknown>;
|
|
379
|
+
const unknownViolated = Object.keys(violatedRecord).find(
|
|
380
|
+
(key) => !["kind", "ref"].includes(key),
|
|
381
|
+
);
|
|
382
|
+
if (unknownViolated)
|
|
383
|
+
throw new Error(`finding ${index} evidence.violated has unknown field: ${unknownViolated}`);
|
|
384
|
+
if (violatedRecord.kind !== "acceptance" && violatedRecord.kind !== "security_boundary")
|
|
385
|
+
throw new Error(
|
|
386
|
+
`finding ${index} evidence.violated.kind must be acceptance or security_boundary`,
|
|
387
|
+
);
|
|
388
|
+
if (typeof violatedRecord.ref !== "string" || !violatedRecord.ref.trim())
|
|
389
|
+
throw new Error(`finding ${index} evidence.violated.ref must be a non-empty string`);
|
|
390
|
+
return {
|
|
391
|
+
trigger: evidence.trigger,
|
|
392
|
+
caller_chain: chain as string[],
|
|
393
|
+
violated: {
|
|
394
|
+
kind: violatedRecord.kind,
|
|
395
|
+
ref: violatedRecord.ref,
|
|
396
|
+
},
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
323
400
|
export function parseAssuranceVerdict(input: unknown, snapshot: SnapshotDescriptor): AssuranceVerdict {
|
|
324
401
|
let raw: Record<string, unknown>;
|
|
325
402
|
if (typeof input === "string") {
|
|
@@ -353,12 +430,33 @@ export function parseAssuranceVerdict(input: unknown, snapshot: SnapshotDescript
|
|
|
353
430
|
if (raw.approval !== undefined && raw.approval !== null) throw new Error("rework verdict must omit approval");
|
|
354
431
|
const findings = raw.findings.map((item, index) => {
|
|
355
432
|
const finding = item as Record<string, unknown>;
|
|
356
|
-
const
|
|
433
|
+
const allowedFindingKeys =
|
|
434
|
+
snapshot.role === "review"
|
|
435
|
+
? ["id", "kind", "acceptance_id", "summary", "evidence"]
|
|
436
|
+
: ["id", "kind", "acceptance_id", "summary"];
|
|
437
|
+
const unknownFinding = Object.keys(finding).find(
|
|
438
|
+
(key) => !allowedFindingKeys.includes(key),
|
|
439
|
+
);
|
|
357
440
|
if (unknownFinding) throw new Error(`finding ${index} has unknown field: ${unknownFinding}`);
|
|
358
441
|
if (typeof finding.id !== "string" || !finding.id.trim() || (finding.kind !== "blocking" && finding.kind !== "advisory") || (finding.acceptance_id !== null && typeof finding.acceptance_id !== "string") || typeof finding.summary !== "string" || !finding.summary.trim()) throw new Error(`finding ${index} is invalid`);
|
|
359
442
|
const id = `review-${snapshotDigest(snapshot).slice(7, 19)}-${index + 1}-${finding.id.replace(/[^A-Za-z0-9._-]/g, "-").slice(0, 48)}`;
|
|
360
443
|
const normalized = { id, kind: finding.kind as "blocking" | "advisory", acceptance_id: finding.acceptance_id as string | null, summary: finding.summary as string };
|
|
361
|
-
|
|
444
|
+
if (snapshot.role !== "review")
|
|
445
|
+
return { ...normalized, findings_digest: findingsDigest([normalized]) };
|
|
446
|
+
// Review findings must carry evidence; the anchor is derived here so a
|
|
447
|
+
// capability minted for one anchor set cannot be spent on another and
|
|
448
|
+
// the reviewer can never assert the anchor directly.
|
|
449
|
+
const evidence = parseVerdictEvidence(finding.evidence, index);
|
|
450
|
+
// The digest that decides refutation inheritance is derived here so the
|
|
451
|
+
// reviewer can never assert the anchor directly; the Kernel recomputes it
|
|
452
|
+
// from the same evidence before admitting the finding.
|
|
453
|
+
const anchor = anchorForEvidence(evidence);
|
|
454
|
+
return {
|
|
455
|
+
...normalized,
|
|
456
|
+
anchor,
|
|
457
|
+
evidence,
|
|
458
|
+
findings_digest: findingsDigest([normalized]),
|
|
459
|
+
};
|
|
362
460
|
});
|
|
363
461
|
return { contract: "assurance_kernel/assurance_verdict/v2", role: snapshot.role, task_id: snapshot.task_id, snapshot_digest: snapshotDigest(snapshot), decision: "rework", findings };
|
|
364
462
|
}
|
|
@@ -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
|
}
|
|
@@ -5,6 +5,7 @@ export const PRIVILEGED_OPERATIONS = [
|
|
|
5
5
|
"request_authorization",
|
|
6
6
|
"approve_breaking_intent_revision",
|
|
7
7
|
"stop",
|
|
8
|
+
"start_unattended_batch",
|
|
8
9
|
] as const;
|
|
9
10
|
|
|
10
11
|
export type PrivilegedOperation = (typeof PRIVILEGED_OPERATIONS)[number];
|
|
@@ -66,12 +67,22 @@ export function evaluateNativeGate(input: NativeGateInput): NativeGateResult {
|
|
|
66
67
|
|
|
67
68
|
export interface NativeConfirmationInput {
|
|
68
69
|
operation: PrivilegedOperation;
|
|
69
|
-
taskId
|
|
70
|
+
taskId?: string;
|
|
71
|
+
initiativeSlug?: string;
|
|
70
72
|
toolCallId: string;
|
|
71
73
|
risk?: string;
|
|
72
74
|
intentRevision?: number;
|
|
73
75
|
intentContentHash?: string;
|
|
74
76
|
bindingDigest?: string;
|
|
77
|
+
planDigest?: string;
|
|
78
|
+
batchDetails?: {
|
|
79
|
+
initiative_slug: string;
|
|
80
|
+
batch_branch: string;
|
|
81
|
+
children: Array<{ task_id: string; slice_id: string; risk?: string }>;
|
|
82
|
+
excluded: Array<{ task_id: string; slice_id: string; reason: string }>;
|
|
83
|
+
budget: { max_children: number; deadline_at: string; qa_failure_limit: number };
|
|
84
|
+
expires_at: string;
|
|
85
|
+
};
|
|
75
86
|
signal?: AbortSignal;
|
|
76
87
|
}
|
|
77
88
|
|
|
@@ -87,13 +98,15 @@ export function confirmationRef(input: {
|
|
|
87
98
|
toolCallId: string;
|
|
88
99
|
requestId: string;
|
|
89
100
|
operation: string;
|
|
90
|
-
taskId
|
|
101
|
+
taskId?: string;
|
|
102
|
+
initiativeSlug?: string;
|
|
91
103
|
intentRevision?: number;
|
|
92
104
|
intentContentHash?: string;
|
|
93
105
|
bindingDigest?: string;
|
|
106
|
+
planDigest?: string;
|
|
94
107
|
}): string {
|
|
95
108
|
return `claude-confirm-${createHash("sha256")
|
|
96
|
-
.update(`${input.connectionId}\0${input.toolCallId}\0${input.requestId}\0${input.operation}\0${input.taskId}\0${input.intentRevision ?? ""}\0${input.intentContentHash ?? ""}\0${input.bindingDigest ?? ""}`)
|
|
109
|
+
.update(`${input.connectionId}\0${input.toolCallId}\0${input.requestId}\0${input.operation}\0${input.taskId ?? ""}\0${input.initiativeSlug ?? ""}\0${input.intentRevision ?? ""}\0${input.intentContentHash ?? ""}\0${input.bindingDigest ?? ""}\0${input.planDigest ?? ""}`)
|
|
97
110
|
.digest("hex")
|
|
98
111
|
.slice(0, 16)}`;
|
|
99
112
|
}
|