immune-brain 3.6.5 → 3.6.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -3
- package/README.zh-CN.md +23 -3
- package/package.json +2 -1
- package/plugins/immune-brain/.claude-plugin/plugin.json +1 -1
- package/plugins/immune-brain/.pi-extension/imm-canary-work.ts +60 -21
- package/plugins/immune-brain/.pi-extension/imm-unattended-batch.ts +932 -0
- package/plugins/immune-brain/.pi-extension/pi-canary-interaction.ts +38 -10
- package/plugins/immune-brain/.pi-extension/runtime-stub.ts +200 -2
- package/plugins/immune-brain/dist/BASELINE.md +48 -15
- package/plugins/immune-brain/dist/claude/mcp-server.mjs +3415 -228
- package/plugins/immune-brain/dist/docs/reference/planning-quality-gate.md +1 -1
- package/plugins/immune-brain/dist/docs/reference/subagent-dispatch-protocol.md +1 -1
- package/plugins/immune-brain/dist/imm-agent-doc-maintain.md +9 -1
- package/plugins/immune-brain/dist/imm-brainstorm.md +49 -35
- package/plugins/immune-brain/dist/imm-doc-prune.md +7 -1
- package/plugins/immune-brain/dist/imm-loop.md +45 -11
- package/plugins/immune-brain/dist/imm-planner.md +65 -28
- package/plugins/immune-brain/dist/imm-pr-fix.md +6 -2
- package/plugins/immune-brain/dist/role-prompts/code-review.md +9 -1
- package/plugins/immune-brain/dist/role-prompts/executor.md +18 -10
- package/plugins/immune-brain/dist/role-prompts/pr-fix.md +5 -2
- package/plugins/immune-brain/runtime/assurance/coordinator.ts +101 -3
- package/plugins/immune-brain/runtime/assurance/verification.ts +13 -2
- package/plugins/immune-brain/runtime/claude/interaction.ts +16 -3
- package/plugins/immune-brain/runtime/claude/kernel_ports.ts +840 -17
- package/plugins/immune-brain/runtime/claude/mcp_server.ts +72 -10
- package/plugins/immune-brain/runtime/github_issue_tracker.ts +1018 -17
- package/plugins/immune-brain/runtime/kernel/canary_application.ts +16 -1
- package/plugins/immune-brain/runtime/kernel/completion.ts +19 -1
- package/plugins/immune-brain/runtime/kernel/reducer.ts +165 -12
- package/plugins/immune-brain/runtime/kernel/refutation.ts +82 -0
- package/plugins/immune-brain/runtime/kernel/types.ts +34 -1
- package/plugins/immune-brain/runtime/kernel/validation.ts +202 -9
- package/plugins/immune-brain/runtime/plugin_version.ts +1 -1
- package/plugins/immune-brain/runtime/prompts/code-review.md +9 -1
- package/plugins/immune-brain/runtime/prompts/executor.md +18 -10
- package/plugins/immune-brain/runtime/prompts/pr-fix.md +5 -2
- package/plugins/immune-brain/runtime/unattended/batch_git.ts +775 -0
- package/plugins/immune-brain/runtime/unattended/batch_plan.ts +203 -0
- package/plugins/immune-brain/runtime/unattended/batch_runner.ts +1224 -0
- package/plugins/immune-brain/runtime/unattended/batch_state.ts +360 -0
- package/plugins/immune-brain/runtime/unattended/types.ts +60 -0
- package/plugins/immune-brain/skills/BASELINE.md +48 -15
- package/plugins/immune-brain/skills/imm-agent-doc-maintain/SKILL.md +20 -4
- package/plugins/immune-brain/skills/imm-brainstorm/SKILL.md +24 -64
- package/plugins/immune-brain/skills/imm-doc-prune/SKILL.md +18 -3
- package/plugins/immune-brain/skills/imm-loop/SKILL.md +21 -6
- package/plugins/immune-brain/skills/imm-planner/SKILL.md +35 -8
- package/plugins/immune-brain/skills/imm-pr-fix/SKILL.md +17 -3
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
// Batch run state persistence for unattended Initiative batch runs.
|
|
2
|
+
// State lives at .imm/state/batches/<batch_id>.json, written only under the
|
|
3
|
+
// kernel store lock, and is Git-ignored with the rest of .imm/state/.
|
|
4
|
+
import { existsSync, mkdirSync, openSync, closeSync, writeFileSync, renameSync, lstatSync, constants, rmSync } from "node:fs";
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
7
|
+
import { readSecureProjectFile, withKernelStoreLock } from "../kernel/storage";
|
|
8
|
+
import type { BatchPlanChild } from "./types";
|
|
9
|
+
|
|
10
|
+
export type BatchRunState =
|
|
11
|
+
| "prepared"
|
|
12
|
+
| "running"
|
|
13
|
+
| "needs_human"
|
|
14
|
+
| "completed"
|
|
15
|
+
| "budget_stopped"
|
|
16
|
+
| "failed"
|
|
17
|
+
| "rejected";
|
|
18
|
+
|
|
19
|
+
export type BatchChildRunState =
|
|
20
|
+
| "pending"
|
|
21
|
+
| "enrolled"
|
|
22
|
+
| "settled"
|
|
23
|
+
| "committed"
|
|
24
|
+
| "needs_human"
|
|
25
|
+
| "skipped_blocked";
|
|
26
|
+
|
|
27
|
+
export interface BatchChildRun {
|
|
28
|
+
task_id: string;
|
|
29
|
+
slice_id: string;
|
|
30
|
+
blocked_by: string[];
|
|
31
|
+
state: BatchChildRunState;
|
|
32
|
+
/** Terminal reason for needs_human / skipped_blocked / failed children. */
|
|
33
|
+
reason: string | null;
|
|
34
|
+
/** Commit created after the Kernel reported this child done. */
|
|
35
|
+
commit: string | null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface BatchRunStateRecord {
|
|
39
|
+
contract: "assurance_kernel/batch_run_state/v1";
|
|
40
|
+
batch_id: string;
|
|
41
|
+
initiative_slug: string;
|
|
42
|
+
plan_digest: string;
|
|
43
|
+
base_head: string;
|
|
44
|
+
/** Dedicated batch branch, e.g. imm/<initiative-slug>. */
|
|
45
|
+
branch?: string;
|
|
46
|
+
/** Timestamp of the literal-user batch confirmation. */
|
|
47
|
+
confirmation_time: string;
|
|
48
|
+
/** Authorization expiry from the batch capability binding. */
|
|
49
|
+
authorization_expires_at: string;
|
|
50
|
+
budget: {
|
|
51
|
+
max_children: number;
|
|
52
|
+
deadline_at: string;
|
|
53
|
+
qa_failure_limit: number;
|
|
54
|
+
};
|
|
55
|
+
batch_state: BatchRunState;
|
|
56
|
+
children: BatchChildRun[];
|
|
57
|
+
/** Consecutive deterministic-QA failures across children in this run. */
|
|
58
|
+
consecutive_qa_failures: number;
|
|
59
|
+
/** Commit heads produced by this batch, in child order. */
|
|
60
|
+
commits: string[];
|
|
61
|
+
created_at: string;
|
|
62
|
+
updated_at: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const BATCH_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
66
|
+
|
|
67
|
+
const CHILD_RUN_STATES: ReadonlySet<string> = new Set([
|
|
68
|
+
"pending",
|
|
69
|
+
"enrolled",
|
|
70
|
+
"settled",
|
|
71
|
+
"committed",
|
|
72
|
+
"needs_human",
|
|
73
|
+
"skipped_blocked",
|
|
74
|
+
]);
|
|
75
|
+
|
|
76
|
+
const BATCH_RUN_STATES: ReadonlySet<string> = new Set([
|
|
77
|
+
"prepared",
|
|
78
|
+
"running",
|
|
79
|
+
"needs_human",
|
|
80
|
+
"completed",
|
|
81
|
+
"budget_stopped",
|
|
82
|
+
"failed",
|
|
83
|
+
"rejected",
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
const ISO_TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
|
87
|
+
|
|
88
|
+
function isCanonicalTimestamp(value: unknown): value is string {
|
|
89
|
+
if (typeof value !== "string" || !ISO_TIMESTAMP_PATTERN.test(value)) return false;
|
|
90
|
+
const milliseconds = Date.parse(value);
|
|
91
|
+
return Number.isFinite(milliseconds) && new Date(milliseconds).toISOString() === value;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function validateBatchId(batchId: string): void {
|
|
95
|
+
if (typeof batchId !== "string" || !BATCH_ID_PATTERN.test(batchId))
|
|
96
|
+
throw new Error("batch id is not a safe file identity");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function statePath(batchId: string): string {
|
|
100
|
+
validateBatchId(batchId);
|
|
101
|
+
return join(".imm", "state", "batches", `${batchId}.json`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function canonicalBytes(record: BatchRunStateRecord): string {
|
|
105
|
+
return `${JSON.stringify(record, null, 2)}\n`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function validateRecordShape(value: unknown, batchId: string): asserts value is BatchRunStateRecord {
|
|
109
|
+
if (typeof value !== "object" || value === null)
|
|
110
|
+
throw new Error(`batch run state ${batchId} is not an object`);
|
|
111
|
+
const record = value as Record<string, unknown>;
|
|
112
|
+
if (record.contract !== "assurance_kernel/batch_run_state/v1")
|
|
113
|
+
throw new Error(`batch run state ${batchId} has an unknown contract`);
|
|
114
|
+
if (record.batch_id !== batchId)
|
|
115
|
+
throw new Error(`batch run state ${batchId} carries batch_id ${String(record.batch_id)}`);
|
|
116
|
+
if (typeof record.plan_digest !== "string" || !record.plan_digest)
|
|
117
|
+
throw new Error(`batch run state ${batchId} has an invalid plan_digest`);
|
|
118
|
+
if (typeof record.base_head !== "string" || !record.base_head)
|
|
119
|
+
throw new Error(`batch run state ${batchId} has an invalid base_head`);
|
|
120
|
+
if (record.branch !== undefined && (typeof record.branch !== "string" || !record.branch))
|
|
121
|
+
throw new Error(`batch run state ${batchId} has an invalid branch`);
|
|
122
|
+
if (!isCanonicalTimestamp(record.confirmation_time))
|
|
123
|
+
throw new Error(`batch run state ${batchId} has an invalid confirmation_time`);
|
|
124
|
+
if (!isCanonicalTimestamp(record.authorization_expires_at))
|
|
125
|
+
throw new Error(`batch run state ${batchId} has an invalid authorization_expires_at`);
|
|
126
|
+
if (!isCanonicalTimestamp(record.created_at) || !isCanonicalTimestamp(record.updated_at))
|
|
127
|
+
throw new Error(`batch run state ${batchId} has invalid state timestamps`);
|
|
128
|
+
if (!Array.isArray(record.children) || record.children.length === 0)
|
|
129
|
+
throw new Error(`batch run state ${batchId} has no children`);
|
|
130
|
+
if (!BATCH_RUN_STATES.has(String(record.batch_state)))
|
|
131
|
+
throw new Error(`batch run state ${batchId} has an invalid batch_state`);
|
|
132
|
+
if (
|
|
133
|
+
typeof record.consecutive_qa_failures !== "number" ||
|
|
134
|
+
!Number.isInteger(record.consecutive_qa_failures) ||
|
|
135
|
+
record.consecutive_qa_failures < 0
|
|
136
|
+
)
|
|
137
|
+
throw new Error(`batch run state ${batchId} has an invalid consecutive_qa_failures`);
|
|
138
|
+
const budget = record.budget as Record<string, unknown>;
|
|
139
|
+
if (
|
|
140
|
+
typeof record.budget !== "object" ||
|
|
141
|
+
record.budget === null ||
|
|
142
|
+
typeof budget.max_children !== "number" ||
|
|
143
|
+
!Number.isInteger(budget.max_children) ||
|
|
144
|
+
budget.max_children <= 0 ||
|
|
145
|
+
!isCanonicalTimestamp(budget.deadline_at) ||
|
|
146
|
+
typeof budget.qa_failure_limit !== "number" ||
|
|
147
|
+
!Number.isInteger(budget.qa_failure_limit) ||
|
|
148
|
+
budget.qa_failure_limit <= 0
|
|
149
|
+
)
|
|
150
|
+
throw new Error(`batch run state ${batchId} has an invalid budget`);
|
|
151
|
+
if (!Array.isArray(record.commits) || record.commits.some((c) => typeof c !== "string"))
|
|
152
|
+
throw new Error(`batch run state ${batchId} has an invalid commits list`);
|
|
153
|
+
const seenTaskIds = new Set<string>();
|
|
154
|
+
for (const child of record.children) {
|
|
155
|
+
if (
|
|
156
|
+
typeof child !== "object" ||
|
|
157
|
+
child === null ||
|
|
158
|
+
typeof child.task_id !== "string" ||
|
|
159
|
+
!child.task_id ||
|
|
160
|
+
typeof child.slice_id !== "string" ||
|
|
161
|
+
!child.slice_id
|
|
162
|
+
)
|
|
163
|
+
throw new Error(`batch run state ${batchId} has an invalid child entry`);
|
|
164
|
+
if (!CHILD_RUN_STATES.has(String(child.state)))
|
|
165
|
+
throw new Error(`batch run state ${batchId} child ${child.task_id} has an invalid state`);
|
|
166
|
+
if (!Array.isArray(child.blocked_by) || child.blocked_by.some((b: unknown) => typeof b !== "string"))
|
|
167
|
+
throw new Error(`batch run state ${batchId} child ${child.task_id} has an invalid blocked_by`);
|
|
168
|
+
if (
|
|
169
|
+
(child.reason !== null && typeof child.reason !== "string") ||
|
|
170
|
+
(child.commit !== null && typeof child.commit !== "string")
|
|
171
|
+
)
|
|
172
|
+
throw new Error(`batch run state ${batchId} child ${child.task_id} has invalid terminal fields`);
|
|
173
|
+
// State invariants: commit/reason pair coherently with the state.
|
|
174
|
+
if (child.state === "committed" && child.commit === null)
|
|
175
|
+
throw new Error(`batch run state ${batchId} child ${child.task_id} is committed without a commit`);
|
|
176
|
+
if (child.state !== "committed" && child.commit !== null)
|
|
177
|
+
throw new Error(`batch run state ${batchId} child ${child.task_id} has a commit but is not committed`);
|
|
178
|
+
if (
|
|
179
|
+
(child.state === "needs_human" || child.state === "skipped_blocked") &&
|
|
180
|
+
child.reason === null
|
|
181
|
+
)
|
|
182
|
+
throw new Error(`batch run state ${batchId} child ${child.task_id} needs a terminal reason`);
|
|
183
|
+
if (seenTaskIds.has(child.task_id))
|
|
184
|
+
throw new Error(`batch run state ${batchId} has a duplicate child ${child.task_id}`);
|
|
185
|
+
seenTaskIds.add(child.task_id);
|
|
186
|
+
}
|
|
187
|
+
// A committed child must appear in the commits list (review-1).
|
|
188
|
+
for (const child of record.children) {
|
|
189
|
+
if (child.state === "committed" && child.commit !== null && !record.commits.includes(child.commit))
|
|
190
|
+
throw new Error(`batch run state ${batchId} child ${child.task_id} commit is missing from commits`);
|
|
191
|
+
}
|
|
192
|
+
if (
|
|
193
|
+
(record.batch_state === "budget_stopped" ||
|
|
194
|
+
record.batch_state === "failed" ||
|
|
195
|
+
record.batch_state === "rejected") &&
|
|
196
|
+
record.children.some((child) => child.state === "enrolled" || child.state === "settled")
|
|
197
|
+
)
|
|
198
|
+
throw new Error(`batch run state ${batchId} is ${String(record.batch_state)} but a child is still mid-flight`);
|
|
199
|
+
if (
|
|
200
|
+
record.batch_state === "completed" &&
|
|
201
|
+
record.children.some((child) => child.state !== "committed")
|
|
202
|
+
)
|
|
203
|
+
throw new Error(`batch run state ${batchId} is completed but a child is not committed`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function prepareBatchRunState(input: {
|
|
207
|
+
batch_id: string;
|
|
208
|
+
initiative_slug: string;
|
|
209
|
+
children: BatchPlanChild[];
|
|
210
|
+
plan_digest: string;
|
|
211
|
+
base_head: string;
|
|
212
|
+
branch?: string;
|
|
213
|
+
confirmation_time: string;
|
|
214
|
+
authorization_expires_at: string;
|
|
215
|
+
budget: { max_children: number; deadline_at: string; qa_failure_limit: number };
|
|
216
|
+
now: string;
|
|
217
|
+
}): BatchRunStateRecord {
|
|
218
|
+
validateBatchId(input.batch_id);
|
|
219
|
+
const prepared: BatchRunStateRecord = {
|
|
220
|
+
contract: "assurance_kernel/batch_run_state/v1",
|
|
221
|
+
batch_id: input.batch_id,
|
|
222
|
+
initiative_slug: input.initiative_slug,
|
|
223
|
+
plan_digest: input.plan_digest,
|
|
224
|
+
base_head: input.base_head,
|
|
225
|
+
branch: input.branch ?? `imm/${input.initiative_slug}`,
|
|
226
|
+
confirmation_time: input.confirmation_time,
|
|
227
|
+
authorization_expires_at: input.authorization_expires_at,
|
|
228
|
+
budget: input.budget,
|
|
229
|
+
batch_state: "prepared",
|
|
230
|
+
children: input.children.map((child) => ({
|
|
231
|
+
task_id: child.task_id,
|
|
232
|
+
slice_id: child.slice_id,
|
|
233
|
+
blocked_by: [...child.blocked_by],
|
|
234
|
+
state: "pending",
|
|
235
|
+
reason: null,
|
|
236
|
+
commit: null,
|
|
237
|
+
})),
|
|
238
|
+
consecutive_qa_failures: 0,
|
|
239
|
+
commits: [],
|
|
240
|
+
created_at: input.now,
|
|
241
|
+
updated_at: input.now,
|
|
242
|
+
};
|
|
243
|
+
return prepared;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function readBatchRunState(root: string, batchId: string): BatchRunStateRecord | null {
|
|
247
|
+
const path = statePath(batchId);
|
|
248
|
+
if (!existsSync(join(root, path))) return null;
|
|
249
|
+
const parsed: unknown = JSON.parse(readSecureProjectFile(root, path));
|
|
250
|
+
validateRecordShape(parsed, batchId);
|
|
251
|
+
return parsed;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function ensureSecureDirectory(root: string, relative: string): string {
|
|
255
|
+
// review-4: create the directory with lstat verification so a pre-existing
|
|
256
|
+
// symlink cannot redirect state writes outside the project; mirrors the
|
|
257
|
+
// kernel storage boundary.
|
|
258
|
+
const target = join(root, relative);
|
|
259
|
+
const parent = dirname(target);
|
|
260
|
+
if (!existsSync(parent)) mkdirSync(parent, { recursive: true });
|
|
261
|
+
if (existsSync(target)) {
|
|
262
|
+
const stats = lstatSync(target);
|
|
263
|
+
if (!stats.isDirectory()) throw new Error(`${relative} exists but is not a directory`);
|
|
264
|
+
} else {
|
|
265
|
+
mkdirSync(target);
|
|
266
|
+
}
|
|
267
|
+
return target;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function writeFileAtomically(root: string, relative: string, bytes: string): void {
|
|
271
|
+
// review-4: no-symlink atomic write; lstat each created directory and the
|
|
272
|
+
// target so writes cannot be redirected by a pre-existing symlink.
|
|
273
|
+
const target = join(root, relative);
|
|
274
|
+
const targetDir = dirname(target);
|
|
275
|
+
const stats = lstatSync(targetDir);
|
|
276
|
+
if (!stats.isDirectory()) throw new Error(`${dirname(relative)} is not a directory`);
|
|
277
|
+
const tempPath = `${target}.${randomUUID()}.tmp`; let fd: number | null = null;
|
|
278
|
+
try {
|
|
279
|
+
fd = openSync(tempPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 0o600);
|
|
280
|
+
writeFileSync(fd, bytes, "utf8");
|
|
281
|
+
closeSync(fd);
|
|
282
|
+
fd = null;
|
|
283
|
+
renameSync(tempPath, target);
|
|
284
|
+
} finally {
|
|
285
|
+
if (fd !== null) closeSync(fd);
|
|
286
|
+
if (existsSync(tempPath)) { try { rmSync(tempPath); } catch { /* temp already moved */ } }
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export function writeBatchRunState(
|
|
291
|
+
root: string,
|
|
292
|
+
record: BatchRunStateRecord,
|
|
293
|
+
): BatchRunStateRecord {
|
|
294
|
+
const path = statePath(record.batch_id);
|
|
295
|
+
validateRecordShape(record, record.batch_id);
|
|
296
|
+
return withKernelStoreLock(root, () => {
|
|
297
|
+
const existing = existsSync(join(root, path)) ? readSecureProjectFile(root, path) : null;
|
|
298
|
+
if (existing !== null && existing === canonicalBytes(record)) return record;
|
|
299
|
+
const stored: BatchRunStateRecord = {
|
|
300
|
+
...record,
|
|
301
|
+
updated_at: new Date().toISOString(),
|
|
302
|
+
};
|
|
303
|
+
// review-4: secure directory + atomic no-symlink write.
|
|
304
|
+
ensureSecureDirectory(root, join(".imm", "state", "batches"));
|
|
305
|
+
writeFileAtomically(root, path, canonicalBytes(stored));
|
|
306
|
+
return stored;
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export interface BatchRunReport {
|
|
311
|
+
contract: "assurance_kernel/batch_run_report/v1";
|
|
312
|
+
batch_id: string;
|
|
313
|
+
initiative_slug: string;
|
|
314
|
+
batch_state: BatchRunState;
|
|
315
|
+
children: BatchChildRun[];
|
|
316
|
+
commits: string[];
|
|
317
|
+
reason: string | null;
|
|
318
|
+
/** The single next action for the operator. */
|
|
319
|
+
next_action: string;
|
|
320
|
+
created_at: string;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function reportPath(batchId: string): string {
|
|
324
|
+
validateBatchId(batchId);
|
|
325
|
+
return join(".imm", "state", "batches", `${batchId}.report.json`);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** Persist one current stop report per batch. Terminal reports are immutable;
|
|
329
|
+
* a resumable needs_human report may be replaced by the later stop reached
|
|
330
|
+
* after a fresh literal-user confirmation. */
|
|
331
|
+
export function writeBatchRunReport(root: string, report: BatchRunReport): BatchRunReport {
|
|
332
|
+
const relative = reportPath(report.batch_id);
|
|
333
|
+
return withKernelStoreLock(root, () => {
|
|
334
|
+
const path = join(root, relative);
|
|
335
|
+
if (existsSync(path)) {
|
|
336
|
+
const original: unknown = JSON.parse(readSecureProjectFile(root, relative));
|
|
337
|
+
if (
|
|
338
|
+
typeof original !== "object" ||
|
|
339
|
+
original === null ||
|
|
340
|
+
(original as BatchRunReport).contract !== "assurance_kernel/batch_run_report/v1"
|
|
341
|
+
)
|
|
342
|
+
throw new Error(`batch run report ${report.batch_id} has an unknown contract`);
|
|
343
|
+
const prior = original as BatchRunReport;
|
|
344
|
+
if (canonicalReportBytes(prior) === canonicalReportBytes(report)) return prior;
|
|
345
|
+
if (prior.batch_state !== "needs_human") return prior;
|
|
346
|
+
}
|
|
347
|
+
ensureSecureDirectory(root, join(".imm", "state", "batches"));
|
|
348
|
+
writeFileAtomically(root, relative, canonicalReportBytes(report));
|
|
349
|
+
return report;
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function canonicalReportBytes(report: BatchRunReport): string {
|
|
354
|
+
return `${JSON.stringify(report, null, 2)}\n`;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/** All terminal batch states; needs_human is a park, not terminal. */
|
|
358
|
+
export function isTerminalBatchState(state: BatchRunState): boolean {
|
|
359
|
+
return state === "completed" || state === "budget_stopped" || state === "failed" || state === "rejected";
|
|
360
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { GithubInitiativeObservation } from "../github_issue_tracker";
|
|
2
|
+
|
|
3
|
+
export interface BatchPlanBudget {
|
|
4
|
+
max_children: number;
|
|
5
|
+
deadline_at: string;
|
|
6
|
+
qa_failure_limit: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface BatchPlanBudgetInput {
|
|
10
|
+
max_children?: number;
|
|
11
|
+
deadline_at?: string;
|
|
12
|
+
qa_failure_limit?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface BatchPlanDigestChild {
|
|
16
|
+
task_id: string;
|
|
17
|
+
intent_path: string;
|
|
18
|
+
intent_revision: number;
|
|
19
|
+
intent_content_hash: string;
|
|
20
|
+
blocked_by: string[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type BatchPlanChildStatus =
|
|
24
|
+
| "enrollable"
|
|
25
|
+
| "already_settled"
|
|
26
|
+
| "already_owned"
|
|
27
|
+
| "needs_human"
|
|
28
|
+
| "blocked";
|
|
29
|
+
|
|
30
|
+
export interface BatchPlanChild {
|
|
31
|
+
task_id: string;
|
|
32
|
+
slice_id: string;
|
|
33
|
+
blocked_by: string[];
|
|
34
|
+
status: BatchPlanChildStatus;
|
|
35
|
+
reason: "critical" | "invalid_intent" | "dependency_unavailable" | null;
|
|
36
|
+
intent_path: string | null;
|
|
37
|
+
intent_revision: number | null;
|
|
38
|
+
intent_content_hash: string | null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface BatchPlan {
|
|
42
|
+
contract: "assurance_kernel/batch_plan/v1";
|
|
43
|
+
initiative_slug: string;
|
|
44
|
+
confirmation_time: string;
|
|
45
|
+
tracker_observation: GithubInitiativeObservation;
|
|
46
|
+
children: BatchPlanChild[];
|
|
47
|
+
enrollable: BatchPlanDigestChild[];
|
|
48
|
+
plan_digest: string;
|
|
49
|
+
budget: BatchPlanBudget;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface ProjectBatchPlanInput {
|
|
53
|
+
confirmation_time: string;
|
|
54
|
+
budget?: BatchPlanBudgetInput;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type InitiativeObservationReader = (
|
|
58
|
+
root: string,
|
|
59
|
+
initiativeSlug: string,
|
|
60
|
+
) => Promise<GithubInitiativeObservation>;
|
|
@@ -2,15 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
## Shared Guards
|
|
4
4
|
|
|
5
|
-
-
|
|
6
|
-
|
|
7
|
-
-
|
|
5
|
+
- On explicit invocation, read only common constraints and selected section routes
|
|
6
|
+
from `dist/`; nested modes, examples, recovery, and references load on demand.
|
|
7
|
+
- Ask only when missing information would change the goal, scope, observable behavior, compatibility, risk acceptance, a protected effect, or a fact only the user can supply. Resolve repository facts and delegated technical choices with bounded evidence instead of asking.
|
|
8
|
+
- Keep edits inside the user-requested Direct scope or the enrolled TaskIntent acceptance and `scope_hint`.
|
|
9
|
+
- Stage only explicit task-owned paths. Never use `git add .` or `git add -A` in a dirty worktree.
|
|
10
|
+
- Do not create, switch, or delete Git worktrees; operate only in the Host launch directory.
|
|
8
11
|
- Record reproducible evidence before reporting closure.
|
|
9
|
-
-
|
|
12
|
+
- Required verification must pass before reporting completion; disclosing a gap is not a substitute. Autonomously diagnose, repair, and rerun failing conventional local checks within the authorized scope; never delete, skip, or weaken a valid check to manufacture a pass. If a required check remains failing or cannot run, report the work as incomplete with the concrete blocker.
|
|
13
|
+
- Inspect unknown test scripts before execution. A script named `test` that
|
|
14
|
+
deploys, writes production data, or uses credentials is a protected effect,
|
|
15
|
+
not routine local verification; apply Host Confirmation Boundary before it runs.
|
|
16
|
+
- Unanswered questions block only dependent commitments or execution. Continue
|
|
17
|
+
independent read-only investigation and local alternative drafts; label drafts
|
|
18
|
+
unapproved and never treat silence as consent or finalize a dependent Spec.
|
|
19
|
+
- Disclose unrelated pre-existing failures without repairing them or widening
|
|
20
|
+
scope. If they prevent a required check from passing, report that blocker and
|
|
21
|
+
incomplete verification rather than claiming completion.
|
|
22
|
+
- Use bounded evidence to cover affected callers and state owners for shared-contract, security, migration, persistence, or authority changes; the category alone never requires full-directory reads. Stop expanding once the relevant behavior and verification are understood.
|
|
10
23
|
- Lead with conclusion, evidence, and Next Action.
|
|
11
24
|
- Advisory roles do not implement; Managed execution roles do not close QA.
|
|
12
25
|
- An explicit Managed Skill entry stops host-native mutation and routes scope changes to `imm-planner`; Managed execution remains inside its accepted boundary.
|
|
13
26
|
|
|
27
|
+
Before local edits or checks, read Verification and Local Recovery below. Read-only
|
|
28
|
+
framing does not load that execution guidance.
|
|
29
|
+
|
|
30
|
+
## Verification and Local Recovery
|
|
31
|
+
|
|
32
|
+
- Select required checks from the requested outcome, affected behavior, and project requirements before claiming completion. Documentation changes use relevant link, mirror, and contract checks; local logic uses focused tests; shared behavior, build configuration, cross-module changes, and release preparation widen regression coverage. Never reduce required checks merely because they fail.
|
|
33
|
+
- Reuse a recorded passing local result only while the relevant code, test inputs, command, dependencies, and environment remain unchanged. Changed or uncertain inputs require rerunning affected checks. Role handoff alone does not invalidate local evidence; local evidence never replaces Kernel-owned deterministic QA or fresh snapshot-bound Review.
|
|
34
|
+
- Recover routine local failures within the authorized scope: select an unused port, create temporary outputs, and remove only task-generated disposable files. Never overwrite user data or stop an unrelated process to free a port. Before dependency installation, inspect the existing project command, lifecycle scripts, network use, and credential effects; use it only within existing authorization, without dependency or lockfile changes. Unknown or protected effects follow Host Confirmation Boundary.
|
|
35
|
+
- Retry a failed ordinary operation only after new evidence or a relevant condition changes; do not repeat identical attempts indefinitely. This does not authorize retrying a failed native authority gate or an uncertain remote write.
|
|
36
|
+
- When changing a supported behavior or contract, update its existing tests, remove tests whose protected behavior has retired, merge redundant coverage, and remove orphaned fixtures/helpers within the affected scope. For each removal, identify the retired behavior or the remaining coverage and run the surviving related checks. Never delete by age, count, slowness, or flakiness alone. Temporary tests name their exit condition; no per-test registry or repository-wide audit is required.
|
|
37
|
+
|
|
14
38
|
## Workflow Activation
|
|
15
39
|
|
|
16
40
|
Ordinary host input stays host-native and does not run natural-language Managed
|
|
@@ -46,11 +70,15 @@ returns to `imm-planner`; an enrolled task resumes through `imm-loop` from the
|
|
|
46
70
|
current Assurance projection. Do not create or mutate workflow state while
|
|
47
71
|
classifying a non-mutating request.
|
|
48
72
|
|
|
49
|
-
Stage only explicit task-owned paths. Never use `git add .` or `git add -A` in a dirty worktree.
|
|
50
|
-
|
|
51
73
|
### Host Confirmation Boundary
|
|
52
74
|
|
|
53
|
-
Require exact host confirmation only for privileged effects
|
|
75
|
+
Require exact host confirmation only for privileged effects. For ordinary
|
|
76
|
+
non-Kernel operations, an explicit user approval already covering the same
|
|
77
|
+
operation, target, and impact is sufficient; ask again only for a material delta.
|
|
78
|
+
A generic continuation or configured preference is not blanket authorization.
|
|
79
|
+
Mandatory native gates and hash-bound manifest approvals still apply.
|
|
80
|
+
|
|
81
|
+
Privileged effects include:
|
|
54
82
|
|
|
55
83
|
- publish, release, deployment, or remote-system mutation;
|
|
56
84
|
- destructive or irreversible operations and Git history rewrite;
|
|
@@ -65,7 +93,10 @@ Routine Managed enrollment uses one current-Host native confirmation bound to th
|
|
|
65
93
|
|
|
66
94
|
State mutations, step activations, QA decisions, and plan switches remain
|
|
67
95
|
strictly sequential. Read-only work — repo exploration, advisory review,
|
|
68
|
-
host probing, planner research —
|
|
96
|
+
host probing, planner research — is eligible for parallel dispatch in
|
|
97
|
+
capability terms, but Pi schedules one foreground child at a time: launch one
|
|
98
|
+
child, consume its direct result, then decide whether another child is needed.
|
|
99
|
+
Do not assume multiple foreground Agents run as a concurrent batch.
|
|
69
100
|
|
|
70
101
|
Parallel dispatch is restricted by capability, not by a closed Skill list. Every
|
|
71
102
|
child delegation packet must enforce read-only advisory behavior: no file edits,
|
|
@@ -99,13 +130,15 @@ always run sequentially.
|
|
|
99
130
|
|
|
100
131
|
## Success Criteria
|
|
101
132
|
|
|
102
|
-
- Direct work closes only
|
|
103
|
-
- A Managed
|
|
133
|
+
- Direct work closes only when the requested result is delivered and the required verification passes: a failed or unavailable required check is reported as incomplete work with its concrete blocker, never as completion. Check breadth follows the request and established project requirements, not a universal full-repository rule. Apply Shared Guards and Verification and Local Recovery for in-scope repair and evidence reuse.
|
|
134
|
+
- A Managed task is ready to execute only when the target result, boundary, and
|
|
104
135
|
verification path are clear enough to avoid speculative edits.
|
|
105
|
-
- A Managed
|
|
106
|
-
verification path and the
|
|
107
|
-
-
|
|
108
|
-
|
|
136
|
+
- A Managed task is closable only when execution evidence proves the recorded
|
|
137
|
+
verification path and the TaskRecord still matches the enrolled TaskIntent boundary.
|
|
138
|
+
- Collect missing in-scope evidence and continue under the current owner. Missing
|
|
139
|
+
evidence alone does not require replanning. Escalate only a demonstrated
|
|
140
|
+
scope/acceptance mismatch or protected decision through the current owner's
|
|
141
|
+
revision or authorization path; never silently expand execution or QA scope.
|
|
109
142
|
|
|
110
143
|
## Retirement Completion
|
|
111
144
|
|
|
@@ -135,4 +168,4 @@ Immune-Brain commands and `.imm` state.
|
|
|
135
168
|
|
|
136
169
|
Prefer shallow discovery before full-file reads. Start with file lists,
|
|
137
170
|
`rg` hits, symbol/signature scans, and targeted line ranges; read whole files
|
|
138
|
-
only when the narrower evidence path cannot answer the active
|
|
171
|
+
only when the narrower evidence path cannot answer the active task question.
|
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: imm-agent-doc-maintain
|
|
3
|
-
description: Use
|
|
3
|
+
description: Use when the user explicitly requests Immune-Brain minimization of tracked AGENTS.md, CLAUDE.md, or GEMINI.md.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Immune-Brain: Agent Doc Maintain
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
Use [`../../dist/imm-agent-doc-maintain.md`](../../dist/imm-agent-doc-maintain.md)
|
|
9
|
+
as the canonical contract index, not a whole-document read.
|
|
10
|
+
This is a standalone host-native maintenance entry, not a Managed Path
|
|
11
11
|
continuation and not an `imm-loop` internal-role dispatch.
|
|
12
|
+
|
|
13
|
+
Mandatory constraints: audit is read-only. Mutation requires exact hash-bound
|
|
14
|
+
manifest approval and immediate revalidation. Preserve active Managed ownership;
|
|
15
|
+
never install project contracts, modify external files, or commit.
|
|
16
|
+
Interruption requires a fresh scan.
|
|
17
|
+
|
|
18
|
+
Section routes - load a section's instructions only when its branch applies.
|
|
19
|
+
Read each linked heading body up to the next heading; nested sections and
|
|
20
|
+
references load only under their own condition. Never read the whole contract
|
|
21
|
+
or all references as an entry prerequisite.
|
|
22
|
+
|
|
23
|
+
- common: [Authority Boundary](../../dist/imm-agent-doc-maintain.md#authority-boundary), [Invocation](../../dist/imm-agent-doc-maintain.md#invocation), [Non-goals](../../dist/imm-agent-doc-maintain.md#non-goals)
|
|
24
|
+
- audit or manifest preparation: [Inventory and Manifest](../../dist/imm-agent-doc-maintain.md#inventory-and-manifest), [Manifest Approval and Recovery](../../dist/imm-agent-doc-maintain.md#manifest-approval-and-recovery)
|
|
25
|
+
- approved mutation before any edit: [Mutation Envelope](../../dist/imm-agent-doc-maintain.md#mutation-envelope), [Approved Mutation](../../dist/imm-agent-doc-maintain.md#approved-mutation)
|
|
26
|
+
- completion: [Verify and Report](../../dist/imm-agent-doc-maintain.md#verify-and-report)
|
|
27
|
+
- interruption or changed candidate before resuming: [Manifest Approval and Recovery](../../dist/imm-agent-doc-maintain.md#manifest-approval-and-recovery); start a fresh scan
|
|
@@ -1,70 +1,30 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: imm-brainstorm
|
|
3
|
-
description: Use
|
|
3
|
+
description: Use when the user explicitly requests Immune-Brain requirement clarification.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Immune-Brain: Brainstorm
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
Direct requirements and adopted recommendations settle only the current nodes;
|
|
32
|
-
they never complete the Brainstorm session by themselves. Recompute the tree
|
|
33
|
-
after every response and continue through newly unlocked downstream branches.
|
|
34
|
-
Minimally clarify an ambiguous answer. If later evidence invalidates a settled
|
|
35
|
-
choice, reopen only that decision delta. An explicit defer stops its subtree and
|
|
36
|
-
becomes `BR-DEFER-*`, unless it still changes the current Result, interface, or
|
|
37
|
-
compatibility and therefore cannot be deferred.
|
|
38
|
-
|
|
39
|
-
Brainstorm is complete only when the frontier is empty and no blocked fact
|
|
40
|
-
prevents traversal. A zero-question fast path is valid only when the complete
|
|
41
|
-
seeded and dynamically expanded tree contains no unresolved user decision. If
|
|
42
|
-
the user stops early, emit every open node as `BR-Q-*` and do not mark the frame
|
|
43
|
-
planning-ready. Otherwise present a result-only summary as a non-blocking
|
|
44
|
-
correction window and retain final decisions in the `BR-*` manifest rather than
|
|
45
|
-
copying the question transcript. Do not ask the user to reconfirm decisions
|
|
46
|
-
reflected without change. If the summary introduces or changes a decision, ask
|
|
47
|
-
for explicit confirmation of only that decision delta and block Planner handoff
|
|
48
|
-
until it is answered. Agent judgment alone never confirms a proposed direction
|
|
49
|
-
or scope.
|
|
50
|
-
|
|
51
|
-
Brainstorm supports `default`, `roundtable`, and `adversarial` modes. All use the
|
|
52
|
-
same exhaustive frontier protocol; `roundtable` and `adversarial` add analysis
|
|
53
|
-
lenses only when explicitly selected by the user. Required failure, rollback,
|
|
54
|
-
compatibility, migration, and risk branches remain part of default traversal.
|
|
55
|
-
Consult ADRs and on-demand rejected-decision evidence only when a live branch
|
|
56
|
-
reaches that topic.
|
|
57
|
-
Architecture mapping is a bounded, read-only `arch-explorer` role selected
|
|
58
|
-
through the internal Loop bridge; it cannot write a Spec, Plan, or workflow
|
|
59
|
-
state. Pi's adapter may consume `brainstorm_ensemble` dispatch JSON to prepare
|
|
60
|
-
advisory Pi subagent envelopes, but envelope construction is not child execution
|
|
61
|
-
and does not transfer framing authority. Pi itself may launch those subagents,
|
|
62
|
-
collect completed child outputs, and feed them to
|
|
63
|
-
`normalizePiBrainstormAgentResults`; runtime does not call any agent, poll
|
|
64
|
-
background work, mutate state, or own final Spec/Plan authority.
|
|
65
|
-
Agreement becomes framing evidence, Disagreement becomes decision criteria or
|
|
66
|
-
`BR-Q-*`, and strong-model blockers become risks or verification requirements.
|
|
67
|
-
|
|
68
|
-
When framing discusses later execution, describe Enrollment only as the current
|
|
69
|
-
Host's native gate. Never recommend another Host, worktree, or unmanaged
|
|
70
|
-
implementation as a fallback for a failed Managed authority interaction.
|
|
8
|
+
Use [`../../dist/imm-brainstorm.md`](../../dist/imm-brainstorm.md) as the
|
|
9
|
+
canonical contract index, not a whole-document read. Explicit entry only: ordinary host questions do not
|
|
10
|
+
start this workflow.
|
|
11
|
+
|
|
12
|
+
Mandatory constraints before any action: Brainstorm is read-only — no code,
|
|
13
|
+
test, or runtime edits; no Spec, Plan, or workflow-state writes. All modes
|
|
14
|
+
produce a `brainstorm_framing` result with goal, constraints, unknowns,
|
|
15
|
+
readiness, and Next Action.
|
|
16
|
+
|
|
17
|
+
Section routes - load a section's instructions only when its branch applies.
|
|
18
|
+
Read each linked heading body up to the next heading; nested sections and
|
|
19
|
+
references load only under their own condition. Never read the whole contract
|
|
20
|
+
or all references as an entry prerequisite.
|
|
21
|
+
|
|
22
|
+
- common: all modes read [Shared Guards](../../dist/BASELINE.md#shared-guards), [Workflow Activation](../../dist/BASELINE.md#workflow-activation), [Host Confirmation Boundary](../../dist/BASELINE.md#host-confirmation-boundary), [Boundary](../../dist/imm-brainstorm.md#boundary), [Invocation modes](../../dist/imm-brainstorm.md#invocation-modes), [Default clarification](../../dist/imm-brainstorm.md#default-clarification), [Workflow Rules](../../dist/imm-brainstorm.md#workflow-rules), [Output artifact](../../dist/imm-brainstorm.md#output-artifact), [Output style](../../dist/imm-brainstorm.md#output-style), [Next Action](../../dist/imm-brainstorm.md#next-action)
|
|
23
|
+
- explicit thorough interrogation: [Explicit exhaustive interrogation](../../dist/imm-brainstorm.md#explicit-exhaustive-interrogation)
|
|
24
|
+
- optional research, including explicitly selected `roundtable` or `adversarial` advisory: [Research Dispatch](../../dist/imm-brainstorm.md#research-dispatch), [Brainstorm Ensemble Advisory](../../dist/imm-brainstorm.md#brainstorm-ensemble-advisory)
|
|
25
|
+
- requested framing document: [Output Language Policy](../../dist/BASELINE.md#output-language-policy)
|
|
26
|
+
|
|
27
|
+
When framing discusses later execution, describe Enrollment only as the
|
|
28
|
+
current Host's native gate. A failed Managed authority interaction stays
|
|
29
|
+
fail-closed: report one same-Host recovery action, never another Host,
|
|
30
|
+
worktree, or unmanaged implementation.
|