immune-brain 5.1.2 → 5.3.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 +6 -2
- package/plugins/immune-brain/dist/claude/mcp-server.mjs +429 -140
- package/plugins/immune-brain/dist/imm-brainstorm.md +3 -3
- package/plugins/immune-brain/dist/imm-doc-prune.md +2 -1
- package/plugins/immune-brain/dist/imm-planner.md +33 -37
- 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 +133 -0
- package/plugins/immune-brain/runtime/claude/interaction.ts +5 -1
- package/plugins/immune-brain/runtime/claude/lane_guard.ts +197 -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 +6 -2
- package/plugins/immune-brain/skills/imm-brainstorm/SKILL.md +6 -4
- package/plugins/immune-brain/skills/imm-planner/SKILL.md +8 -7
- 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,115 @@ 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.3.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 = { run_live: false };
|
|
99
|
+
function applyPrompt(state, entry) {
|
|
100
|
+
if (entry === "run")
|
|
101
|
+
return { run_live: true };
|
|
102
|
+
if (entry === "plan")
|
|
103
|
+
return { run_live: false };
|
|
104
|
+
return state;
|
|
105
|
+
}
|
|
106
|
+
function phaseEntryLogLine(entry) {
|
|
107
|
+
return `${JSON.stringify({ v: 1, entry })}
|
|
108
|
+
`;
|
|
109
|
+
}
|
|
110
|
+
function reducePhaseEntryLog(text) {
|
|
111
|
+
let state = NO_PHASE_ENTRY;
|
|
112
|
+
for (const line of text.split(`
|
|
113
|
+
`)) {
|
|
114
|
+
if (!line)
|
|
115
|
+
continue;
|
|
116
|
+
let record;
|
|
117
|
+
try {
|
|
118
|
+
record = JSON.parse(line);
|
|
119
|
+
} catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
if (!record || typeof record !== "object")
|
|
123
|
+
return null;
|
|
124
|
+
const { v, entry } = record;
|
|
125
|
+
if (v !== 1 || entry !== null && entry !== "plan" && entry !== "run")
|
|
126
|
+
return null;
|
|
127
|
+
state = applyPrompt(state, entry);
|
|
128
|
+
}
|
|
129
|
+
return state;
|
|
130
|
+
}
|
|
131
|
+
function refuse(reason_code, detail) {
|
|
132
|
+
return { ok: false, reason_code, detail, recovery_action: PHASE_ENTRY_RECOVERY[reason_code] };
|
|
133
|
+
}
|
|
134
|
+
function admitPhaseOperation(operation, state, context) {
|
|
135
|
+
if (operation.kind === "tool") {
|
|
136
|
+
if (state?.run_live)
|
|
137
|
+
return { ok: true };
|
|
138
|
+
return refuse(RUN_ENTRY_REQUIRED, `${operation.name} needs a literal-user imm-run entry in this session`);
|
|
139
|
+
}
|
|
140
|
+
if (operation.skill !== "imm-run")
|
|
141
|
+
return { ok: true };
|
|
142
|
+
if (state?.run_live)
|
|
143
|
+
return { ok: true };
|
|
144
|
+
return refuse(PHASE_ENTRY_REQUIRED, "imm-run is entered by the user, not by a model Skill call");
|
|
145
|
+
}
|
|
146
|
+
function phaseRefusalMessage(refusal) {
|
|
147
|
+
return `${refusal.reason_code}: ${refusal.detail}; recovery: ${refusal.recovery_action}`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// plugins/immune-brain/runtime/claude/interaction.ts
|
|
46
151
|
var PRIVILEGED_OPERATIONS = [
|
|
47
152
|
"enroll",
|
|
48
153
|
"request_authorization",
|
|
@@ -57,7 +162,8 @@ var RECOVERY_ACTIONS = {
|
|
|
57
162
|
user_cancelled: "wait for a fresh literal-user request",
|
|
58
163
|
correlation_missing: "retry through a fresh native gate in the current Host",
|
|
59
164
|
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"
|
|
165
|
+
workspace_changed: "review the current workspace and retry through a fresh native gate",
|
|
166
|
+
...PHASE_ENTRY_RECOVERY
|
|
61
167
|
};
|
|
62
168
|
|
|
63
169
|
class NativeAuthorityError extends Error {
|
|
@@ -684,6 +790,11 @@ function parseHookStdin(raw) {
|
|
|
684
790
|
return null;
|
|
685
791
|
}
|
|
686
792
|
|
|
793
|
+
// plugins/immune-brain/runtime/claude/lane_guard.ts
|
|
794
|
+
import { lstatSync as lstatSync10, rmSync as rmSync5 } from "node:fs";
|
|
795
|
+
import { homedir, tmpdir as tmpdir2 } from "node:os";
|
|
796
|
+
import { join as join9 } from "node:path";
|
|
797
|
+
|
|
687
798
|
// plugins/immune-brain/runtime/unattended/lane_workspace.ts
|
|
688
799
|
import { spawnSync } from "node:child_process";
|
|
689
800
|
import { realpathSync as realpathSync2 } from "node:fs";
|
|
@@ -6350,7 +6461,7 @@ var oid = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/;
|
|
|
6350
6461
|
var id = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
6351
6462
|
var batchAuthor = process.env.GIT_AUTHOR_NAME || "Immune-Brain Batch";
|
|
6352
6463
|
var decode = (bytes) => new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
6353
|
-
function
|
|
6464
|
+
function refuse2() {
|
|
6354
6465
|
throw new Error("plan_digest mismatch: batch plan reconfirmation is not eligible");
|
|
6355
6466
|
}
|
|
6356
6467
|
function matchesLocalProof(localJson, exported) {
|
|
@@ -6359,7 +6470,7 @@ function matchesLocalProof(localJson, exported) {
|
|
|
6359
6470
|
function git2(root, args) {
|
|
6360
6471
|
const r = spawnSync6("git", ["-C", root, ...args], { encoding: "buffer", maxBuffer: 262144, env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" } });
|
|
6361
6472
|
if (r.status !== 0)
|
|
6362
|
-
|
|
6473
|
+
refuse2();
|
|
6363
6474
|
return decode(r.stdout);
|
|
6364
6475
|
}
|
|
6365
6476
|
function ownUnpersistedBatchHead(root, record, head) {
|
|
@@ -6410,23 +6521,23 @@ function ownUnpersistedBatchHead(root, record, head) {
|
|
|
6410
6521
|
var confirmations = new Map;
|
|
6411
6522
|
function retainReconfirmation(nonce, snapshot) {
|
|
6412
6523
|
if (confirmations.has(nonce))
|
|
6413
|
-
|
|
6524
|
+
refuse2();
|
|
6414
6525
|
confirmations.set(nonce, snapshot);
|
|
6415
6526
|
}
|
|
6416
6527
|
function takeReconfirmation(nonce) {
|
|
6417
6528
|
const found = confirmations.get(nonce);
|
|
6418
6529
|
confirmations.delete(nonce);
|
|
6419
6530
|
if (!found)
|
|
6420
|
-
|
|
6531
|
+
refuse2();
|
|
6421
6532
|
return found;
|
|
6422
6533
|
}
|
|
6423
6534
|
async function captureBatchReconfirmation(root, record, children) {
|
|
6424
6535
|
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
|
-
|
|
6536
|
+
refuse2();
|
|
6426
6537
|
const assertNoCommitEvidence = () => {
|
|
6427
6538
|
for (const child of record.children) {
|
|
6428
6539
|
if (!id.test(child.task_id))
|
|
6429
|
-
|
|
6540
|
+
refuse2();
|
|
6430
6541
|
const path = batchCommitEvidencePath(record.batch_id, child.task_id);
|
|
6431
6542
|
let missing = false;
|
|
6432
6543
|
try {
|
|
@@ -6435,17 +6546,17 @@ async function captureBatchReconfirmation(root, record, children) {
|
|
|
6435
6546
|
if (error instanceof Error && error.message === `source_missing: ${path}`)
|
|
6436
6547
|
missing = true;
|
|
6437
6548
|
else
|
|
6438
|
-
|
|
6549
|
+
refuse2();
|
|
6439
6550
|
}
|
|
6440
6551
|
if (!missing)
|
|
6441
|
-
|
|
6552
|
+
refuse2();
|
|
6442
6553
|
}
|
|
6443
6554
|
};
|
|
6444
6555
|
assertNoCommitEvidence();
|
|
6445
6556
|
const statePath = batchStatePath(record.batch_id);
|
|
6446
6557
|
const stateBytes = readSecureProjectBytes(root, statePath);
|
|
6447
6558
|
if (JSON.stringify(JSON.parse(decode(stateBytes))) !== JSON.stringify(record))
|
|
6448
|
-
|
|
6559
|
+
refuse2();
|
|
6449
6560
|
const files = new Map;
|
|
6450
6561
|
const capture = (path) => {
|
|
6451
6562
|
const bytes = readSecureProjectBytes(root, path);
|
|
@@ -6457,14 +6568,14 @@ async function captureBatchReconfirmation(root, record, children) {
|
|
|
6457
6568
|
let scope = null;
|
|
6458
6569
|
const workspace = readWorkspaceStateRaw(root);
|
|
6459
6570
|
if (workspace.state.current_working !== null)
|
|
6460
|
-
|
|
6571
|
+
refuse2();
|
|
6461
6572
|
for (const [index, child] of children.entries()) {
|
|
6462
6573
|
const previous = record.children[index];
|
|
6463
6574
|
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
|
-
|
|
6575
|
+
refuse2();
|
|
6465
6576
|
const old = parseTaskIntentV1(JSON.parse(git2(root, ["show", `${record.base_head}:${child.intent_path}`])));
|
|
6466
6577
|
if (old.task_id !== child.task_id)
|
|
6467
|
-
|
|
6578
|
+
refuse2();
|
|
6468
6579
|
const oldHash = canonicalIntentHash(old);
|
|
6469
6580
|
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
6581
|
const currentBytes = capture(child.intent_path);
|
|
@@ -6472,46 +6583,46 @@ async function captureBatchReconfirmation(root, record, children) {
|
|
|
6472
6583
|
const currentHash = canonicalIntentHash(current);
|
|
6473
6584
|
const tracked = readTaskIntent(root, child.task_id, child.intent_path);
|
|
6474
6585
|
if (currentHash !== tracked.content_hash || currentHash !== child.intent_content_hash || current.revision !== child.intent_revision)
|
|
6475
|
-
|
|
6586
|
+
refuse2();
|
|
6476
6587
|
if (currentHash === oldHash)
|
|
6477
6588
|
continue;
|
|
6478
6589
|
if (scope !== null || previous.state === "pending" || previous.state === "skipped_blocked" || current.revision <= old.revision || current.goal !== old.goal || current.owner !== old.owner)
|
|
6479
|
-
|
|
6590
|
+
refuse2();
|
|
6480
6591
|
const settled = readSettledRunEvidence(root, child.task_id);
|
|
6481
6592
|
if (!settled || settled.lifecycle !== "done")
|
|
6482
|
-
|
|
6593
|
+
refuse2();
|
|
6483
6594
|
const run = settled.run_id;
|
|
6484
6595
|
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
|
-
|
|
6596
|
+
refuse2();
|
|
6486
6597
|
const pair = readAuditTaskPair(root, child.task_id, run);
|
|
6487
6598
|
if (!pair || pair.record.contract !== "assurance_kernel/task_record/v4")
|
|
6488
|
-
|
|
6599
|
+
refuse2();
|
|
6489
6600
|
const r = pair.record;
|
|
6490
6601
|
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
|
-
|
|
6602
|
+
refuse2();
|
|
6492
6603
|
const terminal = r.history.at(-1);
|
|
6493
6604
|
if (terminal?.type !== "complete" || terminal.from_state !== "active:frozen" || terminal.to_state !== "done:frozen" || terminal.id !== pair.proof.terminal_event_id)
|
|
6494
|
-
|
|
6605
|
+
refuse2();
|
|
6495
6606
|
const recordPath = auditRunRecordPath(child.task_id, run);
|
|
6496
6607
|
const proofPath = auditRunTerminalProofPath(child.task_id, run);
|
|
6497
6608
|
if (!capture(recordPath).equals(Buffer.from(settled.record_json)))
|
|
6498
|
-
|
|
6609
|
+
refuse2();
|
|
6499
6610
|
const capturedProof = JSON.parse(decode(capture(proofPath)));
|
|
6500
6611
|
if (!matchesLocalProof(settled.proof_json, capturedProof) || !matchesLocalProof(settled.proof_json, pair.proof))
|
|
6501
|
-
|
|
6612
|
+
refuse2();
|
|
6502
6613
|
const identity = taskDeliveryIdentity(root, r);
|
|
6503
6614
|
const projection = await projectAssurance(root, child.task_id, (cwd, task) => {
|
|
6504
6615
|
if (task.contract !== "assurance_kernel/task_record/v4" || !task.git_base_head)
|
|
6505
|
-
|
|
6616
|
+
refuse2();
|
|
6506
6617
|
return taskDeliveryIdentity(cwd, task);
|
|
6507
6618
|
});
|
|
6508
6619
|
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
|
-
|
|
6620
|
+
refuse2();
|
|
6510
6621
|
scope = r.intent_snapshot.scope_hint;
|
|
6511
6622
|
changed.push({ task: child.task_id, run, identity: JSON.stringify(identity), settled: JSON.stringify(settled) });
|
|
6512
6623
|
}
|
|
6513
6624
|
if (computeBatchPlanDigest(oldChildren) !== record.plan_digest || changed.length !== 1 || scope === null)
|
|
6514
|
-
|
|
6625
|
+
refuse2();
|
|
6515
6626
|
const confirmedScope = scope;
|
|
6516
6627
|
const owner = changed[0];
|
|
6517
6628
|
const status = () => git2(root, ["status", "--porcelain=v1", "-z", "--untracked-files=all", "--ignore-submodules=none"]);
|
|
@@ -6520,42 +6631,42 @@ async function captureBatchReconfirmation(root, record, children) {
|
|
|
6520
6631
|
for (const entry of entries) {
|
|
6521
6632
|
const code = entry.slice(0, 2), path = entry.slice(3);
|
|
6522
6633
|
if (code[1] !== " " || !["A", "M", "D"].includes(code[0]) || !path.startsWith(`${auditRunDirPath(owner.task, owner.run)}/`) && !confirmedScope.some((s) => pathMatchesScope(path, s)))
|
|
6523
|
-
|
|
6634
|
+
refuse2();
|
|
6524
6635
|
}
|
|
6525
6636
|
const flagsBytes = git2(root, ["ls-files", "-v"]);
|
|
6526
6637
|
const flags = flagsBytes.split(`
|
|
6527
6638
|
`).filter(Boolean);
|
|
6528
6639
|
if (flags.some((line) => line[0] === "S" || line[0] !== line[0]?.toUpperCase()))
|
|
6529
|
-
|
|
6640
|
+
refuse2();
|
|
6530
6641
|
const head = () => git2(root, ["rev-parse", "HEAD"]).trim();
|
|
6531
6642
|
const branch = () => git2(root, ["symbolic-ref", "--short", "HEAD"]).trim();
|
|
6532
6643
|
if (head() !== record.base_head || branch() !== record.branch)
|
|
6533
|
-
|
|
6644
|
+
refuse2();
|
|
6534
6645
|
const assertDeliveryUnchanged = () => {
|
|
6535
6646
|
for (const c of changed) {
|
|
6536
6647
|
let settled;
|
|
6537
6648
|
try {
|
|
6538
6649
|
settled = readSettledRunEvidence(root, c.task);
|
|
6539
6650
|
} catch {
|
|
6540
|
-
|
|
6651
|
+
refuse2();
|
|
6541
6652
|
}
|
|
6542
6653
|
if (!settled || settled.run_id !== c.run || JSON.stringify(settled) !== c.settled)
|
|
6543
|
-
|
|
6654
|
+
refuse2();
|
|
6544
6655
|
const pair = readAuditTaskPair(root, c.task, c.run);
|
|
6545
6656
|
if (!pair || pair.record.contract !== "assurance_kernel/task_record/v4" || !pair.record.git_base_head)
|
|
6546
|
-
|
|
6657
|
+
refuse2();
|
|
6547
6658
|
const diff = taskDeliveryIdentity(root, pair.record);
|
|
6548
6659
|
if (JSON.stringify(diff) !== c.identity)
|
|
6549
|
-
|
|
6660
|
+
refuse2();
|
|
6550
6661
|
}
|
|
6551
6662
|
};
|
|
6552
6663
|
const assertUnchanged = () => {
|
|
6553
6664
|
assertNoCommitEvidence();
|
|
6554
6665
|
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
|
-
|
|
6666
|
+
refuse2();
|
|
6556
6667
|
for (const [path, bytes] of files)
|
|
6557
6668
|
if (!readSecureProjectBytes(root, path).equals(bytes))
|
|
6558
|
-
|
|
6669
|
+
refuse2();
|
|
6559
6670
|
assertDeliveryUnchanged();
|
|
6560
6671
|
};
|
|
6561
6672
|
assertUnchanged();
|
|
@@ -9012,8 +9123,38 @@ async function activeTaskNotice(root) {
|
|
|
9012
9123
|
].join(" ");
|
|
9013
9124
|
}
|
|
9014
9125
|
|
|
9126
|
+
// plugins/immune-brain/runtime/assurance/alias_file.ts
|
|
9127
|
+
import { closeSync as closeSync7, constants as constants5, fstatSync as fstatSync5, openSync as openSync7, readSync as readSync2 } from "node:fs";
|
|
9128
|
+
var MAX_ALIAS_BYTES = 64 * 1024;
|
|
9129
|
+
function readAliasFile(path, maxBytes = MAX_ALIAS_BYTES) {
|
|
9130
|
+
let fd;
|
|
9131
|
+
try {
|
|
9132
|
+
fd = openSync7(path, constants5.O_RDONLY | constants5.O_NONBLOCK);
|
|
9133
|
+
} catch {
|
|
9134
|
+
return null;
|
|
9135
|
+
}
|
|
9136
|
+
try {
|
|
9137
|
+
const stat = fstatSync5(fd);
|
|
9138
|
+
if (!stat.isFile() || stat.size > maxBytes)
|
|
9139
|
+
return null;
|
|
9140
|
+
const buf = Buffer.alloc(maxBytes + 1);
|
|
9141
|
+
const read = readSync2(fd, buf, 0, buf.length, 0);
|
|
9142
|
+
return read > maxBytes ? null : buf.toString("utf8", 0, read);
|
|
9143
|
+
} catch {
|
|
9144
|
+
return null;
|
|
9145
|
+
} finally {
|
|
9146
|
+
closeSync7(fd);
|
|
9147
|
+
}
|
|
9148
|
+
}
|
|
9149
|
+
|
|
9015
9150
|
// plugins/immune-brain/runtime/claude/lane_guard.ts
|
|
9016
9151
|
var FILE_TOOLS = new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
|
|
9152
|
+
var GATED_MCP_TOOL = /^mcp__.*immune-brain.*__(enroll|start_unattended_batch)$/;
|
|
9153
|
+
function denyPreToolUse(reason) {
|
|
9154
|
+
return JSON.stringify({
|
|
9155
|
+
hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: reason }
|
|
9156
|
+
});
|
|
9157
|
+
}
|
|
9017
9158
|
function laneGuardHookOutput(payload) {
|
|
9018
9159
|
if (payload.hook_event_name !== "PreToolUse" || !FILE_TOOLS.has(String(payload.tool_name)))
|
|
9019
9160
|
return null;
|
|
@@ -9022,9 +9163,7 @@ function laneGuardHookOutput(payload) {
|
|
|
9022
9163
|
const refusal = laneWriteRefusal(cwd, input.file_path ?? input.notebook_path);
|
|
9023
9164
|
if (!refusal)
|
|
9024
9165
|
return null;
|
|
9025
|
-
return
|
|
9026
|
-
hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: refusal }
|
|
9027
|
-
});
|
|
9166
|
+
return denyPreToolUse(refusal);
|
|
9028
9167
|
}
|
|
9029
9168
|
async function activeTaskHookOutput(payload) {
|
|
9030
9169
|
if (payload.hook_event_name !== "UserPromptSubmit")
|
|
@@ -9035,12 +9174,152 @@ async function activeTaskHookOutput(payload) {
|
|
|
9035
9174
|
return null;
|
|
9036
9175
|
return JSON.stringify({ hookSpecificOutput: { hookEventName: "UserPromptSubmit", additionalContext: notice } });
|
|
9037
9176
|
}
|
|
9177
|
+
function entryLogPath(sessionId, root) {
|
|
9178
|
+
return join9(cacheDir(root), `${sessionHash(sessionId)}.entry`);
|
|
9179
|
+
}
|
|
9180
|
+
function admissionPath(toolUseId, root) {
|
|
9181
|
+
return join9(cacheDir(root), `${sessionHash(toolUseId)}.admit`);
|
|
9182
|
+
}
|
|
9183
|
+
function sessionIdOf(payload) {
|
|
9184
|
+
return typeof payload.session_id === "string" ? payload.session_id : "";
|
|
9185
|
+
}
|
|
9186
|
+
function cwdOf(payload) {
|
|
9187
|
+
return typeof payload.cwd === "string" && payload.cwd ? payload.cwd : process.cwd();
|
|
9188
|
+
}
|
|
9189
|
+
function claudeAliasReader(cwd, home) {
|
|
9190
|
+
return (name) => {
|
|
9191
|
+
for (const dir of [join9(cwd, ".claude", "commands"), join9(home, ".claude", "commands")]) {
|
|
9192
|
+
const body = readAliasFile(join9(dir, `${name}.md`));
|
|
9193
|
+
if (body !== null)
|
|
9194
|
+
return body;
|
|
9195
|
+
}
|
|
9196
|
+
return null;
|
|
9197
|
+
};
|
|
9198
|
+
}
|
|
9199
|
+
function readPhaseEntryState(sessionId, root = tmpdir2(), read = readPrivate) {
|
|
9200
|
+
if (!sessionId)
|
|
9201
|
+
return null;
|
|
9202
|
+
const path = entryLogPath(sessionId, root);
|
|
9203
|
+
try {
|
|
9204
|
+
lstatSync10(path);
|
|
9205
|
+
} catch (error) {
|
|
9206
|
+
return error.code === "ENOENT" ? NO_PHASE_ENTRY : null;
|
|
9207
|
+
}
|
|
9208
|
+
if (!ensurePrivateDir(cacheDir(root)))
|
|
9209
|
+
return null;
|
|
9210
|
+
let text;
|
|
9211
|
+
try {
|
|
9212
|
+
text = read(path);
|
|
9213
|
+
} catch {
|
|
9214
|
+
return null;
|
|
9215
|
+
}
|
|
9216
|
+
return text === undefined ? null : reducePhaseEntryLog(text);
|
|
9217
|
+
}
|
|
9218
|
+
function dropPrivate(path) {
|
|
9219
|
+
try {
|
|
9220
|
+
const stat = lstatSync10(path);
|
|
9221
|
+
if (ownedByUs(stat) && stat.isFile())
|
|
9222
|
+
rmSync5(path, { force: true });
|
|
9223
|
+
} catch {}
|
|
9224
|
+
}
|
|
9225
|
+
function appendPhaseEntry(sessionId, entry, root, locations) {
|
|
9226
|
+
const path = entryLogPath(sessionId, root);
|
|
9227
|
+
let recorded = false;
|
|
9228
|
+
try {
|
|
9229
|
+
recorded = (locations.append ?? appendPrivate)(path, cacheDir(root), phaseEntryLogLine(entry));
|
|
9230
|
+
} catch {}
|
|
9231
|
+
if (!recorded)
|
|
9232
|
+
dropPrivate(path);
|
|
9233
|
+
return recorded;
|
|
9234
|
+
}
|
|
9235
|
+
function recordPhaseEntryPrompt(payload, locations = {}) {
|
|
9236
|
+
if (payload.hook_event_name !== "UserPromptSubmit")
|
|
9237
|
+
return false;
|
|
9238
|
+
const sessionId = sessionIdOf(payload);
|
|
9239
|
+
if (!sessionId)
|
|
9240
|
+
return false;
|
|
9241
|
+
const prompt = typeof payload.prompt === "string" ? payload.prompt : "";
|
|
9242
|
+
const entry = recognizePhaseEntry(prompt, claudeAliasReader(cwdOf(payload), locations.home ?? homedir()));
|
|
9243
|
+
return appendPhaseEntry(sessionId, entry, locations.root ?? tmpdir2(), locations);
|
|
9244
|
+
}
|
|
9245
|
+
function gatedOperation(payload) {
|
|
9246
|
+
const toolName = String(payload.tool_name ?? "");
|
|
9247
|
+
if (toolName === "Skill") {
|
|
9248
|
+
const input = payload.tool_input ?? {};
|
|
9249
|
+
const skill = typeof input.skill === "string" ? packagedPhaseSkill(input.skill) : null;
|
|
9250
|
+
return skill ? { kind: "skill", skill } : null;
|
|
9251
|
+
}
|
|
9252
|
+
const mcp = GATED_MCP_TOOL.exec(toolName);
|
|
9253
|
+
return mcp ? { kind: "tool", name: mcp[1] } : null;
|
|
9254
|
+
}
|
|
9255
|
+
function phaseEntryGateHookOutput(payload, locations = {}) {
|
|
9256
|
+
if (payload.hook_event_name !== "PreToolUse")
|
|
9257
|
+
return null;
|
|
9258
|
+
const operation = gatedOperation(payload);
|
|
9259
|
+
if (!operation)
|
|
9260
|
+
return null;
|
|
9261
|
+
const root = locations.root ?? tmpdir2();
|
|
9262
|
+
if (operation.kind === "skill" && operation.skill !== "imm-run") {
|
|
9263
|
+
let activeClaim = false;
|
|
9264
|
+
try {
|
|
9265
|
+
activeClaim = readActiveClaimTaskId(cwdOf(payload)) !== null;
|
|
9266
|
+
} catch {}
|
|
9267
|
+
const sessionId = sessionIdOf(payload);
|
|
9268
|
+
if (!activeClaim && sessionId)
|
|
9269
|
+
appendPhaseEntry(sessionId, "plan", root, locations);
|
|
9270
|
+
return null;
|
|
9271
|
+
}
|
|
9272
|
+
let state = null;
|
|
9273
|
+
try {
|
|
9274
|
+
state = readPhaseEntryState(sessionIdOf(payload), root, locations.read);
|
|
9275
|
+
} catch {}
|
|
9276
|
+
const admission = admitPhaseOperation(operation, state);
|
|
9277
|
+
if (!admission.ok)
|
|
9278
|
+
return denyPreToolUse(phaseRefusalMessage(admission));
|
|
9279
|
+
if (operation.kind === "skill")
|
|
9280
|
+
return null;
|
|
9281
|
+
const toolUseId = typeof payload.tool_use_id === "string" ? payload.tool_use_id : "";
|
|
9282
|
+
let recorded = false;
|
|
9283
|
+
try {
|
|
9284
|
+
recorded = toolUseId !== "" && (locations.append ?? appendPrivate)(admissionPath(toolUseId, root), cacheDir(root), `${JSON.stringify({ v: 1, operation: operation.name })}
|
|
9285
|
+
`);
|
|
9286
|
+
} catch {}
|
|
9287
|
+
if (recorded)
|
|
9288
|
+
return null;
|
|
9289
|
+
return denyPreToolUse(`${RUN_ENTRY_REQUIRED}: the admission for this ${operation.name} call could not be recorded; recovery: ${PHASE_ENTRY_RECOVERY[RUN_ENTRY_REQUIRED]}`);
|
|
9290
|
+
}
|
|
9291
|
+
function consumePhaseAdmission(toolUseId, operation, root = tmpdir2()) {
|
|
9292
|
+
if (!toolUseId || !ensurePrivateDir(cacheDir(root)))
|
|
9293
|
+
return false;
|
|
9294
|
+
const path = admissionPath(toolUseId, root);
|
|
9295
|
+
let text;
|
|
9296
|
+
try {
|
|
9297
|
+
text = readPrivate(path);
|
|
9298
|
+
} catch {
|
|
9299
|
+
return false;
|
|
9300
|
+
}
|
|
9301
|
+
if (text === undefined)
|
|
9302
|
+
return false;
|
|
9303
|
+
dropPrivate(path);
|
|
9304
|
+
const lines = text.split(`
|
|
9305
|
+
`).filter(Boolean);
|
|
9306
|
+
if (lines.length === 0)
|
|
9307
|
+
return false;
|
|
9308
|
+
return lines.every((line) => {
|
|
9309
|
+
try {
|
|
9310
|
+
const record = JSON.parse(line);
|
|
9311
|
+
return record?.v === 1 && record.operation === operation;
|
|
9312
|
+
} catch {
|
|
9313
|
+
return false;
|
|
9314
|
+
}
|
|
9315
|
+
});
|
|
9316
|
+
}
|
|
9038
9317
|
|
|
9039
9318
|
// plugins/immune-brain/runtime/assurance/verdict_authority.ts
|
|
9040
9319
|
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
9041
9320
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
9042
9321
|
import { existsSync as existsSync9 } from "node:fs";
|
|
9043
|
-
import { join as
|
|
9322
|
+
import { join as join13, resolve as resolve12 } from "node:path";
|
|
9044
9323
|
|
|
9045
9324
|
// plugins/immune-brain/runtime/kernel/application.ts
|
|
9046
9325
|
function applyTaskAction(input) {
|
|
@@ -9698,15 +9977,15 @@ import { execFileSync as execFileSync2 } from "node:child_process";
|
|
|
9698
9977
|
import { createHash as createHash14 } from "node:crypto";
|
|
9699
9978
|
import {
|
|
9700
9979
|
mkdtempSync,
|
|
9701
|
-
rmSync as
|
|
9980
|
+
rmSync as rmSync6,
|
|
9702
9981
|
writeFileSync as writeFileSync6,
|
|
9703
9982
|
statSync as statSync3,
|
|
9704
9983
|
readFileSync as readFileSync10,
|
|
9705
9984
|
realpathSync as realpathSync8,
|
|
9706
9985
|
chmodSync
|
|
9707
9986
|
} from "node:fs";
|
|
9708
|
-
import { tmpdir as
|
|
9709
|
-
import { join as
|
|
9987
|
+
import { tmpdir as tmpdir3 } from "node:os";
|
|
9988
|
+
import { join as join10 } from "node:path";
|
|
9710
9989
|
var MAX_REVIEW_BUNDLE_BYTES = 2 * 1024 * 1024;
|
|
9711
9990
|
function bundleDigest(bundle) {
|
|
9712
9991
|
return `sha256:${createHash14("sha256").update(JSON.stringify(bundle)).digest("hex")}`;
|
|
@@ -9930,9 +10209,9 @@ function publishReviewRevision(root, snapshot, diffHash, taskId) {
|
|
|
9930
10209
|
throw new Error("review revision base has invalid identity");
|
|
9931
10210
|
if (!REVISION_DIFF_HASH.test(diffHash))
|
|
9932
10211
|
throw new Error("review revision diff hash has invalid identity");
|
|
9933
|
-
const indexDirectory = mkdtempSync(
|
|
10212
|
+
const indexDirectory = mkdtempSync(join10(tmpdir3(), "imm-review-index-"));
|
|
9934
10213
|
try {
|
|
9935
|
-
const indexFile =
|
|
10214
|
+
const indexFile = join10(indexDirectory, "index");
|
|
9936
10215
|
const env = { GIT_INDEX_FILE: indexFile };
|
|
9937
10216
|
gitEvidence(root, ["read-tree", snapshot.base_tree], env);
|
|
9938
10217
|
for (const [path, entry] of Object.entries(snapshot.changed_paths)) {
|
|
@@ -9982,7 +10261,7 @@ function publishReviewRevision(root, snapshot, diffHash, taskId) {
|
|
|
9982
10261
|
diff_hash: diffHash
|
|
9983
10262
|
};
|
|
9984
10263
|
} finally {
|
|
9985
|
-
|
|
10264
|
+
rmSync6(indexDirectory, { recursive: true, force: true });
|
|
9986
10265
|
}
|
|
9987
10266
|
}
|
|
9988
10267
|
function publishInput(root, input) {
|
|
@@ -10025,19 +10304,19 @@ function captureReviewManifest(root, input) {
|
|
|
10025
10304
|
return manifest;
|
|
10026
10305
|
}
|
|
10027
10306
|
function writeNativeReviewEvidence(payload) {
|
|
10028
|
-
const rawDirectory = mkdtempSync(
|
|
10307
|
+
const rawDirectory = mkdtempSync(join10(tmpdir3(), "imm-canary-native-review-"));
|
|
10029
10308
|
try {
|
|
10030
10309
|
const directory = realpathSync8(rawDirectory);
|
|
10031
10310
|
chmodSync(directory, 493);
|
|
10032
|
-
const path =
|
|
10311
|
+
const path = join10(directory, "evidence.json");
|
|
10033
10312
|
writeFileSync6(path, JSON.stringify(payload), { encoding: "utf8", mode: 420, flag: "wx" });
|
|
10034
10313
|
assertReviewArtifact(path);
|
|
10035
10314
|
return {
|
|
10036
10315
|
path,
|
|
10037
|
-
remove: () =>
|
|
10316
|
+
remove: () => rmSync6(directory, { recursive: true, force: true })
|
|
10038
10317
|
};
|
|
10039
10318
|
} catch (error) {
|
|
10040
|
-
|
|
10319
|
+
rmSync6(rawDirectory, { recursive: true, force: true });
|
|
10041
10320
|
throw error;
|
|
10042
10321
|
}
|
|
10043
10322
|
}
|
|
@@ -10062,8 +10341,8 @@ import { createHash as createHash17, randomUUID as randomUUID6 } from "node:cryp
|
|
|
10062
10341
|
// plugins/immune-brain/runtime/assurance/verification.ts
|
|
10063
10342
|
import { createHash as createHash15, randomBytes } from "node:crypto";
|
|
10064
10343
|
import { execFileSync as execFileSync3, spawn as spawn2 } from "node:child_process";
|
|
10065
|
-
import { accessSync, constants as
|
|
10066
|
-
import { delimiter, isAbsolute as isAbsolute5, join as
|
|
10344
|
+
import { accessSync, constants as constants6, readFileSync as readFileSync11, readdirSync as readdirSync5, realpathSync as realpathSync9, statSync as statSync4 } from "node:fs";
|
|
10345
|
+
import { delimiter, isAbsolute as isAbsolute5, join as join11, relative as relative5, resolve as resolve11, sep as sep6 } from "node:path";
|
|
10067
10346
|
class VerificationAbortedError extends Error {
|
|
10068
10347
|
constructor() {
|
|
10069
10348
|
super("fixed verification aborted");
|
|
@@ -10099,9 +10378,9 @@ function verificationPath() {
|
|
|
10099
10378
|
}
|
|
10100
10379
|
function toolPath(name, path) {
|
|
10101
10380
|
for (const dir of path.split(delimiter).filter(isAbsolute5)) {
|
|
10102
|
-
const candidate =
|
|
10381
|
+
const candidate = join11(dir, name);
|
|
10103
10382
|
try {
|
|
10104
|
-
accessSync(candidate,
|
|
10383
|
+
accessSync(candidate, constants6.X_OK);
|
|
10105
10384
|
if (statSync4(candidate).isFile())
|
|
10106
10385
|
return candidate;
|
|
10107
10386
|
} catch {}
|
|
@@ -10111,7 +10390,7 @@ function toolPath(name, path) {
|
|
|
10111
10390
|
function identity(path) {
|
|
10112
10391
|
try {
|
|
10113
10392
|
const real = realpathSync9(path);
|
|
10114
|
-
accessSync(real,
|
|
10393
|
+
accessSync(real, constants6.X_OK);
|
|
10115
10394
|
const stat = statSync4(real);
|
|
10116
10395
|
if (!stat.isFile())
|
|
10117
10396
|
throw new Error("not a file");
|
|
@@ -10208,7 +10487,7 @@ async function runFixedVerification(root, command, frozen, options) {
|
|
|
10208
10487
|
};
|
|
10209
10488
|
const procEnviron = (pid) => {
|
|
10210
10489
|
try {
|
|
10211
|
-
return readFileSync11(
|
|
10490
|
+
return readFileSync11(join11(procRoot, String(pid), "environ"), "utf8");
|
|
10212
10491
|
} catch {
|
|
10213
10492
|
return null;
|
|
10214
10493
|
}
|
|
@@ -10216,7 +10495,7 @@ async function runFixedVerification(root, command, frozen, options) {
|
|
|
10216
10495
|
const procSession = (entry) => {
|
|
10217
10496
|
let stat;
|
|
10218
10497
|
try {
|
|
10219
|
-
stat = readFileSync11(
|
|
10498
|
+
stat = readFileSync11(join11(procRoot, entry, "stat"), "utf8");
|
|
10220
10499
|
} catch (error) {
|
|
10221
10500
|
if (error.code === "ENOENT")
|
|
10222
10501
|
return null;
|
|
@@ -10427,7 +10706,7 @@ function createInvocationRegistry() {
|
|
|
10427
10706
|
// plugins/immune-brain/runtime/role_prompt_bridge.ts
|
|
10428
10707
|
import { createHash as createHash16 } from "node:crypto";
|
|
10429
10708
|
import { existsSync as existsSync8, readFileSync as readFileSync12 } from "node:fs";
|
|
10430
|
-
import { join as
|
|
10709
|
+
import { join as join12, dirname as dirname8 } from "node:path";
|
|
10431
10710
|
import { fileURLToPath } from "node:url";
|
|
10432
10711
|
var RUNTIME_DIR = dirname8(fileURLToPath(import.meta.url));
|
|
10433
10712
|
var INTERNAL_ROLE_PROMPTS = {
|
|
@@ -10489,14 +10768,14 @@ function roleSpec(role) {
|
|
|
10489
10768
|
}
|
|
10490
10769
|
function rolePromptSearchDirs(moduleDir) {
|
|
10491
10770
|
return [
|
|
10492
|
-
|
|
10493
|
-
|
|
10771
|
+
join12(moduleDir, "..", "dist", "role-prompts"),
|
|
10772
|
+
join12(moduleDir, "..", "role-prompts")
|
|
10494
10773
|
];
|
|
10495
10774
|
}
|
|
10496
10775
|
function loadRolePrompt(role) {
|
|
10497
10776
|
const spec = roleSpec(role);
|
|
10498
10777
|
for (const dir of rolePromptSearchDirs(RUNTIME_DIR)) {
|
|
10499
|
-
const path =
|
|
10778
|
+
const path = join12(dir, spec.file);
|
|
10500
10779
|
if (existsSync8(path))
|
|
10501
10780
|
return readFileSync12(path, "utf8");
|
|
10502
10781
|
}
|
|
@@ -11769,7 +12048,7 @@ function stagePlanningArtifactTransition(root, record) {
|
|
|
11769
12048
|
intentArchive,
|
|
11770
12049
|
...specActive ? [specActive, specActive.replace("docs/specs/", "docs/specs/archive/")] : []
|
|
11771
12050
|
];
|
|
11772
|
-
const paths = candidates.filter((path) => existsSync9(
|
|
12051
|
+
const paths = candidates.filter((path) => existsSync9(join13(root, path)) || execFileSync4("git", ["ls-files", "--cached", "--", path], { cwd: root, encoding: "utf8" }).trim().length > 0);
|
|
11773
12052
|
if (paths.length === 0)
|
|
11774
12053
|
return;
|
|
11775
12054
|
execFileSync4("git", ["add", "--", ...paths], {
|
|
@@ -11865,7 +12144,7 @@ function createVerdictAuthority(options, registry = createMutationAuthorityRegis
|
|
|
11865
12144
|
// plugins/immune-brain/runtime/claude/kernel_ports.ts
|
|
11866
12145
|
import { existsSync as existsSync14, readFileSync as readFileSync17, writeFileSync as writeFileSync10 } from "node:fs";
|
|
11867
12146
|
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
11868
|
-
import { join as
|
|
12147
|
+
import { join as join22 } from "node:path";
|
|
11869
12148
|
|
|
11870
12149
|
// plugins/immune-brain/runtime/kernel/enrollment_authority.ts
|
|
11871
12150
|
var ENROLLMENT_CAPABILITY_BRAND = Symbol.for("assurance-kernel.enrollment-capability-brand");
|
|
@@ -12239,9 +12518,9 @@ import { createHash as createHash19 } from "node:crypto";
|
|
|
12239
12518
|
// plugins/immune-brain/runtime/assurance/delivery_workspace.ts
|
|
12240
12519
|
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
12241
12520
|
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
|
|
12521
|
+
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";
|
|
12522
|
+
import { tmpdir as tmpdir4 } from "node:os";
|
|
12523
|
+
import { dirname as dirname9, isAbsolute as isAbsolute6, join as join14, parse, relative as relative6, resolve as resolve13, sep as sep7 } from "node:path";
|
|
12245
12524
|
|
|
12246
12525
|
class DeliveryWorkspaceError extends Error {
|
|
12247
12526
|
constructor(message) {
|
|
@@ -12253,7 +12532,7 @@ function removeTaskOwnedTree(root) {
|
|
|
12253
12532
|
const makeDirectoriesWritable = (path) => {
|
|
12254
12533
|
let stat;
|
|
12255
12534
|
try {
|
|
12256
|
-
stat =
|
|
12535
|
+
stat = lstatSync11(path);
|
|
12257
12536
|
} catch (error) {
|
|
12258
12537
|
if (error.code === "ENOENT")
|
|
12259
12538
|
return;
|
|
@@ -12263,10 +12542,10 @@ function removeTaskOwnedTree(root) {
|
|
|
12263
12542
|
return;
|
|
12264
12543
|
chmodSync2(path, stat.mode & 4095 | 448);
|
|
12265
12544
|
for (const name of readdirSync6(path))
|
|
12266
|
-
makeDirectoriesWritable(
|
|
12545
|
+
makeDirectoriesWritable(join14(path, name));
|
|
12267
12546
|
};
|
|
12268
12547
|
makeDirectoriesWritable(root);
|
|
12269
|
-
|
|
12548
|
+
rmSync7(root, { recursive: true, force: true });
|
|
12270
12549
|
}
|
|
12271
12550
|
var GIT_OBJECT_ID5 = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/;
|
|
12272
12551
|
function isolatedGitEnv(extra = {}) {
|
|
@@ -12314,9 +12593,9 @@ function resolvedSymlinkTarget(path) {
|
|
|
12314
12593
|
current = dirname9(current);
|
|
12315
12594
|
continue;
|
|
12316
12595
|
}
|
|
12317
|
-
const next =
|
|
12596
|
+
const next = join14(current, part);
|
|
12318
12597
|
try {
|
|
12319
|
-
if (!
|
|
12598
|
+
if (!lstatSync11(next).isSymbolicLink()) {
|
|
12320
12599
|
current = next;
|
|
12321
12600
|
continue;
|
|
12322
12601
|
}
|
|
@@ -12337,8 +12616,8 @@ function resolvedSymlinkTarget(path) {
|
|
|
12337
12616
|
function assertNoEscapingSymlinks(root, dir = root) {
|
|
12338
12617
|
const realRoot = realpathSync10(root);
|
|
12339
12618
|
for (const name of readdirSync6(dir)) {
|
|
12340
|
-
const path =
|
|
12341
|
-
const stat =
|
|
12619
|
+
const path = join14(dir, name);
|
|
12620
|
+
const stat = lstatSync11(path);
|
|
12342
12621
|
if (stat.isSymbolicLink()) {
|
|
12343
12622
|
assertInside(realRoot, resolvedSymlinkTarget(path), `symlink ${relative6(root, path)}`);
|
|
12344
12623
|
} else if (stat.isDirectory()) {
|
|
@@ -12347,41 +12626,41 @@ function assertNoEscapingSymlinks(root, dir = root) {
|
|
|
12347
12626
|
}
|
|
12348
12627
|
}
|
|
12349
12628
|
function writeDeliveryTree(sourceRoot, snapshot) {
|
|
12350
|
-
const indexDirectory = mkdtempSync2(
|
|
12629
|
+
const indexDirectory = mkdtempSync2(join14(tmpdir4(), "imm-delivery-index-"));
|
|
12351
12630
|
try {
|
|
12352
12631
|
git5(sourceRoot, ["read-tree", snapshot.base_tree], {
|
|
12353
|
-
GIT_INDEX_FILE:
|
|
12354
|
-
GIT_DIR:
|
|
12632
|
+
GIT_INDEX_FILE: join14(indexDirectory, "index"),
|
|
12633
|
+
GIT_DIR: join14(sourceRoot, ".git")
|
|
12355
12634
|
});
|
|
12356
12635
|
for (const [path, entry] of Object.entries(snapshot.changed_paths)) {
|
|
12357
12636
|
if (entry.oid && entry.mode) {
|
|
12358
12637
|
git5(sourceRoot, ["update-index", "--add", "--cacheinfo", `${entry.mode},${entry.oid},${path}`], {
|
|
12359
|
-
GIT_INDEX_FILE:
|
|
12360
|
-
GIT_DIR:
|
|
12638
|
+
GIT_INDEX_FILE: join14(indexDirectory, "index"),
|
|
12639
|
+
GIT_DIR: join14(sourceRoot, ".git")
|
|
12361
12640
|
});
|
|
12362
12641
|
} else {
|
|
12363
12642
|
git5(sourceRoot, ["update-index", "--force-remove", "--", path], {
|
|
12364
|
-
GIT_INDEX_FILE:
|
|
12365
|
-
GIT_DIR:
|
|
12643
|
+
GIT_INDEX_FILE: join14(indexDirectory, "index"),
|
|
12644
|
+
GIT_DIR: join14(sourceRoot, ".git")
|
|
12366
12645
|
});
|
|
12367
12646
|
}
|
|
12368
12647
|
}
|
|
12369
12648
|
const next = git5(sourceRoot, ["write-tree"], {
|
|
12370
|
-
GIT_INDEX_FILE:
|
|
12371
|
-
GIT_DIR:
|
|
12649
|
+
GIT_INDEX_FILE: join14(indexDirectory, "index"),
|
|
12650
|
+
GIT_DIR: join14(sourceRoot, ".git")
|
|
12372
12651
|
});
|
|
12373
12652
|
assertTree(next);
|
|
12374
12653
|
return next;
|
|
12375
12654
|
} finally {
|
|
12376
|
-
|
|
12655
|
+
rmSync7(indexDirectory, { recursive: true, force: true });
|
|
12377
12656
|
}
|
|
12378
12657
|
}
|
|
12379
12658
|
function workspaceFiles(root, dir = root, result = Object.create(null)) {
|
|
12380
12659
|
if (dir === root)
|
|
12381
|
-
result["."] = `${
|
|
12660
|
+
result["."] = `${lstatSync11(root).mode & 4095}:directory`;
|
|
12382
12661
|
for (const name of readdirSync6(dir).sort()) {
|
|
12383
|
-
const path =
|
|
12384
|
-
const stat =
|
|
12662
|
+
const path = join14(dir, name), rel = relative6(root, path);
|
|
12663
|
+
const stat = lstatSync11(path);
|
|
12385
12664
|
if (stat.isSymbolicLink())
|
|
12386
12665
|
result[rel] = `link:${stat.mode & 4095}:${readlinkSync2(path)}`;
|
|
12387
12666
|
else if (stat.isDirectory()) {
|
|
@@ -12408,7 +12687,7 @@ function assertDeliveryClean(root, tree, seal, writablePaths = []) {
|
|
|
12408
12687
|
}
|
|
12409
12688
|
if (seal === "[]") {
|
|
12410
12689
|
for (const name of readdirSync6(root)) {
|
|
12411
|
-
const stat =
|
|
12690
|
+
const stat = lstatSync11(join14(root, name));
|
|
12412
12691
|
if (!stat.isDirectory() || name !== ".git")
|
|
12413
12692
|
throw new DeliveryWorkspaceError("delivery workspace was contaminated");
|
|
12414
12693
|
}
|
|
@@ -12430,18 +12709,18 @@ function assertDeliveryClean(root, tree, seal, writablePaths = []) {
|
|
|
12430
12709
|
if (!permitted)
|
|
12431
12710
|
throw new DeliveryWorkspaceError("delivery undeclared output was contaminated");
|
|
12432
12711
|
if (bytes.startsWith("link:")) {
|
|
12433
|
-
assertInside(realpathSync10(root), resolvedSymlinkTarget(
|
|
12712
|
+
assertInside(realpathSync10(root), resolvedSymlinkTarget(join14(root, path)), "generated symlink");
|
|
12434
12713
|
}
|
|
12435
12714
|
}
|
|
12436
12715
|
}
|
|
12437
12716
|
function materializeDeliveryWorkspace(sourceRoot, tree) {
|
|
12438
12717
|
assertTree(tree);
|
|
12439
|
-
const dest = mkdtempSync2(
|
|
12718
|
+
const dest = mkdtempSync2(join14(tmpdir4(), "imm-delivery-"));
|
|
12440
12719
|
const cleanup = () => removeTaskOwnedTree(dest);
|
|
12441
12720
|
try {
|
|
12442
12721
|
mkdirSync7(dest, { recursive: true });
|
|
12443
12722
|
const commit = git5(sourceRoot, ["commit-tree", tree, "-m", `delivery ${tree}`], {
|
|
12444
|
-
GIT_DIR:
|
|
12723
|
+
GIT_DIR: join14(sourceRoot, ".git"),
|
|
12445
12724
|
GIT_AUTHOR_NAME: "Immune-Brain Assurance",
|
|
12446
12725
|
GIT_AUTHOR_EMAIL: "assurance@immune-brain.local",
|
|
12447
12726
|
GIT_AUTHOR_DATE: "1970-01-01T00:00:00 +0000",
|
|
@@ -12469,8 +12748,8 @@ function materializeDeliveryWorkspace(sourceRoot, tree) {
|
|
|
12469
12748
|
|
|
12470
12749
|
// plugins/immune-brain/runtime/assurance/qa.ts
|
|
12471
12750
|
import { mkdtempSync as mkdtempSync3, realpathSync as realpathSync11 } from "node:fs";
|
|
12472
|
-
import { tmpdir as
|
|
12473
|
-
import { join as
|
|
12751
|
+
import { tmpdir as tmpdir5 } from "node:os";
|
|
12752
|
+
import { join as join15, sep as sep8 } from "node:path";
|
|
12474
12753
|
|
|
12475
12754
|
// plugins/immune-brain/runtime/assurance/qa_findings.ts
|
|
12476
12755
|
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
@@ -12537,7 +12816,7 @@ async function runDeterministicQa(snapshot, descriptors, options = {}) {
|
|
|
12537
12816
|
let delivery, home;
|
|
12538
12817
|
try {
|
|
12539
12818
|
delivery = (options._materializeDeliveryWorkspace ?? materializeDeliveryWorkspace)(snapshot.root, tree);
|
|
12540
|
-
home = mkdtempSync3(
|
|
12819
|
+
home = mkdtempSync3(join15(tmpdir5(), "imm-qa-home-"));
|
|
12541
12820
|
return { delivery, home, deliveryPrefix: `${realpathSync11(delivery.root)}${sep8}` };
|
|
12542
12821
|
} catch {
|
|
12543
12822
|
let reason = "delivery_unavailable";
|
|
@@ -12705,11 +12984,11 @@ function deriveAuthorizationOperation(input) {
|
|
|
12705
12984
|
// plugins/immune-brain/runtime/staged_intent.ts
|
|
12706
12985
|
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
12707
12986
|
import { readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "node:fs";
|
|
12708
|
-
import { join as
|
|
12987
|
+
import { join as join16 } from "node:path";
|
|
12709
12988
|
function captureStagedIntent(root, relativePath) {
|
|
12710
12989
|
return {
|
|
12711
12990
|
path: relativePath,
|
|
12712
|
-
bytes: readFileSync14(
|
|
12991
|
+
bytes: readFileSync14(join16(root, relativePath)),
|
|
12713
12992
|
index_state: execFileSync7("git", ["ls-files", "--stage", "-z", "--", relativePath], {
|
|
12714
12993
|
cwd: root,
|
|
12715
12994
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -12717,7 +12996,7 @@ function captureStagedIntent(root, relativePath) {
|
|
|
12717
12996
|
};
|
|
12718
12997
|
}
|
|
12719
12998
|
function restoreStagedIntent(root, snapshot) {
|
|
12720
|
-
writeFileSync7(
|
|
12999
|
+
writeFileSync7(join16(root, snapshot.path), snapshot.bytes);
|
|
12721
13000
|
execFileSync7("git", ["update-index", "--force-remove", "--", snapshot.path], {
|
|
12722
13001
|
cwd: root,
|
|
12723
13002
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -12729,7 +13008,7 @@ function restoreStagedIntent(root, snapshot) {
|
|
|
12729
13008
|
stdio: ["pipe", "ignore", "pipe"]
|
|
12730
13009
|
});
|
|
12731
13010
|
}
|
|
12732
|
-
const restoredBytes = readFileSync14(
|
|
13011
|
+
const restoredBytes = readFileSync14(join16(root, snapshot.path));
|
|
12733
13012
|
if (!restoredBytes.equals(snapshot.bytes)) {
|
|
12734
13013
|
throw new Error("failed to restore prior intent bytes");
|
|
12735
13014
|
}
|
|
@@ -12745,24 +13024,24 @@ function restoreStagedIntent(root, snapshot) {
|
|
|
12745
13024
|
// plugins/immune-brain/runtime/unattended/batch_runner.ts
|
|
12746
13025
|
import { spawnSync as spawnSync14 } from "node:child_process";
|
|
12747
13026
|
import { existsSync as existsSync13 } from "node:fs";
|
|
12748
|
-
import { join as
|
|
13027
|
+
import { join as join21 } from "node:path";
|
|
12749
13028
|
|
|
12750
13029
|
// plugins/immune-brain/runtime/unattended/batch_git.ts
|
|
12751
13030
|
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
12752
13031
|
import { createHash as createHash20, randomUUID as randomUUID9 } from "node:crypto";
|
|
12753
13032
|
import {
|
|
12754
|
-
constants as
|
|
12755
|
-
closeSync as
|
|
13033
|
+
constants as constants7,
|
|
13034
|
+
closeSync as closeSync8,
|
|
12756
13035
|
existsSync as existsSync10,
|
|
12757
|
-
lstatSync as
|
|
13036
|
+
lstatSync as lstatSync12,
|
|
12758
13037
|
mkdirSync as mkdirSync8,
|
|
12759
|
-
openSync as
|
|
13038
|
+
openSync as openSync8,
|
|
12760
13039
|
realpathSync as realpathSync12,
|
|
12761
13040
|
renameSync as renameSync5,
|
|
12762
|
-
rmSync as
|
|
13041
|
+
rmSync as rmSync8,
|
|
12763
13042
|
writeFileSync as writeFileSync8
|
|
12764
13043
|
} from "node:fs";
|
|
12765
|
-
import { dirname as dirname10, join as
|
|
13044
|
+
import { dirname as dirname10, join as join17 } from "node:path";
|
|
12766
13045
|
var DEFAULT_GIT_ENV = {
|
|
12767
13046
|
...process.env,
|
|
12768
13047
|
GIT_AUTHOR_NAME: process.env.GIT_AUTHOR_NAME || "Immune-Brain Batch",
|
|
@@ -12974,15 +13253,15 @@ function getCommittedDeltaPaths(root) {
|
|
|
12974
13253
|
return delta.stdout.split("\x00").filter((p) => p.length > 0);
|
|
12975
13254
|
}
|
|
12976
13255
|
function commitEvidencePath(batchId, taskId) {
|
|
12977
|
-
return
|
|
13256
|
+
return join17(".imm", "state", "batches", "commits", `${batchId}-${taskId}.json`);
|
|
12978
13257
|
}
|
|
12979
13258
|
function ensureSecureDirectory3(root, relativePath) {
|
|
12980
13259
|
const segments = relativePath.split("/").filter(Boolean);
|
|
12981
13260
|
let current = root;
|
|
12982
13261
|
for (const segment of segments) {
|
|
12983
|
-
current =
|
|
13262
|
+
current = join17(current, segment);
|
|
12984
13263
|
if (existsSync10(current)) {
|
|
12985
|
-
const stats =
|
|
13264
|
+
const stats = lstatSync12(current);
|
|
12986
13265
|
if (stats.isSymbolicLink() || !stats.isDirectory()) {
|
|
12987
13266
|
throw new Error(`${segment} exists but is not a real directory`);
|
|
12988
13267
|
}
|
|
@@ -12993,15 +13272,15 @@ function ensureSecureDirectory3(root, relativePath) {
|
|
|
12993
13272
|
return current;
|
|
12994
13273
|
}
|
|
12995
13274
|
function writeFileAtomically2(root, relativePath, bytes) {
|
|
12996
|
-
const target =
|
|
13275
|
+
const target = join17(root, relativePath);
|
|
12997
13276
|
const targetDir = dirname10(target);
|
|
12998
13277
|
ensureSecureDirectory3(root, dirname10(relativePath));
|
|
12999
|
-
const stats =
|
|
13278
|
+
const stats = lstatSync12(targetDir);
|
|
13000
13279
|
if (stats.isSymbolicLink() || !stats.isDirectory()) {
|
|
13001
13280
|
throw new Error(`${dirname10(relativePath)} is not a real directory`);
|
|
13002
13281
|
}
|
|
13003
13282
|
if (existsSync10(target)) {
|
|
13004
|
-
const targetStats =
|
|
13283
|
+
const targetStats = lstatSync12(target);
|
|
13005
13284
|
if (targetStats.isSymbolicLink()) {
|
|
13006
13285
|
throw new Error(`${relativePath} is a symlink`);
|
|
13007
13286
|
}
|
|
@@ -13009,17 +13288,17 @@ function writeFileAtomically2(root, relativePath, bytes) {
|
|
|
13009
13288
|
const tempPath = `${target}.${randomUUID9()}.tmp`;
|
|
13010
13289
|
let fd = null;
|
|
13011
13290
|
try {
|
|
13012
|
-
fd =
|
|
13291
|
+
fd = openSync8(tempPath, constants7.O_WRONLY | constants7.O_CREAT | constants7.O_EXCL, 384);
|
|
13013
13292
|
writeFileSync8(fd, bytes, "utf8");
|
|
13014
|
-
|
|
13293
|
+
closeSync8(fd);
|
|
13015
13294
|
fd = null;
|
|
13016
13295
|
renameSync5(tempPath, target);
|
|
13017
13296
|
} finally {
|
|
13018
13297
|
if (fd !== null)
|
|
13019
|
-
|
|
13298
|
+
closeSync8(fd);
|
|
13020
13299
|
if (existsSync10(tempPath)) {
|
|
13021
13300
|
try {
|
|
13022
|
-
|
|
13301
|
+
rmSync8(tempPath);
|
|
13023
13302
|
} catch {}
|
|
13024
13303
|
}
|
|
13025
13304
|
}
|
|
@@ -13039,7 +13318,7 @@ function writeBatchCommitEvidence(root, evidence) {
|
|
|
13039
13318
|
}
|
|
13040
13319
|
function readBatchCommitEvidence(root, batchId, taskId) {
|
|
13041
13320
|
const path = commitEvidencePath(batchId, taskId);
|
|
13042
|
-
const fullPath =
|
|
13321
|
+
const fullPath = join17(root, path);
|
|
13043
13322
|
if (!existsSync10(fullPath))
|
|
13044
13323
|
return null;
|
|
13045
13324
|
try {
|
|
@@ -13314,11 +13593,11 @@ function createDefaultBatchGitPort() {
|
|
|
13314
13593
|
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
13315
13594
|
import { createHash as createHash21 } from "node:crypto";
|
|
13316
13595
|
import { existsSync as existsSync12, realpathSync as realpathSync13 } from "node:fs";
|
|
13317
|
-
import { isAbsolute as isAbsolute7, join as
|
|
13596
|
+
import { isAbsolute as isAbsolute7, join as join20 } from "node:path";
|
|
13318
13597
|
|
|
13319
13598
|
// plugins/immune-brain/runtime/kernel/runtime_contracts.ts
|
|
13320
13599
|
import { existsSync as existsSync11, readFileSync as readFileSync15 } from "node:fs";
|
|
13321
|
-
import { dirname as dirname11, join as
|
|
13600
|
+
import { dirname as dirname11, join as join18, resolve as resolve14 } from "node:path";
|
|
13322
13601
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
13323
13602
|
var RUNTIME_CONTRACTS_MANIFEST = "runtime_contracts.json";
|
|
13324
13603
|
var RUNTIME_CONTRACTS = {
|
|
@@ -13338,10 +13617,10 @@ function runningRuntimeSource() {
|
|
|
13338
13617
|
}
|
|
13339
13618
|
function readRuntimeContracts(runtimeDir) {
|
|
13340
13619
|
const candidates = [
|
|
13341
|
-
|
|
13342
|
-
|
|
13343
|
-
|
|
13344
|
-
|
|
13620
|
+
join18(runtimeDir, RUNTIME_CONTRACTS_MANIFEST),
|
|
13621
|
+
join18(runtimeDir, "kernel", RUNTIME_CONTRACTS_MANIFEST),
|
|
13622
|
+
join18(runtimeDir, "runtime", "kernel", RUNTIME_CONTRACTS_MANIFEST),
|
|
13623
|
+
join18(runtimeDir, "plugins", "immune-brain", "runtime", "kernel", RUNTIME_CONTRACTS_MANIFEST)
|
|
13345
13624
|
];
|
|
13346
13625
|
const path = candidates.find((candidate) => existsSync11(candidate));
|
|
13347
13626
|
if (!path)
|
|
@@ -13412,7 +13691,7 @@ async function runFinalVerification(root, commands, options = {}) {
|
|
|
13412
13691
|
|
|
13413
13692
|
// plugins/immune-brain/runtime/unattended/batch_delegation.ts
|
|
13414
13693
|
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
13415
|
-
import { join as
|
|
13694
|
+
import { join as join19 } from "node:path";
|
|
13416
13695
|
import { writeFileSync as writeFileSync9 } from "node:fs";
|
|
13417
13696
|
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
13697
|
var DELEGATED_ACTOR_ID = "batch-coordinator";
|
|
@@ -13463,7 +13742,7 @@ function applyDelegatedIntentRevision(input) {
|
|
|
13463
13742
|
const nextRef = { path: `docs/plans/${taskId}.intent.json`, content_hash: nextHash };
|
|
13464
13743
|
const snapshot = captureStagedIntent(root, prior.intent_ref.path);
|
|
13465
13744
|
try {
|
|
13466
|
-
writeFileSync9(
|
|
13745
|
+
writeFileSync9(join19(root, prior.intent_ref.path), `${JSON.stringify(nextIntent, null, 2)}
|
|
13467
13746
|
`);
|
|
13468
13747
|
execFileSync8("git", ["add", "--", prior.intent_ref.path], { cwd: root, stdio: ["ignore", "pipe", "pipe"] });
|
|
13469
13748
|
const prepared = await projectAssurance(root, taskId, taskDeliveryIdentity);
|
|
@@ -13832,7 +14111,7 @@ function laneRuntimeDir(root, explicit) {
|
|
|
13832
14111
|
if (explicit)
|
|
13833
14112
|
return explicit;
|
|
13834
14113
|
try {
|
|
13835
|
-
const manifest = JSON.parse(readFileSync16(
|
|
14114
|
+
const manifest = JSON.parse(readFileSync16(join20(root, "package.json"), "utf8"));
|
|
13836
14115
|
return manifest.name === "immune-brain" ? root : null;
|
|
13837
14116
|
} catch {
|
|
13838
14117
|
return null;
|
|
@@ -14140,7 +14419,7 @@ function finalizeLane(root, record, reason, nextAction, extra = {}) {
|
|
|
14140
14419
|
writeBatchRunReport(root, report);
|
|
14141
14420
|
return report;
|
|
14142
14421
|
}
|
|
14143
|
-
function
|
|
14422
|
+
function refuse3(input, persisted, code, detail) {
|
|
14144
14423
|
const base = persisted && isLaneBatchRecord(persisted) ? persisted : prepareBatchLaneRunState({
|
|
14145
14424
|
batch_id: input.batch_id,
|
|
14146
14425
|
initiative_slug: input.initiative_slug,
|
|
@@ -14229,12 +14508,12 @@ function isLineageBreak(message) {
|
|
|
14229
14508
|
}
|
|
14230
14509
|
async function runLaneBatch(input, persisted) {
|
|
14231
14510
|
if (persisted && !isLaneBatchRecord(persisted))
|
|
14232
|
-
return
|
|
14511
|
+
return refuse3(input, persisted, PARALLEL_MISMATCH, "the recorded batch runs serially; max_parallel cannot be added on a resume");
|
|
14233
14512
|
if (persisted && input.max_parallel !== undefined && input.max_parallel !== persisted.max_parallel)
|
|
14234
|
-
return
|
|
14513
|
+
return refuse3(input, persisted, PARALLEL_MISMATCH, `the recorded batch runs with max_parallel ${persisted.max_parallel}`);
|
|
14235
14514
|
const limit = persisted?.max_parallel ?? input.max_parallel ?? 1;
|
|
14236
14515
|
if (!input.children.length)
|
|
14237
|
-
return
|
|
14516
|
+
return refuse3(input, persisted, "batch_plan_empty", "batch plan is empty");
|
|
14238
14517
|
const git = serialGitOf(input);
|
|
14239
14518
|
const lanes = laneGitOf(input);
|
|
14240
14519
|
let record;
|
|
@@ -14281,11 +14560,11 @@ async function runLaneBatch(input, persisted) {
|
|
|
14281
14560
|
} else {
|
|
14282
14561
|
const invalid = validateNewAuthorization(input);
|
|
14283
14562
|
if (invalid)
|
|
14284
|
-
return
|
|
14563
|
+
return refuse3(input, null, "batch_authorization_invalid", invalid);
|
|
14285
14564
|
const contractRefusal = laneRuntimeContractRefusal(input.root, input.executor_runtime);
|
|
14286
14565
|
if (contractRefusal) {
|
|
14287
14566
|
const [code, ...detail] = contractRefusal.split(": ");
|
|
14288
|
-
return
|
|
14567
|
+
return refuse3(input, null, code, detail.join(": "));
|
|
14289
14568
|
}
|
|
14290
14569
|
const preflight = await git.preflight({
|
|
14291
14570
|
root: input.root,
|
|
@@ -14293,7 +14572,7 @@ async function runLaneBatch(input, persisted) {
|
|
|
14293
14572
|
base_head: input.base_head
|
|
14294
14573
|
});
|
|
14295
14574
|
if (!preflight.ok)
|
|
14296
|
-
return
|
|
14575
|
+
return refuse3(input, null, preflight.reason, preflight.message || "Correct the preflight condition and re-confirm.");
|
|
14297
14576
|
record = prepareBatchLaneRunState({
|
|
14298
14577
|
batch_id: input.batch_id,
|
|
14299
14578
|
initiative_slug: input.initiative_slug,
|
|
@@ -14357,7 +14636,7 @@ async function runLaneBatch(input, persisted) {
|
|
|
14357
14636
|
record.children = record.children.map((c) => c.task_id === child.task_id ? { ...c, state: "released" } : c);
|
|
14358
14637
|
persist();
|
|
14359
14638
|
}
|
|
14360
|
-
const lineage = existsSync12(
|
|
14639
|
+
const lineage = existsSync12(join20(input.root, ".git")) ? classifyBatchLineage({
|
|
14361
14640
|
root: input.root,
|
|
14362
14641
|
branch: record.branch ?? "",
|
|
14363
14642
|
expectedHead: expectedBatchHead(record),
|
|
@@ -14808,7 +15087,7 @@ function failPersistedLineage(root, existing, message) {
|
|
|
14808
15087
|
return writeBatchRunState(root, record);
|
|
14809
15088
|
}
|
|
14810
15089
|
function reconcileLineage(root, record) {
|
|
14811
|
-
if (!existsSync13(
|
|
15090
|
+
if (!existsSync13(join21(root, ".git")))
|
|
14812
15091
|
return { record, failure: null };
|
|
14813
15092
|
const expected = expectedBatchHead(record);
|
|
14814
15093
|
const lineage = classifyBatchLineage({
|
|
@@ -14831,7 +15110,7 @@ function reconcileLineage(root, record) {
|
|
|
14831
15110
|
};
|
|
14832
15111
|
}
|
|
14833
15112
|
async function validatePersistedRun(input, record) {
|
|
14834
|
-
if (!record.commits.length && record.children[0]?.state === "settled" && existsSync13(
|
|
15113
|
+
if (!record.commits.length && record.children[0]?.state === "settled" && existsSync13(join21(input.root, ".git"))) {
|
|
14835
15114
|
const head = spawnSync14("git", ["-C", input.root, "rev-parse", "HEAD"], { encoding: "utf8" });
|
|
14836
15115
|
const live = head.stdout.trim();
|
|
14837
15116
|
const expected = expectedBatchHead(record);
|
|
@@ -14861,7 +15140,7 @@ async function validatePersistedRun(input, record) {
|
|
|
14861
15140
|
const git = batchGitPortOf(input);
|
|
14862
15141
|
const evidence = await git.lookupBatchCommit(input.root, child.task_id, record.batch_id, undefined, record.branch);
|
|
14863
15142
|
if (!evidence || evidence.commit !== child.commit) {
|
|
14864
|
-
if (evidence === null && typeof child.commit === "string" && child.commit.length > 0 && existsSync13(
|
|
15143
|
+
if (evidence === null && typeof child.commit === "string" && child.commit.length > 0 && existsSync13(join21(input.root, ".git"))) {
|
|
14865
15144
|
const reach = spawnSync14("git", ["-C", input.root, "merge-base", "--is-ancestor", child.commit, "HEAD"], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
14866
15145
|
if (reach.status !== 0) {
|
|
14867
15146
|
throw new Error(`batch_head_lineage_broken: recorded commit ${child.commit} for ${child.task_id} is no longer reachable from HEAD`);
|
|
@@ -15301,7 +15580,7 @@ async function driveInterruptedChild(input, child) {
|
|
|
15301
15580
|
const planChild = input.children.find((c) => c.task_id === child.task_id);
|
|
15302
15581
|
const intentPath = planChild?.intent_path ?? undefined;
|
|
15303
15582
|
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(
|
|
15583
|
+
if (existing && !record.commits.length && existsSync13(join21(input.root, ".git")) && !ownUnpersistedBatchHead(input.root, record, existing.commit))
|
|
15305
15584
|
throw new Error("first unpersisted batch commit provenance is invalid");
|
|
15306
15585
|
const adopted = existing ?? await doCommit().catch((error) => {
|
|
15307
15586
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -15832,7 +16111,7 @@ class ClaudeRuntime {
|
|
|
15832
16111
|
if (!slug)
|
|
15833
16112
|
return result;
|
|
15834
16113
|
try {
|
|
15835
|
-
if (existsSync14(
|
|
16114
|
+
if (existsSync14(join22(this.cwd, ".imm", "audit", taskId)))
|
|
15836
16115
|
execFileSync9("git", ["-C", this.cwd, "add", "--", `.imm/audit/${taskId}`], { stdio: "ignore" });
|
|
15837
16116
|
const batch = await this.startUnattendedBatch(slug, meta, { reuseOnly: true });
|
|
15838
16117
|
return { ...result, batch };
|
|
@@ -15929,7 +16208,7 @@ class ClaudeRuntime {
|
|
|
15929
16208
|
throw new Error("approve_breaking_intent_revision requires next_intent");
|
|
15930
16209
|
const nextIntentHash = nextIntent ? canonicalIntentHash(nextIntent) : undefined;
|
|
15931
16210
|
const nextIntentRef = nextIntent ? { path: `docs/plans/${nextIntent.task_id}.intent.json`, content_hash: nextIntentHash } : undefined;
|
|
15932
|
-
const sidecar = nextIntent ?
|
|
16211
|
+
const sidecar = nextIntent ? join22(this.cwd, priorIntent.intent_ref.path) : undefined;
|
|
15933
16212
|
const stagedSnapshot = sidecar ? captureStagedIntent(this.cwd, priorIntent.intent_ref.path) : undefined;
|
|
15934
16213
|
const restoreStagedIntent2 = () => {
|
|
15935
16214
|
if (!stagedSnapshot)
|
|
@@ -16038,7 +16317,7 @@ class ClaudeRuntime {
|
|
|
16038
16317
|
const { app } = await this.authority();
|
|
16039
16318
|
const operation = input.operation.op === "revise_intent" ? { ...input.operation, next_intent: await parseTaskIntentV1(input.operation.next_intent) } : input.operation;
|
|
16040
16319
|
const priorIntent = await readTaskIntentForRecord(ctx.cwd, input.taskId);
|
|
16041
|
-
const sidecar =
|
|
16320
|
+
const sidecar = join22(ctx.cwd, priorIntent.intent_ref.path);
|
|
16042
16321
|
const priorBytes = operation.op === "revise_intent" ? readFileSync17(sidecar) : null;
|
|
16043
16322
|
const priorStaged = priorBytes !== null ? captureStagedIntent(ctx.cwd, priorIntent.intent_ref.path) : null;
|
|
16044
16323
|
try {
|
|
@@ -16442,6 +16721,12 @@ function createMcpRuntime(options = {}) {
|
|
|
16442
16721
|
let negotiatedVersion;
|
|
16443
16722
|
let negotiatedInteractive = false;
|
|
16444
16723
|
const connectionId = randomUUID10();
|
|
16724
|
+
const consumeAdmission = options.consumePhaseAdmission ?? ((toolCallId, operation) => consumePhaseAdmission(toolCallId, operation));
|
|
16725
|
+
const requireRunEntry = (operation, toolCallId) => {
|
|
16726
|
+
if (consumeAdmission(toolCallId, operation))
|
|
16727
|
+
return;
|
|
16728
|
+
throw new NativeAuthorityError("run_entry_required", `${operation} needs a literal-user imm-run entry in this session`);
|
|
16729
|
+
};
|
|
16445
16730
|
return {
|
|
16446
16731
|
runtime,
|
|
16447
16732
|
host,
|
|
@@ -16471,6 +16756,7 @@ function createMcpRuntime(options = {}) {
|
|
|
16471
16756
|
const probe = probeHost(options.env ?? process.env, process.platform, negotiatedVersion);
|
|
16472
16757
|
if (!probe.ok)
|
|
16473
16758
|
throw new NativeAuthorityError("unsupported_host", probe.reason);
|
|
16759
|
+
requireRunEntry(name, meta.toolCallId ?? `call-${name}`);
|
|
16474
16760
|
const toolMeta = {
|
|
16475
16761
|
sessionId: meta.sessionId ?? connectionId,
|
|
16476
16762
|
toolCallId: meta.toolCallId ?? `call-${name}`,
|
|
@@ -16538,8 +16824,10 @@ function createMcpRuntime(options = {}) {
|
|
|
16538
16824
|
interactive: meta.interactive ?? options.interactive ?? negotiatedInteractive,
|
|
16539
16825
|
signal: meta.signal
|
|
16540
16826
|
};
|
|
16541
|
-
if (name === "enroll")
|
|
16827
|
+
if (name === "enroll") {
|
|
16828
|
+
requireRunEntry(name, meta.toolCallId ?? `call-${name}`);
|
|
16542
16829
|
return runtime.enroll(taskId, toolMeta);
|
|
16830
|
+
}
|
|
16543
16831
|
if (name === "advance_assurance")
|
|
16544
16832
|
return runtime.advance(taskId, toolMeta.signal, toolMeta);
|
|
16545
16833
|
if (name === "submit_review") {
|
|
@@ -17046,13 +17334,14 @@ async function runHook() {
|
|
|
17046
17334
|
payload = null;
|
|
17047
17335
|
}
|
|
17048
17336
|
if (payload?.hook_event_name === "PreToolUse") {
|
|
17049
|
-
const decision = laneGuardHookOutput(payload);
|
|
17337
|
+
const decision = laneGuardHookOutput(payload) ?? phaseEntryGateHookOutput(payload);
|
|
17050
17338
|
if (decision)
|
|
17051
17339
|
process.stdout.write(`${decision}
|
|
17052
17340
|
`);
|
|
17053
17341
|
return;
|
|
17054
17342
|
}
|
|
17055
17343
|
if (payload?.hook_event_name === "UserPromptSubmit") {
|
|
17344
|
+
recordPhaseEntryPrompt(payload);
|
|
17056
17345
|
const notice = await activeTaskHookOutput(payload);
|
|
17057
17346
|
if (notice)
|
|
17058
17347
|
process.stdout.write(`${notice}
|