immune-brain 5.1.2 → 5.2.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/README.md +13 -6
- package/README.zh-CN.md +12 -5
- package/package.json +1 -1
- package/plugins/immune-brain/.claude-plugin/plugin.json +1 -1
- package/plugins/immune-brain/.pi-extension/imm-canary-enroll.ts +14 -2
- package/plugins/immune-brain/.pi-extension/imm-canary-work.ts +73 -0
- package/plugins/immune-brain/.pi-extension/imm-unattended-batch.ts +15 -2
- package/plugins/immune-brain/agents/immune-brain-reviewer.md +5 -0
- package/plugins/immune-brain/dist/BASELINE.md +1 -1
- package/plugins/immune-brain/dist/claude/mcp-server.mjs +425 -140
- package/plugins/immune-brain/dist/imm-brainstorm.md +2 -2
- package/plugins/immune-brain/dist/imm-doc-prune.md +2 -1
- package/plugins/immune-brain/dist/imm-planner.md +26 -33
- package/plugins/immune-brain/dist/imm-run.md +14 -3
- package/plugins/immune-brain/dist/imm-test-prune.md +233 -0
- package/plugins/immune-brain/dist/registry.yaml +9 -0
- package/plugins/immune-brain/dist/role-prompts/code-review.md +5 -0
- package/plugins/immune-brain/dist/role-prompts/executor.md +8 -0
- package/plugins/immune-brain/hooks/hooks.json +10 -1
- package/plugins/immune-brain/runtime/assurance/alias_file.ts +24 -0
- package/plugins/immune-brain/runtime/assurance/phase_entry.ts +143 -0
- package/plugins/immune-brain/runtime/claude/interaction.ts +5 -1
- package/plugins/immune-brain/runtime/claude/lane_guard.ts +180 -3
- package/plugins/immune-brain/runtime/claude/mcp_server.ts +21 -3
- package/plugins/immune-brain/runtime/claude/review_host.ts +6 -6
- package/plugins/immune-brain/runtime/plugin_version.ts +1 -1
- package/plugins/immune-brain/runtime/prompts/code-review.md +5 -0
- package/plugins/immune-brain/runtime/prompts/executor.md +8 -0
- package/plugins/immune-brain/skills/BASELINE.md +1 -1
- package/plugins/immune-brain/skills/imm-brainstorm/SKILL.md +2 -1
- package/plugins/immune-brain/skills/imm-planner/SKILL.md +5 -5
- package/plugins/immune-brain/skills/imm-run/SKILL.md +2 -1
- package/plugins/immune-brain/skills/imm-test-prune/SKILL.md +28 -0
- package/plugins/immune-brain/skills/registry.yaml +9 -0
|
@@ -39,10 +39,122 @@ function probeHost(env = process.env, platform = process.platform, hostVersion)
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
// plugins/immune-brain/runtime/plugin_version.ts
|
|
42
|
-
var PLUGIN_VERSION = "5.
|
|
42
|
+
var PLUGIN_VERSION = "5.2.0";
|
|
43
43
|
|
|
44
44
|
// plugins/immune-brain/runtime/claude/interaction.ts
|
|
45
45
|
import { createHash, randomUUID } from "node:crypto";
|
|
46
|
+
|
|
47
|
+
// plugins/immune-brain/runtime/assurance/phase_entry.ts
|
|
48
|
+
var RUN_ENTRY_REQUIRED = "run_entry_required";
|
|
49
|
+
var PHASE_ENTRY_REQUIRED = "phase_entry_required";
|
|
50
|
+
var PHASE_ENTRY_RECOVERY = {
|
|
51
|
+
[RUN_ENTRY_REQUIRED]: "ask the user to enter imm-run in the current Host",
|
|
52
|
+
[PHASE_ENTRY_REQUIRED]: "ask the user to enter the Skill themselves in the current Host"
|
|
53
|
+
};
|
|
54
|
+
var PACKAGED = {
|
|
55
|
+
"imm-planner": "plan",
|
|
56
|
+
"imm-brainstorm": "plan",
|
|
57
|
+
"imm-run": "run"
|
|
58
|
+
};
|
|
59
|
+
var ALIAS_TARGETS = { ...PACKAGED, "imm-loop": "run" };
|
|
60
|
+
var PREFIXES = ["immune-brain:", "skill:"];
|
|
61
|
+
var ALIAS_NAME = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
|
|
62
|
+
var SKILL_MENTION = /(?<![A-Za-z0-9_-])imm-(?:planner|brainstorm|run|loop)(?![A-Za-z0-9_-])/g;
|
|
63
|
+
var EXPANDED = /^<command-message>[^<]*<\/command-message>\s*<command-name>\/([^\s<]+)<\/command-name>/;
|
|
64
|
+
var RAW = /^\/(\S+)/;
|
|
65
|
+
function stripPrefix(name) {
|
|
66
|
+
for (const prefix of PREFIXES)
|
|
67
|
+
if (name.startsWith(prefix))
|
|
68
|
+
return name.slice(prefix.length);
|
|
69
|
+
return name;
|
|
70
|
+
}
|
|
71
|
+
function packagedPhaseSkill(name) {
|
|
72
|
+
const trimmed = name.trim();
|
|
73
|
+
const bare = stripPrefix(trimmed.startsWith("/") ? trimmed.slice(1) : trimmed);
|
|
74
|
+
return Object.hasOwn(PACKAGED, bare) ? bare : null;
|
|
75
|
+
}
|
|
76
|
+
function recognizePhaseEntry(text, readAlias) {
|
|
77
|
+
const command = (RAW.exec(text) ?? EXPANDED.exec(text))?.[1];
|
|
78
|
+
if (!command)
|
|
79
|
+
return null;
|
|
80
|
+
const packaged = packagedPhaseSkill(command);
|
|
81
|
+
if (packaged)
|
|
82
|
+
return PACKAGED[packaged];
|
|
83
|
+
if (!ALIAS_NAME.test(command))
|
|
84
|
+
return null;
|
|
85
|
+
let body;
|
|
86
|
+
try {
|
|
87
|
+
body = readAlias(command);
|
|
88
|
+
} catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
if (!body)
|
|
92
|
+
return null;
|
|
93
|
+
const targets = new Set(body.match(SKILL_MENTION) ?? []);
|
|
94
|
+
if (targets.size !== 1)
|
|
95
|
+
return null;
|
|
96
|
+
return ALIAS_TARGETS[[...targets][0]] ?? null;
|
|
97
|
+
}
|
|
98
|
+
var NO_PHASE_ENTRY = { prompt_seq: 0, run_live: false, run_prompt_seq: 0, plan_prompt_seq: 0 };
|
|
99
|
+
function applyPrompt(state, entry) {
|
|
100
|
+
const prompt_seq = state.prompt_seq + 1;
|
|
101
|
+
if (entry === "run")
|
|
102
|
+
return { ...state, prompt_seq, run_live: true, run_prompt_seq: prompt_seq };
|
|
103
|
+
if (entry === "plan")
|
|
104
|
+
return { prompt_seq, run_live: false, run_prompt_seq: 0, plan_prompt_seq: prompt_seq };
|
|
105
|
+
return { ...state, prompt_seq };
|
|
106
|
+
}
|
|
107
|
+
function phaseEntryLogLine(entry) {
|
|
108
|
+
return `${JSON.stringify({ v: 1, entry })}
|
|
109
|
+
`;
|
|
110
|
+
}
|
|
111
|
+
function reducePhaseEntryLog(text) {
|
|
112
|
+
let state = NO_PHASE_ENTRY;
|
|
113
|
+
for (const line of text.split(`
|
|
114
|
+
`)) {
|
|
115
|
+
if (!line)
|
|
116
|
+
continue;
|
|
117
|
+
let record;
|
|
118
|
+
try {
|
|
119
|
+
record = JSON.parse(line);
|
|
120
|
+
} catch {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
if (!record || typeof record !== "object")
|
|
124
|
+
return null;
|
|
125
|
+
const { v, entry } = record;
|
|
126
|
+
if (v !== 1 || entry !== null && entry !== "plan" && entry !== "run")
|
|
127
|
+
return null;
|
|
128
|
+
state = applyPrompt(state, entry);
|
|
129
|
+
}
|
|
130
|
+
return state;
|
|
131
|
+
}
|
|
132
|
+
function refuse(reason_code, detail) {
|
|
133
|
+
return { ok: false, reason_code, detail, recovery_action: PHASE_ENTRY_RECOVERY[reason_code] };
|
|
134
|
+
}
|
|
135
|
+
function admitPhaseOperation(operation, state, context) {
|
|
136
|
+
if (operation.kind === "tool") {
|
|
137
|
+
if (state?.run_live)
|
|
138
|
+
return { ok: true };
|
|
139
|
+
return refuse(RUN_ENTRY_REQUIRED, `${operation.name} needs a literal-user imm-run entry in this session`);
|
|
140
|
+
}
|
|
141
|
+
const latest = state !== null && state.prompt_seq > 0 ? state.prompt_seq : -1;
|
|
142
|
+
if (operation.skill === "imm-run") {
|
|
143
|
+
if (state?.run_live && state.run_prompt_seq === latest)
|
|
144
|
+
return { ok: true };
|
|
145
|
+
return refuse(PHASE_ENTRY_REQUIRED, "imm-run is entered by the user, not by a model Skill call");
|
|
146
|
+
}
|
|
147
|
+
if (state && state.plan_prompt_seq === latest)
|
|
148
|
+
return { ok: true };
|
|
149
|
+
if (operation.skill === "imm-planner" && state?.run_live && context.activeClaim)
|
|
150
|
+
return { ok: true };
|
|
151
|
+
return refuse(PHASE_ENTRY_REQUIRED, `${operation.skill} is entered by the user, not by a model Skill call`);
|
|
152
|
+
}
|
|
153
|
+
function phaseRefusalMessage(refusal) {
|
|
154
|
+
return `${refusal.reason_code}: ${refusal.detail}; recovery: ${refusal.recovery_action}`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// plugins/immune-brain/runtime/claude/interaction.ts
|
|
46
158
|
var PRIVILEGED_OPERATIONS = [
|
|
47
159
|
"enroll",
|
|
48
160
|
"request_authorization",
|
|
@@ -57,7 +169,8 @@ var RECOVERY_ACTIONS = {
|
|
|
57
169
|
user_cancelled: "wait for a fresh literal-user request",
|
|
58
170
|
correlation_missing: "retry through a fresh native gate in the current Host",
|
|
59
171
|
unsupported_host: "upgrade to a supported Claude Code version and retry in the current Host",
|
|
60
|
-
workspace_changed: "review the current workspace and retry through a fresh native gate"
|
|
172
|
+
workspace_changed: "review the current workspace and retry through a fresh native gate",
|
|
173
|
+
...PHASE_ENTRY_RECOVERY
|
|
61
174
|
};
|
|
62
175
|
|
|
63
176
|
class NativeAuthorityError extends Error {
|
|
@@ -684,6 +797,11 @@ function parseHookStdin(raw) {
|
|
|
684
797
|
return null;
|
|
685
798
|
}
|
|
686
799
|
|
|
800
|
+
// plugins/immune-brain/runtime/claude/lane_guard.ts
|
|
801
|
+
import { lstatSync as lstatSync10, rmSync as rmSync5 } from "node:fs";
|
|
802
|
+
import { homedir, tmpdir as tmpdir2 } from "node:os";
|
|
803
|
+
import { join as join9 } from "node:path";
|
|
804
|
+
|
|
687
805
|
// plugins/immune-brain/runtime/unattended/lane_workspace.ts
|
|
688
806
|
import { spawnSync } from "node:child_process";
|
|
689
807
|
import { realpathSync as realpathSync2 } from "node:fs";
|
|
@@ -6350,7 +6468,7 @@ var oid = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/;
|
|
|
6350
6468
|
var id = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
6351
6469
|
var batchAuthor = process.env.GIT_AUTHOR_NAME || "Immune-Brain Batch";
|
|
6352
6470
|
var decode = (bytes) => new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
6353
|
-
function
|
|
6471
|
+
function refuse2() {
|
|
6354
6472
|
throw new Error("plan_digest mismatch: batch plan reconfirmation is not eligible");
|
|
6355
6473
|
}
|
|
6356
6474
|
function matchesLocalProof(localJson, exported) {
|
|
@@ -6359,7 +6477,7 @@ function matchesLocalProof(localJson, exported) {
|
|
|
6359
6477
|
function git2(root, args) {
|
|
6360
6478
|
const r = spawnSync6("git", ["-C", root, ...args], { encoding: "buffer", maxBuffer: 262144, env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" } });
|
|
6361
6479
|
if (r.status !== 0)
|
|
6362
|
-
|
|
6480
|
+
refuse2();
|
|
6363
6481
|
return decode(r.stdout);
|
|
6364
6482
|
}
|
|
6365
6483
|
function ownUnpersistedBatchHead(root, record, head) {
|
|
@@ -6410,23 +6528,23 @@ function ownUnpersistedBatchHead(root, record, head) {
|
|
|
6410
6528
|
var confirmations = new Map;
|
|
6411
6529
|
function retainReconfirmation(nonce, snapshot) {
|
|
6412
6530
|
if (confirmations.has(nonce))
|
|
6413
|
-
|
|
6531
|
+
refuse2();
|
|
6414
6532
|
confirmations.set(nonce, snapshot);
|
|
6415
6533
|
}
|
|
6416
6534
|
function takeReconfirmation(nonce) {
|
|
6417
6535
|
const found = confirmations.get(nonce);
|
|
6418
6536
|
confirmations.delete(nonce);
|
|
6419
6537
|
if (!found)
|
|
6420
|
-
|
|
6538
|
+
refuse2();
|
|
6421
6539
|
return found;
|
|
6422
6540
|
}
|
|
6423
6541
|
async function captureBatchReconfirmation(root, record, children) {
|
|
6424
6542
|
if (!id.test(record.batch_id) || !oid.test(record.base_head) || isTerminalBatchState(record.batch_state) || record.commits.length || record.children.some((c) => c.commit !== null || c.state === "committed") || record.children.length !== children.length || !children.length)
|
|
6425
|
-
|
|
6543
|
+
refuse2();
|
|
6426
6544
|
const assertNoCommitEvidence = () => {
|
|
6427
6545
|
for (const child of record.children) {
|
|
6428
6546
|
if (!id.test(child.task_id))
|
|
6429
|
-
|
|
6547
|
+
refuse2();
|
|
6430
6548
|
const path = batchCommitEvidencePath(record.batch_id, child.task_id);
|
|
6431
6549
|
let missing = false;
|
|
6432
6550
|
try {
|
|
@@ -6435,17 +6553,17 @@ async function captureBatchReconfirmation(root, record, children) {
|
|
|
6435
6553
|
if (error instanceof Error && error.message === `source_missing: ${path}`)
|
|
6436
6554
|
missing = true;
|
|
6437
6555
|
else
|
|
6438
|
-
|
|
6556
|
+
refuse2();
|
|
6439
6557
|
}
|
|
6440
6558
|
if (!missing)
|
|
6441
|
-
|
|
6559
|
+
refuse2();
|
|
6442
6560
|
}
|
|
6443
6561
|
};
|
|
6444
6562
|
assertNoCommitEvidence();
|
|
6445
6563
|
const statePath = batchStatePath(record.batch_id);
|
|
6446
6564
|
const stateBytes = readSecureProjectBytes(root, statePath);
|
|
6447
6565
|
if (JSON.stringify(JSON.parse(decode(stateBytes))) !== JSON.stringify(record))
|
|
6448
|
-
|
|
6566
|
+
refuse2();
|
|
6449
6567
|
const files = new Map;
|
|
6450
6568
|
const capture = (path) => {
|
|
6451
6569
|
const bytes = readSecureProjectBytes(root, path);
|
|
@@ -6457,14 +6575,14 @@ async function captureBatchReconfirmation(root, record, children) {
|
|
|
6457
6575
|
let scope = null;
|
|
6458
6576
|
const workspace = readWorkspaceStateRaw(root);
|
|
6459
6577
|
if (workspace.state.current_working !== null)
|
|
6460
|
-
|
|
6578
|
+
refuse2();
|
|
6461
6579
|
for (const [index, child] of children.entries()) {
|
|
6462
6580
|
const previous = record.children[index];
|
|
6463
6581
|
if (!id.test(child.task_id) || child.task_id !== previous.task_id || child.slice_id !== previous.slice_id || JSON.stringify(child.blocked_by) !== JSON.stringify(previous.blocked_by) || child.intent_path !== `docs/plans/${child.task_id}.intent.json`)
|
|
6464
|
-
|
|
6582
|
+
refuse2();
|
|
6465
6583
|
const old = parseTaskIntentV1(JSON.parse(git2(root, ["show", `${record.base_head}:${child.intent_path}`])));
|
|
6466
6584
|
if (old.task_id !== child.task_id)
|
|
6467
|
-
|
|
6585
|
+
refuse2();
|
|
6468
6586
|
const oldHash = canonicalIntentHash(old);
|
|
6469
6587
|
oldChildren.push({ task_id: child.task_id, blocked_by: [...child.blocked_by], intent_path: child.intent_path, intent_revision: old.revision, intent_content_hash: oldHash });
|
|
6470
6588
|
const currentBytes = capture(child.intent_path);
|
|
@@ -6472,46 +6590,46 @@ async function captureBatchReconfirmation(root, record, children) {
|
|
|
6472
6590
|
const currentHash = canonicalIntentHash(current);
|
|
6473
6591
|
const tracked = readTaskIntent(root, child.task_id, child.intent_path);
|
|
6474
6592
|
if (currentHash !== tracked.content_hash || currentHash !== child.intent_content_hash || current.revision !== child.intent_revision)
|
|
6475
|
-
|
|
6593
|
+
refuse2();
|
|
6476
6594
|
if (currentHash === oldHash)
|
|
6477
6595
|
continue;
|
|
6478
6596
|
if (scope !== null || previous.state === "pending" || previous.state === "skipped_blocked" || current.revision <= old.revision || current.goal !== old.goal || current.owner !== old.owner)
|
|
6479
|
-
|
|
6597
|
+
refuse2();
|
|
6480
6598
|
const settled = readSettledRunEvidence(root, child.task_id);
|
|
6481
6599
|
if (!settled || settled.lifecycle !== "done")
|
|
6482
|
-
|
|
6600
|
+
refuse2();
|
|
6483
6601
|
const run = settled.run_id;
|
|
6484
6602
|
if (!id.test(run) || settled.enrollment_event_id !== enrollmentEventIdFor(child.task_id, settled.created_at) || Date.parse(settled.created_at) > Date.parse(record.updated_at))
|
|
6485
|
-
|
|
6603
|
+
refuse2();
|
|
6486
6604
|
const pair = readAuditTaskPair(root, child.task_id, run);
|
|
6487
6605
|
if (!pair || pair.record.contract !== "assurance_kernel/task_record/v4")
|
|
6488
|
-
|
|
6606
|
+
refuse2();
|
|
6489
6607
|
const r = pair.record;
|
|
6490
6608
|
if (r.lifecycle !== "done" || r.intent_ref.content_hash !== currentHash || canonicalIntentHash(r.intent_snapshot) !== currentHash || r.git_base_head !== record.base_head || pair.proof.terminal_lifecycle !== "done")
|
|
6491
|
-
|
|
6609
|
+
refuse2();
|
|
6492
6610
|
const terminal = r.history.at(-1);
|
|
6493
6611
|
if (terminal?.type !== "complete" || terminal.from_state !== "active:frozen" || terminal.to_state !== "done:frozen" || terminal.id !== pair.proof.terminal_event_id)
|
|
6494
|
-
|
|
6612
|
+
refuse2();
|
|
6495
6613
|
const recordPath = auditRunRecordPath(child.task_id, run);
|
|
6496
6614
|
const proofPath = auditRunTerminalProofPath(child.task_id, run);
|
|
6497
6615
|
if (!capture(recordPath).equals(Buffer.from(settled.record_json)))
|
|
6498
|
-
|
|
6616
|
+
refuse2();
|
|
6499
6617
|
const capturedProof = JSON.parse(decode(capture(proofPath)));
|
|
6500
6618
|
if (!matchesLocalProof(settled.proof_json, capturedProof) || !matchesLocalProof(settled.proof_json, pair.proof))
|
|
6501
|
-
|
|
6619
|
+
refuse2();
|
|
6502
6620
|
const identity = taskDeliveryIdentity(root, r);
|
|
6503
6621
|
const projection = await projectAssurance(root, child.task_id, (cwd, task) => {
|
|
6504
6622
|
if (task.contract !== "assurance_kernel/task_record/v4" || !task.git_base_head)
|
|
6505
|
-
|
|
6623
|
+
refuse2();
|
|
6506
6624
|
return taskDeliveryIdentity(cwd, task);
|
|
6507
6625
|
});
|
|
6508
6626
|
if (projection.error || projection.claim || projection.projection.run_id !== run || projection.projection.lifecycle !== "done" || !projection.projection.completion_ready || projection.projection.intent_content_hash !== currentHash || projection.projection.diff_hash !== identity.diff_hash || !projection.projection.fresh_approval_kinds.includes("qa") || projection.projection.risk !== "routine" && !projection.projection.fresh_approval_kinds.includes("review"))
|
|
6509
|
-
|
|
6627
|
+
refuse2();
|
|
6510
6628
|
scope = r.intent_snapshot.scope_hint;
|
|
6511
6629
|
changed.push({ task: child.task_id, run, identity: JSON.stringify(identity), settled: JSON.stringify(settled) });
|
|
6512
6630
|
}
|
|
6513
6631
|
if (computeBatchPlanDigest(oldChildren) !== record.plan_digest || changed.length !== 1 || scope === null)
|
|
6514
|
-
|
|
6632
|
+
refuse2();
|
|
6515
6633
|
const confirmedScope = scope;
|
|
6516
6634
|
const owner = changed[0];
|
|
6517
6635
|
const status = () => git2(root, ["status", "--porcelain=v1", "-z", "--untracked-files=all", "--ignore-submodules=none"]);
|
|
@@ -6520,42 +6638,42 @@ async function captureBatchReconfirmation(root, record, children) {
|
|
|
6520
6638
|
for (const entry of entries) {
|
|
6521
6639
|
const code = entry.slice(0, 2), path = entry.slice(3);
|
|
6522
6640
|
if (code[1] !== " " || !["A", "M", "D"].includes(code[0]) || !path.startsWith(`${auditRunDirPath(owner.task, owner.run)}/`) && !confirmedScope.some((s) => pathMatchesScope(path, s)))
|
|
6523
|
-
|
|
6641
|
+
refuse2();
|
|
6524
6642
|
}
|
|
6525
6643
|
const flagsBytes = git2(root, ["ls-files", "-v"]);
|
|
6526
6644
|
const flags = flagsBytes.split(`
|
|
6527
6645
|
`).filter(Boolean);
|
|
6528
6646
|
if (flags.some((line) => line[0] === "S" || line[0] !== line[0]?.toUpperCase()))
|
|
6529
|
-
|
|
6647
|
+
refuse2();
|
|
6530
6648
|
const head = () => git2(root, ["rev-parse", "HEAD"]).trim();
|
|
6531
6649
|
const branch = () => git2(root, ["symbolic-ref", "--short", "HEAD"]).trim();
|
|
6532
6650
|
if (head() !== record.base_head || branch() !== record.branch)
|
|
6533
|
-
|
|
6651
|
+
refuse2();
|
|
6534
6652
|
const assertDeliveryUnchanged = () => {
|
|
6535
6653
|
for (const c of changed) {
|
|
6536
6654
|
let settled;
|
|
6537
6655
|
try {
|
|
6538
6656
|
settled = readSettledRunEvidence(root, c.task);
|
|
6539
6657
|
} catch {
|
|
6540
|
-
|
|
6658
|
+
refuse2();
|
|
6541
6659
|
}
|
|
6542
6660
|
if (!settled || settled.run_id !== c.run || JSON.stringify(settled) !== c.settled)
|
|
6543
|
-
|
|
6661
|
+
refuse2();
|
|
6544
6662
|
const pair = readAuditTaskPair(root, c.task, c.run);
|
|
6545
6663
|
if (!pair || pair.record.contract !== "assurance_kernel/task_record/v4" || !pair.record.git_base_head)
|
|
6546
|
-
|
|
6664
|
+
refuse2();
|
|
6547
6665
|
const diff = taskDeliveryIdentity(root, pair.record);
|
|
6548
6666
|
if (JSON.stringify(diff) !== c.identity)
|
|
6549
|
-
|
|
6667
|
+
refuse2();
|
|
6550
6668
|
}
|
|
6551
6669
|
};
|
|
6552
6670
|
const assertUnchanged = () => {
|
|
6553
6671
|
assertNoCommitEvidence();
|
|
6554
6672
|
if (!readSecureProjectBytes(root, statePath).equals(stateBytes) || head() !== record.base_head || branch() !== record.branch || status() !== statusBytes || git2(root, ["ls-files", "-v"]) !== flagsBytes || readWorkspaceStateRaw(root).revision !== workspace.revision)
|
|
6555
|
-
|
|
6673
|
+
refuse2();
|
|
6556
6674
|
for (const [path, bytes] of files)
|
|
6557
6675
|
if (!readSecureProjectBytes(root, path).equals(bytes))
|
|
6558
|
-
|
|
6676
|
+
refuse2();
|
|
6559
6677
|
assertDeliveryUnchanged();
|
|
6560
6678
|
};
|
|
6561
6679
|
assertUnchanged();
|
|
@@ -9012,8 +9130,38 @@ async function activeTaskNotice(root) {
|
|
|
9012
9130
|
].join(" ");
|
|
9013
9131
|
}
|
|
9014
9132
|
|
|
9133
|
+
// plugins/immune-brain/runtime/assurance/alias_file.ts
|
|
9134
|
+
import { closeSync as closeSync7, constants as constants5, fstatSync as fstatSync5, openSync as openSync7, readSync as readSync2 } from "node:fs";
|
|
9135
|
+
var MAX_ALIAS_BYTES = 64 * 1024;
|
|
9136
|
+
function readAliasFile(path, maxBytes = MAX_ALIAS_BYTES) {
|
|
9137
|
+
let fd;
|
|
9138
|
+
try {
|
|
9139
|
+
fd = openSync7(path, constants5.O_RDONLY | constants5.O_NONBLOCK);
|
|
9140
|
+
} catch {
|
|
9141
|
+
return null;
|
|
9142
|
+
}
|
|
9143
|
+
try {
|
|
9144
|
+
const stat = fstatSync5(fd);
|
|
9145
|
+
if (!stat.isFile() || stat.size > maxBytes)
|
|
9146
|
+
return null;
|
|
9147
|
+
const buf = Buffer.alloc(maxBytes + 1);
|
|
9148
|
+
const read = readSync2(fd, buf, 0, buf.length, 0);
|
|
9149
|
+
return read > maxBytes ? null : buf.toString("utf8", 0, read);
|
|
9150
|
+
} catch {
|
|
9151
|
+
return null;
|
|
9152
|
+
} finally {
|
|
9153
|
+
closeSync7(fd);
|
|
9154
|
+
}
|
|
9155
|
+
}
|
|
9156
|
+
|
|
9015
9157
|
// plugins/immune-brain/runtime/claude/lane_guard.ts
|
|
9016
9158
|
var FILE_TOOLS = new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
|
|
9159
|
+
var GATED_MCP_TOOL = /^mcp__.*immune-brain.*__(enroll|start_unattended_batch)$/;
|
|
9160
|
+
function denyPreToolUse(reason) {
|
|
9161
|
+
return JSON.stringify({
|
|
9162
|
+
hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: reason }
|
|
9163
|
+
});
|
|
9164
|
+
}
|
|
9017
9165
|
function laneGuardHookOutput(payload) {
|
|
9018
9166
|
if (payload.hook_event_name !== "PreToolUse" || !FILE_TOOLS.has(String(payload.tool_name)))
|
|
9019
9167
|
return null;
|
|
@@ -9022,9 +9170,7 @@ function laneGuardHookOutput(payload) {
|
|
|
9022
9170
|
const refusal = laneWriteRefusal(cwd, input.file_path ?? input.notebook_path);
|
|
9023
9171
|
if (!refusal)
|
|
9024
9172
|
return null;
|
|
9025
|
-
return
|
|
9026
|
-
hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: refusal }
|
|
9027
|
-
});
|
|
9173
|
+
return denyPreToolUse(refusal);
|
|
9028
9174
|
}
|
|
9029
9175
|
async function activeTaskHookOutput(payload) {
|
|
9030
9176
|
if (payload.hook_event_name !== "UserPromptSubmit")
|
|
@@ -9035,12 +9181,141 @@ async function activeTaskHookOutput(payload) {
|
|
|
9035
9181
|
return null;
|
|
9036
9182
|
return JSON.stringify({ hookSpecificOutput: { hookEventName: "UserPromptSubmit", additionalContext: notice } });
|
|
9037
9183
|
}
|
|
9184
|
+
function entryLogPath(sessionId, root) {
|
|
9185
|
+
return join9(cacheDir(root), `${sessionHash(sessionId)}.entry`);
|
|
9186
|
+
}
|
|
9187
|
+
function admissionPath(toolUseId, root) {
|
|
9188
|
+
return join9(cacheDir(root), `${sessionHash(toolUseId)}.admit`);
|
|
9189
|
+
}
|
|
9190
|
+
function sessionIdOf(payload) {
|
|
9191
|
+
return typeof payload.session_id === "string" ? payload.session_id : "";
|
|
9192
|
+
}
|
|
9193
|
+
function cwdOf(payload) {
|
|
9194
|
+
return typeof payload.cwd === "string" && payload.cwd ? payload.cwd : process.cwd();
|
|
9195
|
+
}
|
|
9196
|
+
function claudeAliasReader(cwd, home) {
|
|
9197
|
+
return (name) => {
|
|
9198
|
+
for (const dir of [join9(cwd, ".claude", "commands"), join9(home, ".claude", "commands")]) {
|
|
9199
|
+
const body = readAliasFile(join9(dir, `${name}.md`));
|
|
9200
|
+
if (body !== null)
|
|
9201
|
+
return body;
|
|
9202
|
+
}
|
|
9203
|
+
return null;
|
|
9204
|
+
};
|
|
9205
|
+
}
|
|
9206
|
+
function readPhaseEntryState(sessionId, root = tmpdir2(), read = readPrivate) {
|
|
9207
|
+
if (!sessionId)
|
|
9208
|
+
return null;
|
|
9209
|
+
const path = entryLogPath(sessionId, root);
|
|
9210
|
+
try {
|
|
9211
|
+
lstatSync10(path);
|
|
9212
|
+
} catch (error) {
|
|
9213
|
+
return error.code === "ENOENT" ? NO_PHASE_ENTRY : null;
|
|
9214
|
+
}
|
|
9215
|
+
if (!ensurePrivateDir(cacheDir(root)))
|
|
9216
|
+
return null;
|
|
9217
|
+
let text;
|
|
9218
|
+
try {
|
|
9219
|
+
text = read(path);
|
|
9220
|
+
} catch {
|
|
9221
|
+
return null;
|
|
9222
|
+
}
|
|
9223
|
+
return text === undefined ? null : reducePhaseEntryLog(text);
|
|
9224
|
+
}
|
|
9225
|
+
function dropPrivate(path) {
|
|
9226
|
+
try {
|
|
9227
|
+
const stat = lstatSync10(path);
|
|
9228
|
+
if (ownedByUs(stat) && stat.isFile())
|
|
9229
|
+
rmSync5(path, { force: true });
|
|
9230
|
+
} catch {}
|
|
9231
|
+
}
|
|
9232
|
+
function recordPhaseEntryPrompt(payload, locations = {}) {
|
|
9233
|
+
if (payload.hook_event_name !== "UserPromptSubmit")
|
|
9234
|
+
return false;
|
|
9235
|
+
const sessionId = sessionIdOf(payload);
|
|
9236
|
+
if (!sessionId)
|
|
9237
|
+
return false;
|
|
9238
|
+
const root = locations.root ?? tmpdir2();
|
|
9239
|
+
const prompt = typeof payload.prompt === "string" ? payload.prompt : "";
|
|
9240
|
+
const entry = recognizePhaseEntry(prompt, claudeAliasReader(cwdOf(payload), locations.home ?? homedir()));
|
|
9241
|
+
const path = entryLogPath(sessionId, root);
|
|
9242
|
+
const recorded = (locations.append ?? appendPrivate)(path, cacheDir(root), phaseEntryLogLine(entry));
|
|
9243
|
+
if (!recorded)
|
|
9244
|
+
dropPrivate(path);
|
|
9245
|
+
return recorded;
|
|
9246
|
+
}
|
|
9247
|
+
function gatedOperation(payload) {
|
|
9248
|
+
const toolName = String(payload.tool_name ?? "");
|
|
9249
|
+
if (toolName === "Skill") {
|
|
9250
|
+
const input = payload.tool_input ?? {};
|
|
9251
|
+
const skill = typeof input.skill === "string" ? packagedPhaseSkill(input.skill) : null;
|
|
9252
|
+
return skill ? { kind: "skill", skill } : null;
|
|
9253
|
+
}
|
|
9254
|
+
const mcp = GATED_MCP_TOOL.exec(toolName);
|
|
9255
|
+
return mcp ? { kind: "tool", name: mcp[1] } : null;
|
|
9256
|
+
}
|
|
9257
|
+
function phaseEntryGateHookOutput(payload, locations = {}) {
|
|
9258
|
+
if (payload.hook_event_name !== "PreToolUse")
|
|
9259
|
+
return null;
|
|
9260
|
+
const operation = gatedOperation(payload);
|
|
9261
|
+
if (!operation)
|
|
9262
|
+
return null;
|
|
9263
|
+
const root = locations.root ?? tmpdir2();
|
|
9264
|
+
let state = null;
|
|
9265
|
+
try {
|
|
9266
|
+
state = readPhaseEntryState(sessionIdOf(payload), root, locations.read);
|
|
9267
|
+
} catch {}
|
|
9268
|
+
let activeClaim = false;
|
|
9269
|
+
try {
|
|
9270
|
+
activeClaim = readActiveClaimTaskId(cwdOf(payload)) !== null;
|
|
9271
|
+
} catch {}
|
|
9272
|
+
const admission = admitPhaseOperation(operation, state, { activeClaim });
|
|
9273
|
+
if (!admission.ok)
|
|
9274
|
+
return denyPreToolUse(phaseRefusalMessage(admission));
|
|
9275
|
+
if (operation.kind === "skill")
|
|
9276
|
+
return null;
|
|
9277
|
+
const toolUseId = typeof payload.tool_use_id === "string" ? payload.tool_use_id : "";
|
|
9278
|
+
let recorded = false;
|
|
9279
|
+
try {
|
|
9280
|
+
recorded = toolUseId !== "" && (locations.append ?? appendPrivate)(admissionPath(toolUseId, root), cacheDir(root), `${JSON.stringify({ v: 1, operation: operation.name })}
|
|
9281
|
+
`);
|
|
9282
|
+
} catch {}
|
|
9283
|
+
if (recorded)
|
|
9284
|
+
return null;
|
|
9285
|
+
return denyPreToolUse(`${RUN_ENTRY_REQUIRED}: the admission for this ${operation.name} call could not be recorded; recovery: ${PHASE_ENTRY_RECOVERY[RUN_ENTRY_REQUIRED]}`);
|
|
9286
|
+
}
|
|
9287
|
+
function consumePhaseAdmission(toolUseId, operation, root = tmpdir2()) {
|
|
9288
|
+
if (!toolUseId || !ensurePrivateDir(cacheDir(root)))
|
|
9289
|
+
return false;
|
|
9290
|
+
const path = admissionPath(toolUseId, root);
|
|
9291
|
+
let text;
|
|
9292
|
+
try {
|
|
9293
|
+
text = readPrivate(path);
|
|
9294
|
+
} catch {
|
|
9295
|
+
return false;
|
|
9296
|
+
}
|
|
9297
|
+
if (text === undefined)
|
|
9298
|
+
return false;
|
|
9299
|
+
dropPrivate(path);
|
|
9300
|
+
const lines = text.split(`
|
|
9301
|
+
`).filter(Boolean);
|
|
9302
|
+
if (lines.length === 0)
|
|
9303
|
+
return false;
|
|
9304
|
+
return lines.every((line) => {
|
|
9305
|
+
try {
|
|
9306
|
+
const record = JSON.parse(line);
|
|
9307
|
+
return record?.v === 1 && record.operation === operation;
|
|
9308
|
+
} catch {
|
|
9309
|
+
return false;
|
|
9310
|
+
}
|
|
9311
|
+
});
|
|
9312
|
+
}
|
|
9038
9313
|
|
|
9039
9314
|
// plugins/immune-brain/runtime/assurance/verdict_authority.ts
|
|
9040
9315
|
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
9041
9316
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
9042
9317
|
import { existsSync as existsSync9 } from "node:fs";
|
|
9043
|
-
import { join as
|
|
9318
|
+
import { join as join13, resolve as resolve12 } from "node:path";
|
|
9044
9319
|
|
|
9045
9320
|
// plugins/immune-brain/runtime/kernel/application.ts
|
|
9046
9321
|
function applyTaskAction(input) {
|
|
@@ -9698,15 +9973,15 @@ import { execFileSync as execFileSync2 } from "node:child_process";
|
|
|
9698
9973
|
import { createHash as createHash14 } from "node:crypto";
|
|
9699
9974
|
import {
|
|
9700
9975
|
mkdtempSync,
|
|
9701
|
-
rmSync as
|
|
9976
|
+
rmSync as rmSync6,
|
|
9702
9977
|
writeFileSync as writeFileSync6,
|
|
9703
9978
|
statSync as statSync3,
|
|
9704
9979
|
readFileSync as readFileSync10,
|
|
9705
9980
|
realpathSync as realpathSync8,
|
|
9706
9981
|
chmodSync
|
|
9707
9982
|
} from "node:fs";
|
|
9708
|
-
import { tmpdir as
|
|
9709
|
-
import { join as
|
|
9983
|
+
import { tmpdir as tmpdir3 } from "node:os";
|
|
9984
|
+
import { join as join10 } from "node:path";
|
|
9710
9985
|
var MAX_REVIEW_BUNDLE_BYTES = 2 * 1024 * 1024;
|
|
9711
9986
|
function bundleDigest(bundle) {
|
|
9712
9987
|
return `sha256:${createHash14("sha256").update(JSON.stringify(bundle)).digest("hex")}`;
|
|
@@ -9930,9 +10205,9 @@ function publishReviewRevision(root, snapshot, diffHash, taskId) {
|
|
|
9930
10205
|
throw new Error("review revision base has invalid identity");
|
|
9931
10206
|
if (!REVISION_DIFF_HASH.test(diffHash))
|
|
9932
10207
|
throw new Error("review revision diff hash has invalid identity");
|
|
9933
|
-
const indexDirectory = mkdtempSync(
|
|
10208
|
+
const indexDirectory = mkdtempSync(join10(tmpdir3(), "imm-review-index-"));
|
|
9934
10209
|
try {
|
|
9935
|
-
const indexFile =
|
|
10210
|
+
const indexFile = join10(indexDirectory, "index");
|
|
9936
10211
|
const env = { GIT_INDEX_FILE: indexFile };
|
|
9937
10212
|
gitEvidence(root, ["read-tree", snapshot.base_tree], env);
|
|
9938
10213
|
for (const [path, entry] of Object.entries(snapshot.changed_paths)) {
|
|
@@ -9982,7 +10257,7 @@ function publishReviewRevision(root, snapshot, diffHash, taskId) {
|
|
|
9982
10257
|
diff_hash: diffHash
|
|
9983
10258
|
};
|
|
9984
10259
|
} finally {
|
|
9985
|
-
|
|
10260
|
+
rmSync6(indexDirectory, { recursive: true, force: true });
|
|
9986
10261
|
}
|
|
9987
10262
|
}
|
|
9988
10263
|
function publishInput(root, input) {
|
|
@@ -10025,19 +10300,19 @@ function captureReviewManifest(root, input) {
|
|
|
10025
10300
|
return manifest;
|
|
10026
10301
|
}
|
|
10027
10302
|
function writeNativeReviewEvidence(payload) {
|
|
10028
|
-
const rawDirectory = mkdtempSync(
|
|
10303
|
+
const rawDirectory = mkdtempSync(join10(tmpdir3(), "imm-canary-native-review-"));
|
|
10029
10304
|
try {
|
|
10030
10305
|
const directory = realpathSync8(rawDirectory);
|
|
10031
10306
|
chmodSync(directory, 493);
|
|
10032
|
-
const path =
|
|
10307
|
+
const path = join10(directory, "evidence.json");
|
|
10033
10308
|
writeFileSync6(path, JSON.stringify(payload), { encoding: "utf8", mode: 420, flag: "wx" });
|
|
10034
10309
|
assertReviewArtifact(path);
|
|
10035
10310
|
return {
|
|
10036
10311
|
path,
|
|
10037
|
-
remove: () =>
|
|
10312
|
+
remove: () => rmSync6(directory, { recursive: true, force: true })
|
|
10038
10313
|
};
|
|
10039
10314
|
} catch (error) {
|
|
10040
|
-
|
|
10315
|
+
rmSync6(rawDirectory, { recursive: true, force: true });
|
|
10041
10316
|
throw error;
|
|
10042
10317
|
}
|
|
10043
10318
|
}
|
|
@@ -10062,8 +10337,8 @@ import { createHash as createHash17, randomUUID as randomUUID6 } from "node:cryp
|
|
|
10062
10337
|
// plugins/immune-brain/runtime/assurance/verification.ts
|
|
10063
10338
|
import { createHash as createHash15, randomBytes } from "node:crypto";
|
|
10064
10339
|
import { execFileSync as execFileSync3, spawn as spawn2 } from "node:child_process";
|
|
10065
|
-
import { accessSync, constants as
|
|
10066
|
-
import { delimiter, isAbsolute as isAbsolute5, join as
|
|
10340
|
+
import { accessSync, constants as constants6, readFileSync as readFileSync11, readdirSync as readdirSync5, realpathSync as realpathSync9, statSync as statSync4 } from "node:fs";
|
|
10341
|
+
import { delimiter, isAbsolute as isAbsolute5, join as join11, relative as relative5, resolve as resolve11, sep as sep6 } from "node:path";
|
|
10067
10342
|
class VerificationAbortedError extends Error {
|
|
10068
10343
|
constructor() {
|
|
10069
10344
|
super("fixed verification aborted");
|
|
@@ -10099,9 +10374,9 @@ function verificationPath() {
|
|
|
10099
10374
|
}
|
|
10100
10375
|
function toolPath(name, path) {
|
|
10101
10376
|
for (const dir of path.split(delimiter).filter(isAbsolute5)) {
|
|
10102
|
-
const candidate =
|
|
10377
|
+
const candidate = join11(dir, name);
|
|
10103
10378
|
try {
|
|
10104
|
-
accessSync(candidate,
|
|
10379
|
+
accessSync(candidate, constants6.X_OK);
|
|
10105
10380
|
if (statSync4(candidate).isFile())
|
|
10106
10381
|
return candidate;
|
|
10107
10382
|
} catch {}
|
|
@@ -10111,7 +10386,7 @@ function toolPath(name, path) {
|
|
|
10111
10386
|
function identity(path) {
|
|
10112
10387
|
try {
|
|
10113
10388
|
const real = realpathSync9(path);
|
|
10114
|
-
accessSync(real,
|
|
10389
|
+
accessSync(real, constants6.X_OK);
|
|
10115
10390
|
const stat = statSync4(real);
|
|
10116
10391
|
if (!stat.isFile())
|
|
10117
10392
|
throw new Error("not a file");
|
|
@@ -10208,7 +10483,7 @@ async function runFixedVerification(root, command, frozen, options) {
|
|
|
10208
10483
|
};
|
|
10209
10484
|
const procEnviron = (pid) => {
|
|
10210
10485
|
try {
|
|
10211
|
-
return readFileSync11(
|
|
10486
|
+
return readFileSync11(join11(procRoot, String(pid), "environ"), "utf8");
|
|
10212
10487
|
} catch {
|
|
10213
10488
|
return null;
|
|
10214
10489
|
}
|
|
@@ -10216,7 +10491,7 @@ async function runFixedVerification(root, command, frozen, options) {
|
|
|
10216
10491
|
const procSession = (entry) => {
|
|
10217
10492
|
let stat;
|
|
10218
10493
|
try {
|
|
10219
|
-
stat = readFileSync11(
|
|
10494
|
+
stat = readFileSync11(join11(procRoot, entry, "stat"), "utf8");
|
|
10220
10495
|
} catch (error) {
|
|
10221
10496
|
if (error.code === "ENOENT")
|
|
10222
10497
|
return null;
|
|
@@ -10427,7 +10702,7 @@ function createInvocationRegistry() {
|
|
|
10427
10702
|
// plugins/immune-brain/runtime/role_prompt_bridge.ts
|
|
10428
10703
|
import { createHash as createHash16 } from "node:crypto";
|
|
10429
10704
|
import { existsSync as existsSync8, readFileSync as readFileSync12 } from "node:fs";
|
|
10430
|
-
import { join as
|
|
10705
|
+
import { join as join12, dirname as dirname8 } from "node:path";
|
|
10431
10706
|
import { fileURLToPath } from "node:url";
|
|
10432
10707
|
var RUNTIME_DIR = dirname8(fileURLToPath(import.meta.url));
|
|
10433
10708
|
var INTERNAL_ROLE_PROMPTS = {
|
|
@@ -10489,14 +10764,14 @@ function roleSpec(role) {
|
|
|
10489
10764
|
}
|
|
10490
10765
|
function rolePromptSearchDirs(moduleDir) {
|
|
10491
10766
|
return [
|
|
10492
|
-
|
|
10493
|
-
|
|
10767
|
+
join12(moduleDir, "..", "dist", "role-prompts"),
|
|
10768
|
+
join12(moduleDir, "..", "role-prompts")
|
|
10494
10769
|
];
|
|
10495
10770
|
}
|
|
10496
10771
|
function loadRolePrompt(role) {
|
|
10497
10772
|
const spec = roleSpec(role);
|
|
10498
10773
|
for (const dir of rolePromptSearchDirs(RUNTIME_DIR)) {
|
|
10499
|
-
const path =
|
|
10774
|
+
const path = join12(dir, spec.file);
|
|
10500
10775
|
if (existsSync8(path))
|
|
10501
10776
|
return readFileSync12(path, "utf8");
|
|
10502
10777
|
}
|
|
@@ -11769,7 +12044,7 @@ function stagePlanningArtifactTransition(root, record) {
|
|
|
11769
12044
|
intentArchive,
|
|
11770
12045
|
...specActive ? [specActive, specActive.replace("docs/specs/", "docs/specs/archive/")] : []
|
|
11771
12046
|
];
|
|
11772
|
-
const paths = candidates.filter((path) => existsSync9(
|
|
12047
|
+
const paths = candidates.filter((path) => existsSync9(join13(root, path)) || execFileSync4("git", ["ls-files", "--cached", "--", path], { cwd: root, encoding: "utf8" }).trim().length > 0);
|
|
11773
12048
|
if (paths.length === 0)
|
|
11774
12049
|
return;
|
|
11775
12050
|
execFileSync4("git", ["add", "--", ...paths], {
|
|
@@ -11865,7 +12140,7 @@ function createVerdictAuthority(options, registry = createMutationAuthorityRegis
|
|
|
11865
12140
|
// plugins/immune-brain/runtime/claude/kernel_ports.ts
|
|
11866
12141
|
import { existsSync as existsSync14, readFileSync as readFileSync17, writeFileSync as writeFileSync10 } from "node:fs";
|
|
11867
12142
|
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
11868
|
-
import { join as
|
|
12143
|
+
import { join as join22 } from "node:path";
|
|
11869
12144
|
|
|
11870
12145
|
// plugins/immune-brain/runtime/kernel/enrollment_authority.ts
|
|
11871
12146
|
var ENROLLMENT_CAPABILITY_BRAND = Symbol.for("assurance-kernel.enrollment-capability-brand");
|
|
@@ -12239,9 +12514,9 @@ import { createHash as createHash19 } from "node:crypto";
|
|
|
12239
12514
|
// plugins/immune-brain/runtime/assurance/delivery_workspace.ts
|
|
12240
12515
|
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
12241
12516
|
import { createHash as createHash18 } from "node:crypto";
|
|
12242
|
-
import { chmodSync as chmodSync2, lstatSync as
|
|
12243
|
-
import { tmpdir as
|
|
12244
|
-
import { dirname as dirname9, isAbsolute as isAbsolute6, join as
|
|
12517
|
+
import { chmodSync as chmodSync2, lstatSync as lstatSync11, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync2, readdirSync as readdirSync6, readFileSync as readFileSync13, readlinkSync as readlinkSync2, realpathSync as realpathSync10, rmSync as rmSync7 } from "node:fs";
|
|
12518
|
+
import { tmpdir as tmpdir4 } from "node:os";
|
|
12519
|
+
import { dirname as dirname9, isAbsolute as isAbsolute6, join as join14, parse, relative as relative6, resolve as resolve13, sep as sep7 } from "node:path";
|
|
12245
12520
|
|
|
12246
12521
|
class DeliveryWorkspaceError extends Error {
|
|
12247
12522
|
constructor(message) {
|
|
@@ -12253,7 +12528,7 @@ function removeTaskOwnedTree(root) {
|
|
|
12253
12528
|
const makeDirectoriesWritable = (path) => {
|
|
12254
12529
|
let stat;
|
|
12255
12530
|
try {
|
|
12256
|
-
stat =
|
|
12531
|
+
stat = lstatSync11(path);
|
|
12257
12532
|
} catch (error) {
|
|
12258
12533
|
if (error.code === "ENOENT")
|
|
12259
12534
|
return;
|
|
@@ -12263,10 +12538,10 @@ function removeTaskOwnedTree(root) {
|
|
|
12263
12538
|
return;
|
|
12264
12539
|
chmodSync2(path, stat.mode & 4095 | 448);
|
|
12265
12540
|
for (const name of readdirSync6(path))
|
|
12266
|
-
makeDirectoriesWritable(
|
|
12541
|
+
makeDirectoriesWritable(join14(path, name));
|
|
12267
12542
|
};
|
|
12268
12543
|
makeDirectoriesWritable(root);
|
|
12269
|
-
|
|
12544
|
+
rmSync7(root, { recursive: true, force: true });
|
|
12270
12545
|
}
|
|
12271
12546
|
var GIT_OBJECT_ID5 = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/;
|
|
12272
12547
|
function isolatedGitEnv(extra = {}) {
|
|
@@ -12314,9 +12589,9 @@ function resolvedSymlinkTarget(path) {
|
|
|
12314
12589
|
current = dirname9(current);
|
|
12315
12590
|
continue;
|
|
12316
12591
|
}
|
|
12317
|
-
const next =
|
|
12592
|
+
const next = join14(current, part);
|
|
12318
12593
|
try {
|
|
12319
|
-
if (!
|
|
12594
|
+
if (!lstatSync11(next).isSymbolicLink()) {
|
|
12320
12595
|
current = next;
|
|
12321
12596
|
continue;
|
|
12322
12597
|
}
|
|
@@ -12337,8 +12612,8 @@ function resolvedSymlinkTarget(path) {
|
|
|
12337
12612
|
function assertNoEscapingSymlinks(root, dir = root) {
|
|
12338
12613
|
const realRoot = realpathSync10(root);
|
|
12339
12614
|
for (const name of readdirSync6(dir)) {
|
|
12340
|
-
const path =
|
|
12341
|
-
const stat =
|
|
12615
|
+
const path = join14(dir, name);
|
|
12616
|
+
const stat = lstatSync11(path);
|
|
12342
12617
|
if (stat.isSymbolicLink()) {
|
|
12343
12618
|
assertInside(realRoot, resolvedSymlinkTarget(path), `symlink ${relative6(root, path)}`);
|
|
12344
12619
|
} else if (stat.isDirectory()) {
|
|
@@ -12347,41 +12622,41 @@ function assertNoEscapingSymlinks(root, dir = root) {
|
|
|
12347
12622
|
}
|
|
12348
12623
|
}
|
|
12349
12624
|
function writeDeliveryTree(sourceRoot, snapshot) {
|
|
12350
|
-
const indexDirectory = mkdtempSync2(
|
|
12625
|
+
const indexDirectory = mkdtempSync2(join14(tmpdir4(), "imm-delivery-index-"));
|
|
12351
12626
|
try {
|
|
12352
12627
|
git5(sourceRoot, ["read-tree", snapshot.base_tree], {
|
|
12353
|
-
GIT_INDEX_FILE:
|
|
12354
|
-
GIT_DIR:
|
|
12628
|
+
GIT_INDEX_FILE: join14(indexDirectory, "index"),
|
|
12629
|
+
GIT_DIR: join14(sourceRoot, ".git")
|
|
12355
12630
|
});
|
|
12356
12631
|
for (const [path, entry] of Object.entries(snapshot.changed_paths)) {
|
|
12357
12632
|
if (entry.oid && entry.mode) {
|
|
12358
12633
|
git5(sourceRoot, ["update-index", "--add", "--cacheinfo", `${entry.mode},${entry.oid},${path}`], {
|
|
12359
|
-
GIT_INDEX_FILE:
|
|
12360
|
-
GIT_DIR:
|
|
12634
|
+
GIT_INDEX_FILE: join14(indexDirectory, "index"),
|
|
12635
|
+
GIT_DIR: join14(sourceRoot, ".git")
|
|
12361
12636
|
});
|
|
12362
12637
|
} else {
|
|
12363
12638
|
git5(sourceRoot, ["update-index", "--force-remove", "--", path], {
|
|
12364
|
-
GIT_INDEX_FILE:
|
|
12365
|
-
GIT_DIR:
|
|
12639
|
+
GIT_INDEX_FILE: join14(indexDirectory, "index"),
|
|
12640
|
+
GIT_DIR: join14(sourceRoot, ".git")
|
|
12366
12641
|
});
|
|
12367
12642
|
}
|
|
12368
12643
|
}
|
|
12369
12644
|
const next = git5(sourceRoot, ["write-tree"], {
|
|
12370
|
-
GIT_INDEX_FILE:
|
|
12371
|
-
GIT_DIR:
|
|
12645
|
+
GIT_INDEX_FILE: join14(indexDirectory, "index"),
|
|
12646
|
+
GIT_DIR: join14(sourceRoot, ".git")
|
|
12372
12647
|
});
|
|
12373
12648
|
assertTree(next);
|
|
12374
12649
|
return next;
|
|
12375
12650
|
} finally {
|
|
12376
|
-
|
|
12651
|
+
rmSync7(indexDirectory, { recursive: true, force: true });
|
|
12377
12652
|
}
|
|
12378
12653
|
}
|
|
12379
12654
|
function workspaceFiles(root, dir = root, result = Object.create(null)) {
|
|
12380
12655
|
if (dir === root)
|
|
12381
|
-
result["."] = `${
|
|
12656
|
+
result["."] = `${lstatSync11(root).mode & 4095}:directory`;
|
|
12382
12657
|
for (const name of readdirSync6(dir).sort()) {
|
|
12383
|
-
const path =
|
|
12384
|
-
const stat =
|
|
12658
|
+
const path = join14(dir, name), rel = relative6(root, path);
|
|
12659
|
+
const stat = lstatSync11(path);
|
|
12385
12660
|
if (stat.isSymbolicLink())
|
|
12386
12661
|
result[rel] = `link:${stat.mode & 4095}:${readlinkSync2(path)}`;
|
|
12387
12662
|
else if (stat.isDirectory()) {
|
|
@@ -12408,7 +12683,7 @@ function assertDeliveryClean(root, tree, seal, writablePaths = []) {
|
|
|
12408
12683
|
}
|
|
12409
12684
|
if (seal === "[]") {
|
|
12410
12685
|
for (const name of readdirSync6(root)) {
|
|
12411
|
-
const stat =
|
|
12686
|
+
const stat = lstatSync11(join14(root, name));
|
|
12412
12687
|
if (!stat.isDirectory() || name !== ".git")
|
|
12413
12688
|
throw new DeliveryWorkspaceError("delivery workspace was contaminated");
|
|
12414
12689
|
}
|
|
@@ -12430,18 +12705,18 @@ function assertDeliveryClean(root, tree, seal, writablePaths = []) {
|
|
|
12430
12705
|
if (!permitted)
|
|
12431
12706
|
throw new DeliveryWorkspaceError("delivery undeclared output was contaminated");
|
|
12432
12707
|
if (bytes.startsWith("link:")) {
|
|
12433
|
-
assertInside(realpathSync10(root), resolvedSymlinkTarget(
|
|
12708
|
+
assertInside(realpathSync10(root), resolvedSymlinkTarget(join14(root, path)), "generated symlink");
|
|
12434
12709
|
}
|
|
12435
12710
|
}
|
|
12436
12711
|
}
|
|
12437
12712
|
function materializeDeliveryWorkspace(sourceRoot, tree) {
|
|
12438
12713
|
assertTree(tree);
|
|
12439
|
-
const dest = mkdtempSync2(
|
|
12714
|
+
const dest = mkdtempSync2(join14(tmpdir4(), "imm-delivery-"));
|
|
12440
12715
|
const cleanup = () => removeTaskOwnedTree(dest);
|
|
12441
12716
|
try {
|
|
12442
12717
|
mkdirSync7(dest, { recursive: true });
|
|
12443
12718
|
const commit = git5(sourceRoot, ["commit-tree", tree, "-m", `delivery ${tree}`], {
|
|
12444
|
-
GIT_DIR:
|
|
12719
|
+
GIT_DIR: join14(sourceRoot, ".git"),
|
|
12445
12720
|
GIT_AUTHOR_NAME: "Immune-Brain Assurance",
|
|
12446
12721
|
GIT_AUTHOR_EMAIL: "assurance@immune-brain.local",
|
|
12447
12722
|
GIT_AUTHOR_DATE: "1970-01-01T00:00:00 +0000",
|
|
@@ -12469,8 +12744,8 @@ function materializeDeliveryWorkspace(sourceRoot, tree) {
|
|
|
12469
12744
|
|
|
12470
12745
|
// plugins/immune-brain/runtime/assurance/qa.ts
|
|
12471
12746
|
import { mkdtempSync as mkdtempSync3, realpathSync as realpathSync11 } from "node:fs";
|
|
12472
|
-
import { tmpdir as
|
|
12473
|
-
import { join as
|
|
12747
|
+
import { tmpdir as tmpdir5 } from "node:os";
|
|
12748
|
+
import { join as join15, sep as sep8 } from "node:path";
|
|
12474
12749
|
|
|
12475
12750
|
// plugins/immune-brain/runtime/assurance/qa_findings.ts
|
|
12476
12751
|
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
@@ -12537,7 +12812,7 @@ async function runDeterministicQa(snapshot, descriptors, options = {}) {
|
|
|
12537
12812
|
let delivery, home;
|
|
12538
12813
|
try {
|
|
12539
12814
|
delivery = (options._materializeDeliveryWorkspace ?? materializeDeliveryWorkspace)(snapshot.root, tree);
|
|
12540
|
-
home = mkdtempSync3(
|
|
12815
|
+
home = mkdtempSync3(join15(tmpdir5(), "imm-qa-home-"));
|
|
12541
12816
|
return { delivery, home, deliveryPrefix: `${realpathSync11(delivery.root)}${sep8}` };
|
|
12542
12817
|
} catch {
|
|
12543
12818
|
let reason = "delivery_unavailable";
|
|
@@ -12705,11 +12980,11 @@ function deriveAuthorizationOperation(input) {
|
|
|
12705
12980
|
// plugins/immune-brain/runtime/staged_intent.ts
|
|
12706
12981
|
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
12707
12982
|
import { readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "node:fs";
|
|
12708
|
-
import { join as
|
|
12983
|
+
import { join as join16 } from "node:path";
|
|
12709
12984
|
function captureStagedIntent(root, relativePath) {
|
|
12710
12985
|
return {
|
|
12711
12986
|
path: relativePath,
|
|
12712
|
-
bytes: readFileSync14(
|
|
12987
|
+
bytes: readFileSync14(join16(root, relativePath)),
|
|
12713
12988
|
index_state: execFileSync7("git", ["ls-files", "--stage", "-z", "--", relativePath], {
|
|
12714
12989
|
cwd: root,
|
|
12715
12990
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -12717,7 +12992,7 @@ function captureStagedIntent(root, relativePath) {
|
|
|
12717
12992
|
};
|
|
12718
12993
|
}
|
|
12719
12994
|
function restoreStagedIntent(root, snapshot) {
|
|
12720
|
-
writeFileSync7(
|
|
12995
|
+
writeFileSync7(join16(root, snapshot.path), snapshot.bytes);
|
|
12721
12996
|
execFileSync7("git", ["update-index", "--force-remove", "--", snapshot.path], {
|
|
12722
12997
|
cwd: root,
|
|
12723
12998
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -12729,7 +13004,7 @@ function restoreStagedIntent(root, snapshot) {
|
|
|
12729
13004
|
stdio: ["pipe", "ignore", "pipe"]
|
|
12730
13005
|
});
|
|
12731
13006
|
}
|
|
12732
|
-
const restoredBytes = readFileSync14(
|
|
13007
|
+
const restoredBytes = readFileSync14(join16(root, snapshot.path));
|
|
12733
13008
|
if (!restoredBytes.equals(snapshot.bytes)) {
|
|
12734
13009
|
throw new Error("failed to restore prior intent bytes");
|
|
12735
13010
|
}
|
|
@@ -12745,24 +13020,24 @@ function restoreStagedIntent(root, snapshot) {
|
|
|
12745
13020
|
// plugins/immune-brain/runtime/unattended/batch_runner.ts
|
|
12746
13021
|
import { spawnSync as spawnSync14 } from "node:child_process";
|
|
12747
13022
|
import { existsSync as existsSync13 } from "node:fs";
|
|
12748
|
-
import { join as
|
|
13023
|
+
import { join as join21 } from "node:path";
|
|
12749
13024
|
|
|
12750
13025
|
// plugins/immune-brain/runtime/unattended/batch_git.ts
|
|
12751
13026
|
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
12752
13027
|
import { createHash as createHash20, randomUUID as randomUUID9 } from "node:crypto";
|
|
12753
13028
|
import {
|
|
12754
|
-
constants as
|
|
12755
|
-
closeSync as
|
|
13029
|
+
constants as constants7,
|
|
13030
|
+
closeSync as closeSync8,
|
|
12756
13031
|
existsSync as existsSync10,
|
|
12757
|
-
lstatSync as
|
|
13032
|
+
lstatSync as lstatSync12,
|
|
12758
13033
|
mkdirSync as mkdirSync8,
|
|
12759
|
-
openSync as
|
|
13034
|
+
openSync as openSync8,
|
|
12760
13035
|
realpathSync as realpathSync12,
|
|
12761
13036
|
renameSync as renameSync5,
|
|
12762
|
-
rmSync as
|
|
13037
|
+
rmSync as rmSync8,
|
|
12763
13038
|
writeFileSync as writeFileSync8
|
|
12764
13039
|
} from "node:fs";
|
|
12765
|
-
import { dirname as dirname10, join as
|
|
13040
|
+
import { dirname as dirname10, join as join17 } from "node:path";
|
|
12766
13041
|
var DEFAULT_GIT_ENV = {
|
|
12767
13042
|
...process.env,
|
|
12768
13043
|
GIT_AUTHOR_NAME: process.env.GIT_AUTHOR_NAME || "Immune-Brain Batch",
|
|
@@ -12974,15 +13249,15 @@ function getCommittedDeltaPaths(root) {
|
|
|
12974
13249
|
return delta.stdout.split("\x00").filter((p) => p.length > 0);
|
|
12975
13250
|
}
|
|
12976
13251
|
function commitEvidencePath(batchId, taskId) {
|
|
12977
|
-
return
|
|
13252
|
+
return join17(".imm", "state", "batches", "commits", `${batchId}-${taskId}.json`);
|
|
12978
13253
|
}
|
|
12979
13254
|
function ensureSecureDirectory3(root, relativePath) {
|
|
12980
13255
|
const segments = relativePath.split("/").filter(Boolean);
|
|
12981
13256
|
let current = root;
|
|
12982
13257
|
for (const segment of segments) {
|
|
12983
|
-
current =
|
|
13258
|
+
current = join17(current, segment);
|
|
12984
13259
|
if (existsSync10(current)) {
|
|
12985
|
-
const stats =
|
|
13260
|
+
const stats = lstatSync12(current);
|
|
12986
13261
|
if (stats.isSymbolicLink() || !stats.isDirectory()) {
|
|
12987
13262
|
throw new Error(`${segment} exists but is not a real directory`);
|
|
12988
13263
|
}
|
|
@@ -12993,15 +13268,15 @@ function ensureSecureDirectory3(root, relativePath) {
|
|
|
12993
13268
|
return current;
|
|
12994
13269
|
}
|
|
12995
13270
|
function writeFileAtomically2(root, relativePath, bytes) {
|
|
12996
|
-
const target =
|
|
13271
|
+
const target = join17(root, relativePath);
|
|
12997
13272
|
const targetDir = dirname10(target);
|
|
12998
13273
|
ensureSecureDirectory3(root, dirname10(relativePath));
|
|
12999
|
-
const stats =
|
|
13274
|
+
const stats = lstatSync12(targetDir);
|
|
13000
13275
|
if (stats.isSymbolicLink() || !stats.isDirectory()) {
|
|
13001
13276
|
throw new Error(`${dirname10(relativePath)} is not a real directory`);
|
|
13002
13277
|
}
|
|
13003
13278
|
if (existsSync10(target)) {
|
|
13004
|
-
const targetStats =
|
|
13279
|
+
const targetStats = lstatSync12(target);
|
|
13005
13280
|
if (targetStats.isSymbolicLink()) {
|
|
13006
13281
|
throw new Error(`${relativePath} is a symlink`);
|
|
13007
13282
|
}
|
|
@@ -13009,17 +13284,17 @@ function writeFileAtomically2(root, relativePath, bytes) {
|
|
|
13009
13284
|
const tempPath = `${target}.${randomUUID9()}.tmp`;
|
|
13010
13285
|
let fd = null;
|
|
13011
13286
|
try {
|
|
13012
|
-
fd =
|
|
13287
|
+
fd = openSync8(tempPath, constants7.O_WRONLY | constants7.O_CREAT | constants7.O_EXCL, 384);
|
|
13013
13288
|
writeFileSync8(fd, bytes, "utf8");
|
|
13014
|
-
|
|
13289
|
+
closeSync8(fd);
|
|
13015
13290
|
fd = null;
|
|
13016
13291
|
renameSync5(tempPath, target);
|
|
13017
13292
|
} finally {
|
|
13018
13293
|
if (fd !== null)
|
|
13019
|
-
|
|
13294
|
+
closeSync8(fd);
|
|
13020
13295
|
if (existsSync10(tempPath)) {
|
|
13021
13296
|
try {
|
|
13022
|
-
|
|
13297
|
+
rmSync8(tempPath);
|
|
13023
13298
|
} catch {}
|
|
13024
13299
|
}
|
|
13025
13300
|
}
|
|
@@ -13039,7 +13314,7 @@ function writeBatchCommitEvidence(root, evidence) {
|
|
|
13039
13314
|
}
|
|
13040
13315
|
function readBatchCommitEvidence(root, batchId, taskId) {
|
|
13041
13316
|
const path = commitEvidencePath(batchId, taskId);
|
|
13042
|
-
const fullPath =
|
|
13317
|
+
const fullPath = join17(root, path);
|
|
13043
13318
|
if (!existsSync10(fullPath))
|
|
13044
13319
|
return null;
|
|
13045
13320
|
try {
|
|
@@ -13314,11 +13589,11 @@ function createDefaultBatchGitPort() {
|
|
|
13314
13589
|
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
13315
13590
|
import { createHash as createHash21 } from "node:crypto";
|
|
13316
13591
|
import { existsSync as existsSync12, realpathSync as realpathSync13 } from "node:fs";
|
|
13317
|
-
import { isAbsolute as isAbsolute7, join as
|
|
13592
|
+
import { isAbsolute as isAbsolute7, join as join20 } from "node:path";
|
|
13318
13593
|
|
|
13319
13594
|
// plugins/immune-brain/runtime/kernel/runtime_contracts.ts
|
|
13320
13595
|
import { existsSync as existsSync11, readFileSync as readFileSync15 } from "node:fs";
|
|
13321
|
-
import { dirname as dirname11, join as
|
|
13596
|
+
import { dirname as dirname11, join as join18, resolve as resolve14 } from "node:path";
|
|
13322
13597
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
13323
13598
|
var RUNTIME_CONTRACTS_MANIFEST = "runtime_contracts.json";
|
|
13324
13599
|
var RUNTIME_CONTRACTS = {
|
|
@@ -13338,10 +13613,10 @@ function runningRuntimeSource() {
|
|
|
13338
13613
|
}
|
|
13339
13614
|
function readRuntimeContracts(runtimeDir) {
|
|
13340
13615
|
const candidates = [
|
|
13341
|
-
|
|
13342
|
-
|
|
13343
|
-
|
|
13344
|
-
|
|
13616
|
+
join18(runtimeDir, RUNTIME_CONTRACTS_MANIFEST),
|
|
13617
|
+
join18(runtimeDir, "kernel", RUNTIME_CONTRACTS_MANIFEST),
|
|
13618
|
+
join18(runtimeDir, "runtime", "kernel", RUNTIME_CONTRACTS_MANIFEST),
|
|
13619
|
+
join18(runtimeDir, "plugins", "immune-brain", "runtime", "kernel", RUNTIME_CONTRACTS_MANIFEST)
|
|
13345
13620
|
];
|
|
13346
13621
|
const path = candidates.find((candidate) => existsSync11(candidate));
|
|
13347
13622
|
if (!path)
|
|
@@ -13412,7 +13687,7 @@ async function runFinalVerification(root, commands, options = {}) {
|
|
|
13412
13687
|
|
|
13413
13688
|
// plugins/immune-brain/runtime/unattended/batch_delegation.ts
|
|
13414
13689
|
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
13415
|
-
import { join as
|
|
13690
|
+
import { join as join19 } from "node:path";
|
|
13416
13691
|
import { writeFileSync as writeFileSync9 } from "node:fs";
|
|
13417
13692
|
var REVISION_DELEGATION_TEXT = "Optional, off by default: also let the batch coordinator approve a Lane child's breaking Intent revisions that stay inside that child's authorized TaskIntent (acceptance changes, a narrower scope_hint). Widening scope, changing risk, goal or the bound Spec still needs your own confirmation. The grant ends with this batch.";
|
|
13418
13693
|
var DELEGATED_ACTOR_ID = "batch-coordinator";
|
|
@@ -13463,7 +13738,7 @@ function applyDelegatedIntentRevision(input) {
|
|
|
13463
13738
|
const nextRef = { path: `docs/plans/${taskId}.intent.json`, content_hash: nextHash };
|
|
13464
13739
|
const snapshot = captureStagedIntent(root, prior.intent_ref.path);
|
|
13465
13740
|
try {
|
|
13466
|
-
writeFileSync9(
|
|
13741
|
+
writeFileSync9(join19(root, prior.intent_ref.path), `${JSON.stringify(nextIntent, null, 2)}
|
|
13467
13742
|
`);
|
|
13468
13743
|
execFileSync8("git", ["add", "--", prior.intent_ref.path], { cwd: root, stdio: ["ignore", "pipe", "pipe"] });
|
|
13469
13744
|
const prepared = await projectAssurance(root, taskId, taskDeliveryIdentity);
|
|
@@ -13832,7 +14107,7 @@ function laneRuntimeDir(root, explicit) {
|
|
|
13832
14107
|
if (explicit)
|
|
13833
14108
|
return explicit;
|
|
13834
14109
|
try {
|
|
13835
|
-
const manifest = JSON.parse(readFileSync16(
|
|
14110
|
+
const manifest = JSON.parse(readFileSync16(join20(root, "package.json"), "utf8"));
|
|
13836
14111
|
return manifest.name === "immune-brain" ? root : null;
|
|
13837
14112
|
} catch {
|
|
13838
14113
|
return null;
|
|
@@ -14140,7 +14415,7 @@ function finalizeLane(root, record, reason, nextAction, extra = {}) {
|
|
|
14140
14415
|
writeBatchRunReport(root, report);
|
|
14141
14416
|
return report;
|
|
14142
14417
|
}
|
|
14143
|
-
function
|
|
14418
|
+
function refuse3(input, persisted, code, detail) {
|
|
14144
14419
|
const base = persisted && isLaneBatchRecord(persisted) ? persisted : prepareBatchLaneRunState({
|
|
14145
14420
|
batch_id: input.batch_id,
|
|
14146
14421
|
initiative_slug: input.initiative_slug,
|
|
@@ -14229,12 +14504,12 @@ function isLineageBreak(message) {
|
|
|
14229
14504
|
}
|
|
14230
14505
|
async function runLaneBatch(input, persisted) {
|
|
14231
14506
|
if (persisted && !isLaneBatchRecord(persisted))
|
|
14232
|
-
return
|
|
14507
|
+
return refuse3(input, persisted, PARALLEL_MISMATCH, "the recorded batch runs serially; max_parallel cannot be added on a resume");
|
|
14233
14508
|
if (persisted && input.max_parallel !== undefined && input.max_parallel !== persisted.max_parallel)
|
|
14234
|
-
return
|
|
14509
|
+
return refuse3(input, persisted, PARALLEL_MISMATCH, `the recorded batch runs with max_parallel ${persisted.max_parallel}`);
|
|
14235
14510
|
const limit = persisted?.max_parallel ?? input.max_parallel ?? 1;
|
|
14236
14511
|
if (!input.children.length)
|
|
14237
|
-
return
|
|
14512
|
+
return refuse3(input, persisted, "batch_plan_empty", "batch plan is empty");
|
|
14238
14513
|
const git = serialGitOf(input);
|
|
14239
14514
|
const lanes = laneGitOf(input);
|
|
14240
14515
|
let record;
|
|
@@ -14281,11 +14556,11 @@ async function runLaneBatch(input, persisted) {
|
|
|
14281
14556
|
} else {
|
|
14282
14557
|
const invalid = validateNewAuthorization(input);
|
|
14283
14558
|
if (invalid)
|
|
14284
|
-
return
|
|
14559
|
+
return refuse3(input, null, "batch_authorization_invalid", invalid);
|
|
14285
14560
|
const contractRefusal = laneRuntimeContractRefusal(input.root, input.executor_runtime);
|
|
14286
14561
|
if (contractRefusal) {
|
|
14287
14562
|
const [code, ...detail] = contractRefusal.split(": ");
|
|
14288
|
-
return
|
|
14563
|
+
return refuse3(input, null, code, detail.join(": "));
|
|
14289
14564
|
}
|
|
14290
14565
|
const preflight = await git.preflight({
|
|
14291
14566
|
root: input.root,
|
|
@@ -14293,7 +14568,7 @@ async function runLaneBatch(input, persisted) {
|
|
|
14293
14568
|
base_head: input.base_head
|
|
14294
14569
|
});
|
|
14295
14570
|
if (!preflight.ok)
|
|
14296
|
-
return
|
|
14571
|
+
return refuse3(input, null, preflight.reason, preflight.message || "Correct the preflight condition and re-confirm.");
|
|
14297
14572
|
record = prepareBatchLaneRunState({
|
|
14298
14573
|
batch_id: input.batch_id,
|
|
14299
14574
|
initiative_slug: input.initiative_slug,
|
|
@@ -14357,7 +14632,7 @@ async function runLaneBatch(input, persisted) {
|
|
|
14357
14632
|
record.children = record.children.map((c) => c.task_id === child.task_id ? { ...c, state: "released" } : c);
|
|
14358
14633
|
persist();
|
|
14359
14634
|
}
|
|
14360
|
-
const lineage = existsSync12(
|
|
14635
|
+
const lineage = existsSync12(join20(input.root, ".git")) ? classifyBatchLineage({
|
|
14361
14636
|
root: input.root,
|
|
14362
14637
|
branch: record.branch ?? "",
|
|
14363
14638
|
expectedHead: expectedBatchHead(record),
|
|
@@ -14808,7 +15083,7 @@ function failPersistedLineage(root, existing, message) {
|
|
|
14808
15083
|
return writeBatchRunState(root, record);
|
|
14809
15084
|
}
|
|
14810
15085
|
function reconcileLineage(root, record) {
|
|
14811
|
-
if (!existsSync13(
|
|
15086
|
+
if (!existsSync13(join21(root, ".git")))
|
|
14812
15087
|
return { record, failure: null };
|
|
14813
15088
|
const expected = expectedBatchHead(record);
|
|
14814
15089
|
const lineage = classifyBatchLineage({
|
|
@@ -14831,7 +15106,7 @@ function reconcileLineage(root, record) {
|
|
|
14831
15106
|
};
|
|
14832
15107
|
}
|
|
14833
15108
|
async function validatePersistedRun(input, record) {
|
|
14834
|
-
if (!record.commits.length && record.children[0]?.state === "settled" && existsSync13(
|
|
15109
|
+
if (!record.commits.length && record.children[0]?.state === "settled" && existsSync13(join21(input.root, ".git"))) {
|
|
14835
15110
|
const head = spawnSync14("git", ["-C", input.root, "rev-parse", "HEAD"], { encoding: "utf8" });
|
|
14836
15111
|
const live = head.stdout.trim();
|
|
14837
15112
|
const expected = expectedBatchHead(record);
|
|
@@ -14861,7 +15136,7 @@ async function validatePersistedRun(input, record) {
|
|
|
14861
15136
|
const git = batchGitPortOf(input);
|
|
14862
15137
|
const evidence = await git.lookupBatchCommit(input.root, child.task_id, record.batch_id, undefined, record.branch);
|
|
14863
15138
|
if (!evidence || evidence.commit !== child.commit) {
|
|
14864
|
-
if (evidence === null && typeof child.commit === "string" && child.commit.length > 0 && existsSync13(
|
|
15139
|
+
if (evidence === null && typeof child.commit === "string" && child.commit.length > 0 && existsSync13(join21(input.root, ".git"))) {
|
|
14865
15140
|
const reach = spawnSync14("git", ["-C", input.root, "merge-base", "--is-ancestor", child.commit, "HEAD"], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
14866
15141
|
if (reach.status !== 0) {
|
|
14867
15142
|
throw new Error(`batch_head_lineage_broken: recorded commit ${child.commit} for ${child.task_id} is no longer reachable from HEAD`);
|
|
@@ -15301,7 +15576,7 @@ async function driveInterruptedChild(input, child) {
|
|
|
15301
15576
|
const planChild = input.children.find((c) => c.task_id === child.task_id);
|
|
15302
15577
|
const intentPath = planChild?.intent_path ?? undefined;
|
|
15303
15578
|
const doCommit = async () => batchGitPortOf(input).commitChild(input.root, child.task_id, input.batch_id, head, record.branch, intentPath);
|
|
15304
|
-
if (existing && !record.commits.length && existsSync13(
|
|
15579
|
+
if (existing && !record.commits.length && existsSync13(join21(input.root, ".git")) && !ownUnpersistedBatchHead(input.root, record, existing.commit))
|
|
15305
15580
|
throw new Error("first unpersisted batch commit provenance is invalid");
|
|
15306
15581
|
const adopted = existing ?? await doCommit().catch((error) => {
|
|
15307
15582
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -15832,7 +16107,7 @@ class ClaudeRuntime {
|
|
|
15832
16107
|
if (!slug)
|
|
15833
16108
|
return result;
|
|
15834
16109
|
try {
|
|
15835
|
-
if (existsSync14(
|
|
16110
|
+
if (existsSync14(join22(this.cwd, ".imm", "audit", taskId)))
|
|
15836
16111
|
execFileSync9("git", ["-C", this.cwd, "add", "--", `.imm/audit/${taskId}`], { stdio: "ignore" });
|
|
15837
16112
|
const batch = await this.startUnattendedBatch(slug, meta, { reuseOnly: true });
|
|
15838
16113
|
return { ...result, batch };
|
|
@@ -15929,7 +16204,7 @@ class ClaudeRuntime {
|
|
|
15929
16204
|
throw new Error("approve_breaking_intent_revision requires next_intent");
|
|
15930
16205
|
const nextIntentHash = nextIntent ? canonicalIntentHash(nextIntent) : undefined;
|
|
15931
16206
|
const nextIntentRef = nextIntent ? { path: `docs/plans/${nextIntent.task_id}.intent.json`, content_hash: nextIntentHash } : undefined;
|
|
15932
|
-
const sidecar = nextIntent ?
|
|
16207
|
+
const sidecar = nextIntent ? join22(this.cwd, priorIntent.intent_ref.path) : undefined;
|
|
15933
16208
|
const stagedSnapshot = sidecar ? captureStagedIntent(this.cwd, priorIntent.intent_ref.path) : undefined;
|
|
15934
16209
|
const restoreStagedIntent2 = () => {
|
|
15935
16210
|
if (!stagedSnapshot)
|
|
@@ -16038,7 +16313,7 @@ class ClaudeRuntime {
|
|
|
16038
16313
|
const { app } = await this.authority();
|
|
16039
16314
|
const operation = input.operation.op === "revise_intent" ? { ...input.operation, next_intent: await parseTaskIntentV1(input.operation.next_intent) } : input.operation;
|
|
16040
16315
|
const priorIntent = await readTaskIntentForRecord(ctx.cwd, input.taskId);
|
|
16041
|
-
const sidecar =
|
|
16316
|
+
const sidecar = join22(ctx.cwd, priorIntent.intent_ref.path);
|
|
16042
16317
|
const priorBytes = operation.op === "revise_intent" ? readFileSync17(sidecar) : null;
|
|
16043
16318
|
const priorStaged = priorBytes !== null ? captureStagedIntent(ctx.cwd, priorIntent.intent_ref.path) : null;
|
|
16044
16319
|
try {
|
|
@@ -16442,6 +16717,12 @@ function createMcpRuntime(options = {}) {
|
|
|
16442
16717
|
let negotiatedVersion;
|
|
16443
16718
|
let negotiatedInteractive = false;
|
|
16444
16719
|
const connectionId = randomUUID10();
|
|
16720
|
+
const consumeAdmission = options.consumePhaseAdmission ?? ((toolCallId, operation) => consumePhaseAdmission(toolCallId, operation));
|
|
16721
|
+
const requireRunEntry = (operation, toolCallId) => {
|
|
16722
|
+
if (consumeAdmission(toolCallId, operation))
|
|
16723
|
+
return;
|
|
16724
|
+
throw new NativeAuthorityError("run_entry_required", `${operation} needs a literal-user imm-run entry in this session`);
|
|
16725
|
+
};
|
|
16445
16726
|
return {
|
|
16446
16727
|
runtime,
|
|
16447
16728
|
host,
|
|
@@ -16471,6 +16752,7 @@ function createMcpRuntime(options = {}) {
|
|
|
16471
16752
|
const probe = probeHost(options.env ?? process.env, process.platform, negotiatedVersion);
|
|
16472
16753
|
if (!probe.ok)
|
|
16473
16754
|
throw new NativeAuthorityError("unsupported_host", probe.reason);
|
|
16755
|
+
requireRunEntry(name, meta.toolCallId ?? `call-${name}`);
|
|
16474
16756
|
const toolMeta = {
|
|
16475
16757
|
sessionId: meta.sessionId ?? connectionId,
|
|
16476
16758
|
toolCallId: meta.toolCallId ?? `call-${name}`,
|
|
@@ -16538,8 +16820,10 @@ function createMcpRuntime(options = {}) {
|
|
|
16538
16820
|
interactive: meta.interactive ?? options.interactive ?? negotiatedInteractive,
|
|
16539
16821
|
signal: meta.signal
|
|
16540
16822
|
};
|
|
16541
|
-
if (name === "enroll")
|
|
16823
|
+
if (name === "enroll") {
|
|
16824
|
+
requireRunEntry(name, meta.toolCallId ?? `call-${name}`);
|
|
16542
16825
|
return runtime.enroll(taskId, toolMeta);
|
|
16826
|
+
}
|
|
16543
16827
|
if (name === "advance_assurance")
|
|
16544
16828
|
return runtime.advance(taskId, toolMeta.signal, toolMeta);
|
|
16545
16829
|
if (name === "submit_review") {
|
|
@@ -17046,13 +17330,14 @@ async function runHook() {
|
|
|
17046
17330
|
payload = null;
|
|
17047
17331
|
}
|
|
17048
17332
|
if (payload?.hook_event_name === "PreToolUse") {
|
|
17049
|
-
const decision = laneGuardHookOutput(payload);
|
|
17333
|
+
const decision = laneGuardHookOutput(payload) ?? phaseEntryGateHookOutput(payload);
|
|
17050
17334
|
if (decision)
|
|
17051
17335
|
process.stdout.write(`${decision}
|
|
17052
17336
|
`);
|
|
17053
17337
|
return;
|
|
17054
17338
|
}
|
|
17055
17339
|
if (payload?.hook_event_name === "UserPromptSubmit") {
|
|
17340
|
+
recordPhaseEntryPrompt(payload);
|
|
17056
17341
|
const notice = await activeTaskHookOutput(payload);
|
|
17057
17342
|
if (notice)
|
|
17058
17343
|
process.stdout.write(`${notice}
|