prae-cli 0.9.2 → 0.10.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.
@@ -1,456 +1,18 @@
1
- import { n as hostOf, r as withinAny, t as evaluate } from "./evaluate-BqLxhYiP.js";
2
- import { a as renderPacks, c as renderNav, d as FRAMEWORKS, f as cite, i as renderOverview, l as renderShell, n as renderApprovals, o as CONTROLS, r as renderCompliance, s as esc, t as navFor, u as VERDICT_LABEL$1 } from "./views-D4UEfbzt.js";
3
- import { c as fingerprint, g as chain, m as GENESIS, n as readEntries, p as verifySignature, r as verifyEntries, s as verifyBytes, t as Ledger, v as seal } from "./src-LA7NdLKb.js";
4
- import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, watch, writeFileSync } from "node:fs";
1
+ import { a as withinAny, r as hostOf, t as evaluate } from "./evaluate-DhoGQw0Z.js";
2
+ import { n as cite, t as FRAMEWORKS } from "./frameworks-BgZHGKiH.js";
3
+ import { t as parsePack } from "./pack-h5n43S0F.js";
4
+ import { a as verifyEntries, b as seal, g as GENESIS, h as verifySignature, l as verifyBytes, n as Ledger, r as readEntries, t as verifyGrowing, u as fingerprint, v as chain } from "./src-Cy-Acrgj.js";
5
+ import { a as renderPacks, c as renderNav, i as renderOverview, l as renderShell, n as renderApprovals, o as CONTROLS, r as renderCompliance, s as esc, t as navFor, u as VERDICT_LABEL$1 } from "./views-CUcixte0.js";
6
+ import { createRequire } from "node:module";
7
+ import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, statSync, watch, writeFileSync } from "node:fs";
5
8
  import { mkdir, readFile, writeFile } from "node:fs/promises";
6
9
  import { basename, dirname, extname, join, normalize, resolve, sep } from "node:path";
7
10
  import { createHash } from "node:crypto";
8
- import { parse } from "yaml";
9
11
  import { homedir } from "node:os";
12
+ import { parse } from "yaml";
10
13
  import { composePatchFile, preflightOps, revisionOf, takeSnapshot } from "dsh-overlay-check";
11
14
  import { createServer } from "node:http";
12
15
 
13
- //#region ../kernel/src/pack.ts
14
- function isObject(value) {
15
- return typeof value === "object" && value !== null && !Array.isArray(value);
16
- }
17
- var Check = class {
18
- errors = [];
19
- fail(path, message) {
20
- this.errors.push({
21
- path,
22
- message
23
- });
24
- }
25
- /** Reject any key the shape does not define. */
26
- keys(path, value, allowed) {
27
- for (const key of Object.keys(value)) if (!allowed.includes(key)) this.fail(`${path}.${key}`, `unknown field \`${key}\`; expected one of ${allowed.map((k) => `\`${k}\``).join(", ")}`);
28
- }
29
- text(path, value) {
30
- if (typeof value !== "string" || value.trim() === "") {
31
- this.fail(path, "expected a non-empty string");
32
- return;
33
- }
34
- return value;
35
- }
36
- /** A field that accepts one name or a list of them. */
37
- names(path, value) {
38
- if (typeof value === "string") return value;
39
- if (Array.isArray(value)) {
40
- const out = [];
41
- value.forEach((entry, index) => {
42
- const one = this.text(`${path}[${index}]`, entry);
43
- if (one !== void 0) out.push(one);
44
- });
45
- return out;
46
- }
47
- this.fail(path, "expected a string or an array of strings");
48
- }
49
- globs(path, value) {
50
- if (!Array.isArray(value)) {
51
- this.fail(path, "expected an array of path globs");
52
- return;
53
- }
54
- const out = [];
55
- value.forEach((entry, index) => {
56
- const one = this.text(`${path}[${index}]`, entry);
57
- if (one !== void 0) out.push(one);
58
- });
59
- return out;
60
- }
61
- number(path, value) {
62
- if (typeof value !== "number" || !Number.isFinite(value)) {
63
- this.fail(path, "expected a finite number");
64
- return;
65
- }
66
- return value;
67
- }
68
- };
69
- function readCommandMatch(check$1, path, value) {
70
- if (!isObject(value)) {
71
- check$1.fail(path, "expected an object");
72
- return;
73
- }
74
- check$1.keys(path, value, [
75
- "name",
76
- "token",
77
- "anyToken",
78
- "pipedTo",
79
- "pipeline"
80
- ]);
81
- if (value.name === void 0 && value.token === void 0 && value.anyToken === void 0 && value.pipedTo === void 0 && value.pipeline === void 0) check$1.fail(path, "expected `name`, `token`, `anyToken`, `pipedTo` or `pipeline`; a command matcher with none matches nothing");
82
- const out = {};
83
- if (value.token !== void 0) {
84
- const tokens = check$1.names(`${path}.token`, value.token);
85
- if (tokens !== void 0) out.token = tokens;
86
- }
87
- if (value.name !== void 0) {
88
- const names = check$1.names(`${path}.name`, value.name);
89
- if (names !== void 0) out.name = names;
90
- }
91
- if (value.anyToken !== void 0) {
92
- const tokens = check$1.names(`${path}.anyToken`, value.anyToken);
93
- if (tokens !== void 0) out.anyToken = tokens;
94
- }
95
- if (value.pipedTo !== void 0) {
96
- const pipes = check$1.names(`${path}.pipedTo`, value.pipedTo);
97
- if (pipes !== void 0) out.pipedTo = pipes;
98
- }
99
- if (value.pipeline !== void 0) {
100
- const shapes = check$1.names(`${path}.pipeline`, value.pipeline);
101
- if (shapes !== void 0) out.pipeline = shapes;
102
- }
103
- return out;
104
- }
105
- function readPathMatch(check$1, path, value) {
106
- if (!isObject(value)) {
107
- check$1.fail(path, "expected an object");
108
- return;
109
- }
110
- check$1.keys(path, value, [
111
- "arg",
112
- "outside",
113
- "inside"
114
- ]);
115
- if (value.outside === void 0 && value.inside === void 0) check$1.fail(path, "expected `inside` or `outside`; a path matcher with neither matches nothing");
116
- const out = {};
117
- if (value.arg !== void 0) {
118
- const arg = check$1.text(`${path}.arg`, value.arg);
119
- if (arg !== void 0) out.arg = arg;
120
- }
121
- if (value.outside !== void 0) {
122
- const globs = check$1.globs(`${path}.outside`, value.outside);
123
- if (globs !== void 0) out.outside = globs;
124
- }
125
- if (value.inside !== void 0) {
126
- const globs = check$1.globs(`${path}.inside`, value.inside);
127
- if (globs !== void 0) out.inside = globs;
128
- }
129
- return out;
130
- }
131
- function readHostMatch(check$1, path, value) {
132
- if (!isObject(value)) {
133
- check$1.fail(path, "expected an object");
134
- return;
135
- }
136
- check$1.keys(path, value, [
137
- "arg",
138
- "in",
139
- "notIn"
140
- ]);
141
- if (value.in === void 0 && value.notIn === void 0) check$1.fail(path, "expected `in` or `notIn`; a host matcher with neither matches nothing");
142
- const out = {};
143
- if (value.arg !== void 0) {
144
- const arg = check$1.text(`${path}.arg`, value.arg);
145
- if (arg !== void 0) out.arg = arg;
146
- }
147
- if (value.in !== void 0) {
148
- const list = check$1.globs(`${path}.in`, value.in);
149
- if (list !== void 0) out.in = list;
150
- }
151
- if (value.notIn !== void 0) {
152
- const list = check$1.globs(`${path}.notIn`, value.notIn);
153
- if (list !== void 0) out.notIn = list;
154
- }
155
- return out;
156
- }
157
- function readArgMatch(check$1, path, value) {
158
- if (!isObject(value)) {
159
- check$1.fail(path, "expected an object");
160
- return;
161
- }
162
- check$1.keys(path, value, [
163
- "name",
164
- "above",
165
- "below",
166
- "equals",
167
- "oneOf",
168
- "present"
169
- ]);
170
- const name = check$1.text(`${path}.name`, value.name);
171
- if (name === void 0) return void 0;
172
- const out = { name };
173
- if (value.above !== void 0) {
174
- const n = check$1.number(`${path}.above`, value.above);
175
- if (n !== void 0) out.above = n;
176
- }
177
- if (value.below !== void 0) {
178
- const n = check$1.number(`${path}.below`, value.below);
179
- if (n !== void 0) out.below = n;
180
- }
181
- if (value.equals !== void 0) out.equals = value.equals;
182
- if (value.oneOf !== void 0) if (!Array.isArray(value.oneOf)) check$1.fail(`${path}.oneOf`, "expected an array");
183
- else out.oneOf = value.oneOf;
184
- if (value.present !== void 0) if (typeof value.present !== "boolean") check$1.fail(`${path}.present`, "expected a boolean");
185
- else out.present = value.present;
186
- return out;
187
- }
188
- function readSecretsMatch(check$1, path, value) {
189
- if (!isObject(value)) {
190
- check$1.fail(path, "expected an object");
191
- return;
192
- }
193
- check$1.keys(path, value, [
194
- "types",
195
- "minConfidence",
196
- "atLeast"
197
- ]);
198
- const out = {};
199
- if (value.types !== void 0) {
200
- const types = check$1.globs(`${path}.types`, value.types);
201
- if (types !== void 0) out.types = types;
202
- }
203
- if (value.minConfidence !== void 0) {
204
- const n = check$1.number(`${path}.minConfidence`, value.minConfidence);
205
- if (n !== void 0) if (n < 0 || n > 1) check$1.fail(`${path}.minConfidence`, "expected a number between 0 and 1");
206
- else out.minConfidence = n;
207
- }
208
- if (value.atLeast !== void 0) {
209
- const n = check$1.number(`${path}.atLeast`, value.atLeast);
210
- if (n !== void 0) if (!Number.isInteger(n) || n < 1) check$1.fail(`${path}.atLeast`, "expected an integer of at least 1");
211
- else out.atLeast = n;
212
- }
213
- return out;
214
- }
215
- function readMatch(check$1, path, value) {
216
- if (!isObject(value)) {
217
- check$1.fail(path, "expected an object");
218
- return {};
219
- }
220
- check$1.keys(path, value, [
221
- "tool",
222
- "capability",
223
- "plugin",
224
- "agent",
225
- "command",
226
- "path",
227
- "host",
228
- "arg",
229
- "count",
230
- "secrets"
231
- ]);
232
- const out = {};
233
- for (const key of [
234
- "tool",
235
- "capability",
236
- "plugin",
237
- "agent"
238
- ]) if (value[key] !== void 0) {
239
- const names = check$1.names(`${path}.${key}`, value[key]);
240
- if (names !== void 0) out[key] = names;
241
- }
242
- if (value.command !== void 0) {
243
- const match = readCommandMatch(check$1, `${path}.command`, value.command);
244
- if (match !== void 0) out.command = match;
245
- }
246
- if (value.path !== void 0) {
247
- const match = readPathMatch(check$1, `${path}.path`, value.path);
248
- if (match !== void 0) out.path = match;
249
- }
250
- if (value.host !== void 0) {
251
- const match = readHostMatch(check$1, `${path}.host`, value.host);
252
- if (match !== void 0) out.host = match;
253
- }
254
- if (value.arg !== void 0) if (!Array.isArray(value.arg)) check$1.fail(`${path}.arg`, "expected an array of argument matchers");
255
- else {
256
- const list = [];
257
- value.arg.forEach((entry, index) => {
258
- const one = readArgMatch(check$1, `${path}.arg[${index}]`, entry);
259
- if (one !== void 0) list.push(one);
260
- });
261
- out.arg = list;
262
- }
263
- if (value.count !== void 0) if (!isObject(value.count)) check$1.fail(`${path}.count`, "expected an object");
264
- else {
265
- check$1.keys(`${path}.count`, value.count, ["above"]);
266
- const above = check$1.number(`${path}.count.above`, value.count.above);
267
- if (above !== void 0) out.count = { above };
268
- }
269
- if (value.secrets !== void 0) {
270
- const match = readSecretsMatch(check$1, `${path}.secrets`, value.secrets);
271
- if (match !== void 0) out.secrets = match;
272
- }
273
- return out;
274
- }
275
- function readEffect(check$1, path, value) {
276
- if (!isObject(value)) {
277
- check$1.fail(path, "expected an object");
278
- return;
279
- }
280
- const kind = value.kind === "hold" ? "ask" : value.kind;
281
- if (kind !== "deny" && kind !== "ask" && kind !== "correct" && kind !== "warn" && kind !== "allow") {
282
- check$1.fail(`${path}.kind`, "expected `allow`, `deny`, `hold` (or `ask`), `correct`, or `warn`");
283
- return;
284
- }
285
- check$1.keys(path, value, kind === "correct" ? [
286
- "kind",
287
- "set",
288
- "because"
289
- ] : ["kind", "because"]);
290
- const because = check$1.text(`${path}.because`, value.because);
291
- if (because === void 0) return void 0;
292
- if (kind === "correct") {
293
- if (!isObject(value.set) || Object.keys(value.set).length === 0) {
294
- check$1.fail(`${path}.set`, "expected a non-empty object of arguments to set");
295
- return;
296
- }
297
- return {
298
- kind,
299
- set: value.set,
300
- because
301
- };
302
- }
303
- return {
304
- kind,
305
- because
306
- };
307
- }
308
- function readRule(check$1, path, value) {
309
- if (!isObject(value)) {
310
- check$1.fail(path, "expected an object");
311
- return;
312
- }
313
- check$1.keys(path, value, [
314
- "id",
315
- "title",
316
- "description",
317
- "when",
318
- "effect",
319
- "frameworks"
320
- ]);
321
- const id = check$1.text(`${path}.id`, value.id);
322
- const effect = readEffect(check$1, `${path}.effect`, value.effect);
323
- if (value.when === void 0) check$1.fail(`${path}.when`, "expected an object");
324
- const when = readMatch(check$1, `${path}.when`, value.when ?? {});
325
- if (id === void 0 || effect === void 0) return void 0;
326
- const rule = {
327
- id,
328
- when,
329
- effect
330
- };
331
- if (value.title !== void 0) {
332
- const title = check$1.text(`${path}.title`, value.title);
333
- if (title !== void 0) rule.title = title;
334
- }
335
- if (value.description !== void 0) {
336
- const description = check$1.text(`${path}.description`, value.description);
337
- if (description !== void 0) rule.description = description;
338
- }
339
- if (value.frameworks !== void 0) {
340
- const frameworks = readFrameworks(check$1, `${path}.frameworks`, value.frameworks);
341
- if (frameworks !== void 0) rule.frameworks = frameworks;
342
- }
343
- return rule;
344
- }
345
- /**
346
- * A citation names a framework edition and controls the registry knows. A
347
- * typo here would be a rule that claims a mapping no view can render, so it
348
- * is a pack error rather than an ignored key — the same line the rest of the
349
- * parser takes.
350
- */
351
- function readFrameworks(check$1, path, value) {
352
- if (!isObject(value)) {
353
- check$1.fail(path, "expected an object keyed by framework id");
354
- return;
355
- }
356
- const out = {};
357
- for (const [framework, controls] of Object.entries(value)) {
358
- const known = FRAMEWORKS[framework];
359
- if (known === void 0) {
360
- check$1.fail(`${path}.${framework}`, `unknown framework; expected one of ${Object.keys(FRAMEWORKS).map((k) => `\`${k}\``).join(", ")}`);
361
- continue;
362
- }
363
- if (!Array.isArray(controls) || !controls.every((c) => typeof c === "string")) {
364
- check$1.fail(`${path}.${framework}`, "expected an array of control ids");
365
- continue;
366
- }
367
- const ids = controls;
368
- for (const control of ids) if (known.controls[control] === void 0) check$1.fail(`${path}.${framework}`, `unknown control \`${control}\` in ${known.name}`);
369
- if (ids.length > 0) out[framework] = [...new Set(ids)];
370
- }
371
- return out;
372
- }
373
- /** Parse and validate a pack. Never throws; every problem lands in `errors`. */
374
- function parsePack(source) {
375
- let raw;
376
- try {
377
- raw = JSON.parse(source);
378
- } catch (error) {
379
- return {
380
- ok: false,
381
- errors: [{
382
- path: "",
383
- message: `invalid JSON: ${error instanceof Error ? error.message : String(error)}`
384
- }]
385
- };
386
- }
387
- const check$1 = new Check();
388
- if (!isObject(raw)) return {
389
- ok: false,
390
- errors: [{
391
- path: "",
392
- message: "expected an object"
393
- }]
394
- };
395
- check$1.keys("", raw, [
396
- "prae",
397
- "pack",
398
- "version",
399
- "description",
400
- "owner",
401
- "baseline",
402
- "rules"
403
- ]);
404
- if (raw.prae !== "0.1") check$1.fail(".prae", "expected `\"0.1\"`; this is the only pack version this build reads");
405
- const name = check$1.text(".pack", raw.pack);
406
- const version = check$1.text(".version", raw.version);
407
- const description = raw.description === void 0 ? void 0 : check$1.text(".description", raw.description);
408
- const owner = raw.owner === void 0 ? void 0 : check$1.text(".owner", raw.owner);
409
- let baseline;
410
- if (raw.baseline !== void 0) if (!isObject(raw.baseline)) check$1.fail(".baseline", "expected an object");
411
- else {
412
- check$1.keys(".baseline", raw.baseline, ["capabilities"]);
413
- const caps = check$1.globs(".baseline.capabilities", raw.baseline.capabilities);
414
- if (caps !== void 0) baseline = { capabilities: caps };
415
- }
416
- const rules = [];
417
- if (!Array.isArray(raw.rules)) check$1.fail(".rules", "expected an array");
418
- else {
419
- const seen = /* @__PURE__ */ new Set();
420
- raw.rules.forEach((entry, index) => {
421
- const rule = readRule(check$1, `rules[${index}]`, entry);
422
- if (rule === void 0) return;
423
- if (seen.has(rule.id)) {
424
- check$1.fail(`rules[${index}].id`, `duplicate rule id \`${rule.id}\``);
425
- return;
426
- }
427
- seen.add(rule.id);
428
- rules.push(rule);
429
- });
430
- }
431
- if (check$1.errors.length > 0) return {
432
- ok: false,
433
- errors: check$1.errors
434
- };
435
- if (name === void 0 || version === void 0) return {
436
- ok: false,
437
- errors: check$1.errors
438
- };
439
- return {
440
- ok: true,
441
- pack: {
442
- prae: "0.1",
443
- pack: name,
444
- version,
445
- ...description === void 0 ? {} : { description },
446
- ...owner === void 0 ? {} : { owner },
447
- ...baseline === void 0 ? {} : { baseline },
448
- rules
449
- }
450
- };
451
- }
452
-
453
- //#endregion
454
16
  //#region ../dsh/src/chain.ts
455
17
  const STAGES = [
456
18
  {
@@ -707,14 +269,14 @@ function readPackFile(file, policyKey, origin = "workspace") {
707
269
  reason: `invalid YAML: ${e instanceof Error ? e.message : String(e)}`
708
270
  };
709
271
  }
710
- const parsed = parsePack(json);
711
- if (!parsed.ok) return {
272
+ const parsed$1 = parsePack(json);
273
+ if (!parsed$1.ok) return {
712
274
  file,
713
- reason: parsed.errors.map((e) => `${e.path || "<root>"}: ${e.message}`).join("; ")
275
+ reason: parsed$1.errors.map((e) => `${e.path || "<root>"}: ${e.message}`).join("; ")
714
276
  };
715
277
  return {
716
278
  file,
717
- pack: parsed.pack,
279
+ pack: parsed$1.pack,
718
280
  origin,
719
281
  sha256,
720
282
  ...policyKey === void 0 ? {} : { signedBy: fingerprint(policyKey) }
@@ -809,6 +371,7 @@ function merge(packs, rejected) {
809
371
  };
810
372
  const baseline = packs.find((p) => p.pack.baseline !== void 0)?.pack.baseline;
811
373
  const only = packs.length === 1 ? packs[0] : void 0;
374
+ const expires = packs.map((p) => p.pack.expires_at).filter((e) => e !== void 0).sort((a, b) => Date.parse(a) - Date.parse(b))[0];
812
375
  return {
813
376
  packs,
814
377
  rejected,
@@ -818,6 +381,7 @@ function merge(packs, rejected) {
818
381
  pack: only?.pack.pack ?? packs.map((p) => p.pack.pack).join("+"),
819
382
  version: only?.pack.version ?? createHash("sha256").update(packs.map((p) => p.sha256).join("")).digest("hex").slice(0, 12),
820
383
  ...baseline === void 0 ? {} : { baseline },
384
+ ...expires === void 0 ? {} : { expires_at: expires },
821
385
  rules: packs.flatMap((p) => p.pack.rules)
822
386
  }
823
387
  };
@@ -843,7 +407,7 @@ function row(entry, pack) {
843
407
  <div class="call">${esc(call)}</div>
844
408
  <div class="actor">${esc(actor)}</div>
845
409
  <div class="clause">${esc(entry.rule ?? "—")}${cited}</div>
846
- <div class="verdict v-${esc(entry.verdict)}">${esc(VERDICT_LABEL$1[entry.verdict] ?? entry.verdict)}</div>
410
+ <div class="verdict v-${esc(entry.verdict)}">${esc(VERDICT_LABEL$1[entry.verdict] ?? entry.verdict)}${entry.shadow === true ? " · SHADOW" : ""}</div>
847
411
  <div class="seal">${esc(seal(entry))}</div>
848
412
  </div>`;
849
413
  }
@@ -851,12 +415,14 @@ function renderLedgerView(entries, verification, options) {
851
415
  const counts = {};
852
416
  const packs = /* @__PURE__ */ new Set();
853
417
  const sessions = /* @__PURE__ */ new Set();
418
+ let shadowed = 0;
854
419
  for (const entry of entries) {
855
- counts[entry.verdict] = (counts[entry.verdict] ?? 0) + 1;
420
+ if (entry.shadow === true) shadowed += 1;
421
+ else counts[entry.verdict] = (counts[entry.verdict] ?? 0) + 1;
856
422
  packs.add(`${entry.pack.name} ${entry.pack.version}`);
857
423
  if (entry.session !== void 0) sessions.add(entry.session);
858
424
  }
859
- const held = entries.filter((e) => e.verdict === "ask");
425
+ const held = entries.filter((e) => e.verdict === "ask" && e.shadow !== true);
860
426
  const pack = [...packs][0] ?? "—";
861
427
  const now = Date.parse(options.generatedAt);
862
428
  const chainOk = verification.ok;
@@ -866,6 +432,7 @@ function renderLedgerView(entries, verification, options) {
866
432
  <div class="meta">${esc([
867
433
  `${sessions.size || 1} session${sessions.size === 1 ? "" : "s"}`,
868
434
  `${entries.length.toLocaleString("en-US")} decisions`,
435
+ ...shadowed === 0 ? [] : [`${shadowed} in shadow mode (recorded, not enforced)`],
869
436
  options.retention === void 0 ? "retention local" : `retention ${options.retention}`
870
437
  ].join(" · "))}</div>
871
438
  </div>
@@ -1201,16 +768,60 @@ function jsonOrNull(path) {
1201
768
  return null;
1202
769
  }
1203
770
  }
1204
- function readLedger(path) {
1205
- if (!existsSync(path)) return [];
1206
- const out = [];
1207
- for (const line of readFileSync(path, "utf8").split("\n")) {
771
+ const parsed = /* @__PURE__ */ new Map();
772
+ function parseLines(text, into) {
773
+ for (const line of text.split("\n")) {
1208
774
  if (line.trim() === "") continue;
1209
775
  try {
1210
- out.push(JSON.parse(line));
776
+ into.push(JSON.parse(line));
1211
777
  } catch {}
1212
778
  }
1213
- return out;
779
+ }
780
+ /**
781
+ * The ledger's rows. A ledger is append-only, so the parse is kept and only
782
+ * the bytes appended since are read: a long demo grows it to tens of
783
+ * thousands of rows, and every endpoint the console polls reads it — parsed
784
+ * whole each time, that kept the server too busy to answer anyone. A file
785
+ * that shrank or was replaced is read again from the start. A half-written
786
+ * last line is left unread until its newline arrives.
787
+ */
788
+ function readLedger(path) {
789
+ if (!existsSync(path)) {
790
+ parsed.delete(path);
791
+ return [];
792
+ }
793
+ const stat$1 = statSync(path);
794
+ let entry = parsed.get(path);
795
+ if (entry === void 0 || entry.ino !== stat$1.ino || stat$1.size < entry.size) {
796
+ entry = {
797
+ ino: stat$1.ino,
798
+ size: 0,
799
+ mtimeMs: 0,
800
+ offset: 0,
801
+ rows: []
802
+ };
803
+ parsed.set(path, entry);
804
+ }
805
+ if (stat$1.size !== entry.size || stat$1.mtimeMs !== entry.mtimeMs) {
806
+ const length = stat$1.size - entry.offset;
807
+ if (length > 0) {
808
+ const buf = Buffer.alloc(length);
809
+ const fd = openSync(path, "r");
810
+ try {
811
+ readSync(fd, buf, 0, length, entry.offset);
812
+ } finally {
813
+ closeSync(fd);
814
+ }
815
+ const end = buf.lastIndexOf(10);
816
+ if (end >= 0) {
817
+ parseLines(buf.subarray(0, end).toString("utf8"), entry.rows);
818
+ entry.offset += end + 1;
819
+ }
820
+ }
821
+ entry.size = stat$1.size;
822
+ entry.mtimeMs = stat$1.mtimeMs;
823
+ }
824
+ return entry.rows.slice();
1214
825
  }
1215
826
  /** The defaults the demo writes; a caller with other names overrides them. */
1216
827
  const DEFAULT_FILES = {
@@ -1229,7 +840,8 @@ function readRecord(dir, files) {
1229
840
  discovery: jsonOrNull(resolve(dir, files.discovery)),
1230
841
  report: jsonOrNull(resolve(dir, files.report)),
1231
842
  guardians: jsonOrNull(resolve(dir, files.guardians)),
1232
- pack: jsonOrNull(resolve(dir, files.pack))
843
+ pack: jsonOrNull(resolve(dir, files.pack)),
844
+ crownJewel: jsonOrNull(resolve(dir, "crown-jewel.json"))
1233
845
  };
1234
846
  }
1235
847
  /** The red-team campaign and its runs, as the runner wrote them; empty when none. */
@@ -1238,15 +850,19 @@ function readRedteam(dir, files) {
1238
850
  if (!existsSync(root)) return {
1239
851
  campaign: null,
1240
852
  runs: [],
1241
- steering: []
853
+ steering: [],
854
+ history: []
1242
855
  };
1243
856
  const runs = readdirSync(root).filter((n) => n.endsWith(".json")).sort().filter((n) => n !== "campaign.json").map((n) => jsonOrNull(resolve(root, n))).filter((r) => r !== null);
1244
857
  const steeringDir = resolve(root, "steering");
1245
858
  const steering = existsSync(steeringDir) ? readdirSync(steeringDir).filter((n) => n.endsWith(".json")).sort().map((n) => jsonOrNull(resolve(steeringDir, n))).filter((r) => r !== null && typeof r === "object" && "objective" in r) : [];
859
+ const historyDir = resolve(steeringDir, "history");
860
+ const history = existsSync(historyDir) ? readdirSync(historyDir).filter((n) => n.endsWith(".json")).sort().reverse().map((n) => jsonOrNull(resolve(historyDir, n))).filter((r) => r !== null) : [];
1246
861
  return {
1247
862
  campaign: jsonOrNull(resolve(root, "campaign.json")),
1248
863
  runs,
1249
- steering
864
+ steering,
865
+ history
1250
866
  };
1251
867
  }
1252
868
 
@@ -1647,6 +1263,23 @@ function verifyRecord(record, tamper, repair = false) {
1647
1263
  repaired: false,
1648
1264
  signatures: null
1649
1265
  };
1266
+ if (tamper === void 0) {
1267
+ const keyPath$1 = resolve(dir, files.publicKey);
1268
+ const publicKey = existsSync(keyPath$1) ? readFileSync(keyPath$1, "utf8") : void 0;
1269
+ const v = verifyGrowing(path, publicKey);
1270
+ return {
1271
+ ok: v.ok,
1272
+ entries: v.entries,
1273
+ problems: [...v.problems],
1274
+ tampered: null,
1275
+ repaired: false,
1276
+ signatures: v.signatures === null || publicKey === void 0 ? null : {
1277
+ checked: v.signatures.checked,
1278
+ failed: [...v.signatures.failed],
1279
+ fingerprint: fingerprint(publicKey)
1280
+ }
1281
+ };
1282
+ }
1650
1283
  let text = readFileSync(path, "utf8");
1651
1284
  let tampered = null;
1652
1285
  if (tamper !== void 0) {
@@ -1754,19 +1387,19 @@ function packsOf(record) {
1754
1387
  function complianceOf(record) {
1755
1388
  const { dir, files } = record;
1756
1389
  const ledgerPath = resolve(dir, files.ledger);
1757
- const text = existsSync(ledgerPath) ? readFileSync(ledgerPath, "utf8") : "";
1758
- let entries = [];
1759
- try {
1760
- entries = readEntries(text);
1761
- } catch {
1762
- entries = [];
1763
- }
1390
+ const entries = readLedger(ledgerPath);
1391
+ const grown = existsSync(ledgerPath) ? verifyGrowing(ledgerPath) : void 0;
1392
+ const verification = grown === void 0 ? verifyEntries("") : {
1393
+ ok: grown.ok,
1394
+ entries: grown.entries,
1395
+ problems: grown.problems
1396
+ };
1764
1397
  const { set } = packSetOf(record);
1765
1398
  const pack = set.packs.length > 0 ? set.effective : void 0;
1766
1399
  const data = {
1767
1400
  ...pack === void 0 ? {} : { pack },
1768
1401
  entries,
1769
- verification: verifyEntries(text),
1402
+ verification,
1770
1403
  generatedAt: (/* @__PURE__ */ new Date()).toISOString()
1771
1404
  };
1772
1405
  return {
@@ -1789,6 +1422,23 @@ function complianceOf(record) {
1789
1422
  };
1790
1423
  }
1791
1424
  /**
1425
+ * The API's version is prae-cli's: every `/api/*` response names it in
1426
+ * `X-PRAE-API-Version`, so a client can tell which contract answered. The
1427
+ * contract follows the package's semver — a breaking change to a route bumps
1428
+ * the major — and a route on its way out carries `Deprecation` and `Sunset`
1429
+ * (RFC 9745 / RFC 8594) for at least one minor release before it goes.
1430
+ */
1431
+ /**
1432
+ * The most rows `/api/record` sends: the newest ones. The console keeps the
1433
+ * same window (apps/console/src/data/hooks.ts); `trimmed` counts the rest.
1434
+ */
1435
+ const RECORD_WINDOW = 25e3;
1436
+ const API_VERSION = createRequire(import.meta.url)("../package.json").version;
1437
+ /** Name the contract on an API response; call before the status is written. */
1438
+ function apiVersion(res) {
1439
+ res.setHeader("x-prae-api-version", API_VERSION);
1440
+ }
1441
+ /**
1792
1442
  * The record over HTTP, for the Command Center (apps/console): the same
1793
1443
  * files `prae console` renders, as JSON, plus one event stream that says
1794
1444
  * when the ledger moved. Read-only, from files on disk, nothing computed
@@ -1801,6 +1451,7 @@ function complianceOf(record) {
1801
1451
  function handleApi(req, res, record) {
1802
1452
  const route = (req.url ?? "").split("?")[0] ?? "";
1803
1453
  if (!route.startsWith("/api/")) return false;
1454
+ apiVersion(res);
1804
1455
  const json = (body, status = 200) => {
1805
1456
  res.statusCode = status;
1806
1457
  res.setHeader("content-type", "application/json; charset=utf-8");
@@ -1815,19 +1466,56 @@ function handleApi(req, res, record) {
1815
1466
  connection: "keep-alive"
1816
1467
  });
1817
1468
  res.write(": connected\n\n");
1818
- let sent = readRecord(dir, files).ledger.length;
1469
+ const ledgerPath = resolve(dir, files.ledger);
1470
+ const size = () => existsSync(ledgerPath) ? statSync(ledgerPath).size : 0;
1471
+ let offset = (() => {
1472
+ if (!existsSync(ledgerPath)) return 0;
1473
+ return readFileSync(ledgerPath).lastIndexOf(10) + 1;
1474
+ })();
1819
1475
  const push = () => {
1820
- const rows = readRecord(dir, files).ledger;
1821
- if (rows.length < sent) sent = 0;
1822
- const fresh = rows.slice(sent);
1823
- sent = rows.length;
1476
+ const end = size();
1477
+ if (end < offset) {
1478
+ offset = 0;
1479
+ res.write("event: reset\ndata: {}\n\n");
1480
+ }
1481
+ const fresh = [];
1482
+ if (end > offset) {
1483
+ const bytes = Buffer.alloc(end - offset);
1484
+ const fd = openSync(ledgerPath, "r");
1485
+ try {
1486
+ readSync(fd, bytes, 0, bytes.length, offset);
1487
+ } finally {
1488
+ closeSync(fd);
1489
+ }
1490
+ let at = 0;
1491
+ for (;;) {
1492
+ const nl = bytes.indexOf(10, at);
1493
+ if (nl === -1) break;
1494
+ const l = bytes.subarray(at, nl).toString("utf8");
1495
+ if (l.trim() !== "") try {
1496
+ fresh.push(JSON.parse(l));
1497
+ } catch {
1498
+ break;
1499
+ }
1500
+ at = nl + 1;
1501
+ }
1502
+ offset += at;
1503
+ }
1824
1504
  if (fresh.length > 0) res.write(`data: ${JSON.stringify(fresh)}\n\n`);
1825
1505
  else res.write(": keepalive\n\n");
1826
1506
  };
1827
- const watcher = existsSync(dir) ? watch(dir, { persistent: false }, () => push()) : void 0;
1507
+ let soon;
1508
+ const schedule = () => {
1509
+ soon ??= setTimeout(() => {
1510
+ soon = void 0;
1511
+ push();
1512
+ }, 250);
1513
+ };
1514
+ const watcher = existsSync(dir) ? watch(dir, { persistent: false }, schedule) : void 0;
1828
1515
  const keep = setInterval(push, 2e3);
1829
1516
  res.on("close", () => {
1830
1517
  clearInterval(keep);
1518
+ if (soon !== void 0) clearTimeout(soon);
1831
1519
  watcher?.close();
1832
1520
  });
1833
1521
  return true;
@@ -1851,11 +1539,32 @@ function handleApi(req, res, record) {
1851
1539
  json(readRedteam(dir, files));
1852
1540
  return true;
1853
1541
  }
1542
+ if (route === "/api/status") {
1543
+ json({
1544
+ dir,
1545
+ files: Object.fromEntries(Object.entries(files).map(([name, file]) => {
1546
+ const path = resolve(dir, file);
1547
+ if (!existsSync(path)) return [name, null];
1548
+ const stat$1 = statSync(path);
1549
+ return [name, {
1550
+ size: stat$1.size,
1551
+ mtime: stat$1.mtimeMs
1552
+ }];
1553
+ }))
1554
+ });
1555
+ return true;
1556
+ }
1854
1557
  const snapshot = readRecord(dir, files);
1855
1558
  switch (route) {
1856
- case "/api/record":
1857
- json(snapshot);
1559
+ case "/api/record": {
1560
+ const over = snapshot.ledger.length - RECORD_WINDOW;
1561
+ json(over > 0 ? {
1562
+ ...snapshot,
1563
+ ledger: snapshot.ledger.slice(over),
1564
+ trimmed: over
1565
+ } : snapshot);
1858
1566
  break;
1567
+ }
1859
1568
  case "/api/ledger":
1860
1569
  json(snapshot.ledger);
1861
1570
  break;
@@ -1871,20 +1580,6 @@ function handleApi(req, res, record) {
1871
1580
  case "/api/pack":
1872
1581
  json(snapshot.pack);
1873
1582
  break;
1874
- case "/api/status":
1875
- json({
1876
- dir,
1877
- files: Object.fromEntries(Object.entries(files).map(([name, file]) => {
1878
- const path = resolve(dir, file);
1879
- if (!existsSync(path)) return [name, null];
1880
- const stat$1 = statSync(path);
1881
- return [name, {
1882
- size: stat$1.size,
1883
- mtime: stat$1.mtimeMs
1884
- }];
1885
- }))
1886
- });
1887
- break;
1888
1583
  default: json({ error: "not found" }, 404);
1889
1584
  }
1890
1585
  return true;
@@ -1901,6 +1596,7 @@ function serveConsole(root, port, record) {
1901
1596
  res.end();
1902
1597
  return;
1903
1598
  }
1599
+ if ((req.url ?? "").startsWith("/api/")) apiVersion(res);
1904
1600
  if (record !== void 0 && handleApi(req, res, record)) return;
1905
1601
  const target = safePath(root, req.url ?? "/");
1906
1602
  if (target === void 0) {
@@ -2052,6 +1748,21 @@ const USAGE = `⌜▪⌟ prae — operator policy for agent harnesses
2052
1748
  --pack-dir adds the workspace's own packs (e.g. .prae/packs) beside
2053
1749
  that pack, loaded and watched as a set; a set that would not load is
2054
1750
  not mounted (see \`prae check <dir>\`).
1751
+
1752
+ prae init claude-code (--settings <file> | --scope user|project)
1753
+ [--shadow | --enforce] [--pack <file>] [--ledger <file>]
1754
+ [--dir <dir>] [--apply] [--remove]
1755
+ Register PRAE as a Claude Code PreToolUse hook: every tool call,
1756
+ built-in or MCP, judged by the pack and sealed in a signed ledger
1757
+ (default ~/.prae/claude-code/). Shadow (the default) records and
1758
+ never blocks; --enforce returns deny/ask to Claude Code. Prints the
1759
+ settings diff; writes only with --apply. --remove takes out PRAE's
1760
+ own entry and nothing else. See docs/pilot-claude-code.md.
1761
+
1762
+ prae hook claude-code [--shadow | --enforce] --pack <file> --ledger <file>
1763
+ [--key <private.pem>] [--settings <file>]
1764
+ The command that settings entry runs: reads one PreToolUse payload on
1765
+ stdin and answers through the hook's output contract.
2055
1766
  `;
2056
1767
  function fail(message) {
2057
1768
  console.error(`prae: ${message}`);
@@ -2111,7 +1822,7 @@ async function check(path, argv = []) {
2111
1822
  * from the findings, so there is nothing here to be careful about printing.
2112
1823
  */
2113
1824
  async function scanFile(path, packPath, json) {
2114
- const { scan } = await import("./src-CwBEFhww.js");
1825
+ const { scan } = await import("./src-DMoYsdfo.js");
2115
1826
  let content;
2116
1827
  try {
2117
1828
  content = await readFile(path, "utf8");
@@ -2151,13 +1862,13 @@ async function scanFile(path, packPath, json) {
2151
1862
  * not come up inspecting nothing.
2152
1863
  */
2153
1864
  async function mcp(root, packPath, ledgerPath) {
2154
- const { serveMcp } = await import("./mcp-BCXbpqu4.js");
1865
+ const { serveMcp } = await import("./mcp-D521EoP6.js");
2155
1866
  const { resolve: resolve$1 } = await import("node:path");
2156
1867
  const bundled = [join(import.meta.dirname, "secret-catcher.prae.json"), join(import.meta.dirname, "..", "..", "..", "examples", "secret-catcher.prae.json")].find((candidate) => existsSync(candidate));
2157
1868
  const chosen = packPath ?? bundled;
2158
1869
  if (chosen === void 0) return fail("mcp needs --pack <file>; no bundled secret-catcher found");
2159
1870
  const pack = await loadPack(chosen);
2160
- const { Ledger: Ledger$1 } = await import("./src-7X6dO7gC.js");
1871
+ const { Ledger: Ledger$1 } = await import("./src-wG36kT6K.js");
2161
1872
  const ledger = ledgerPath === void 0 ? void 0 : Ledger$1.open(ledgerPath);
2162
1873
  console.error(`prae mcp: serving ${resolve$1(root)} under ${pack.pack} ${pack.version}` + (ledgerPath === void 0 ? " (no ledger)" : ` · ledger ${ledgerPath}`));
2163
1874
  await serveMcp({
@@ -2209,7 +1920,7 @@ async function explain(path, tool, pairs) {
2209
1920
  * a key that arrived beside it is checked against nothing.
2210
1921
  */
2211
1922
  async function keygen(dir) {
2212
- const { generateKeypair, fingerprint: fingerprint$1 } = await import("./src-7X6dO7gC.js");
1923
+ const { generateKeypair, fingerprint: fingerprint$1 } = await import("./src-wG36kT6K.js");
2213
1924
  const { writeFileSync: writeFileSync$1 } = await import("node:fs");
2214
1925
  const keys = generateKeypair();
2215
1926
  const priv = join(dir, "prae-signing.pem");
@@ -2232,7 +1943,7 @@ async function keygen(dir) {
2232
1943
  async function keygenSealed(role, dir) {
2233
1944
  if (role !== "operator" && role !== "policy" && role !== "ledger") fail("--role is operator, policy or ledger");
2234
1945
  const stem = role === "ledger" ? "prae-signing" : role;
2235
- const { sealedKeypair, fingerprint: fingerprint$1 } = await import("./src-7X6dO7gC.js");
1946
+ const { sealedKeypair, fingerprint: fingerprint$1 } = await import("./src-wG36kT6K.js");
2236
1947
  const { writeFileSync: writeFileSync$1 } = await import("node:fs");
2237
1948
  const keys = sealedKeypair(process.env.PRAE_KEY_PASSPHRASE ?? await askHidden("passphrase: "));
2238
1949
  const pub = join(dir, `${stem}.pub.pem`);
@@ -2245,11 +1956,11 @@ async function keygenSealed(role, dir) {
2245
1956
  }
2246
1957
  /** A detached signature over a pack's bytes (PRA-107). */
2247
1958
  async function signPack(packPath, keyPath) {
2248
- const { signBytes, unsealPrivateKey, fingerprint: fingerprint$1 } = await import("./src-7X6dO7gC.js");
1959
+ const { signBytes, unsealPrivateKey, fingerprint: fingerprint$1 } = await import("./src-wG36kT6K.js");
2249
1960
  const { createPublicKey: createPublicKey$1 } = await import("node:crypto");
2250
1961
  const bytes = readFileSync(packPath);
2251
- const parsed = parsePack(bytes.toString("utf8"));
2252
- if (!parsed.ok) fail(`${packPath} does not parse; nothing signed`);
1962
+ const parsed$1 = parsePack(bytes.toString("utf8"));
1963
+ if (!parsed$1.ok) fail(`${packPath} does not parse; nothing signed`);
2253
1964
  const passphrase = process.env.PRAE_KEY_PASSPHRASE ?? await askHidden("passphrase: ");
2254
1965
  let key;
2255
1966
  try {
@@ -2262,7 +1973,7 @@ async function signPack(packPath, keyPath) {
2262
1973
  type: "spki",
2263
1974
  format: "pem"
2264
1975
  });
2265
- console.log(`signed ${packPath}.sig (${parsed.pack.pack} ${parsed.pack.version})`);
1976
+ console.log(`signed ${packPath}.sig (${parsed$1.pack.pack} ${parsed$1.pack.version})`);
2266
1977
  console.log(`policy key ${fingerprint$1(pub.toString())}`);
2267
1978
  }
2268
1979
  /** Read a line from the terminal without echoing it. */
@@ -2290,7 +2001,7 @@ function askHidden(prompt) {
2290
2001
  });
2291
2002
  }
2292
2003
  async function receipt(path, seq, keyPath, out) {
2293
- const { Ledger: Ledger$1, makeReceipt, fingerprint: fingerprint$1 } = await import("./src-7X6dO7gC.js");
2004
+ const { Ledger: Ledger$1, makeReceipt, fingerprint: fingerprint$1 } = await import("./src-wG36kT6K.js");
2294
2005
  const { readFileSync: readFileSync$1 } = await import("node:fs");
2295
2006
  const { createPublicKey: createPublicKey$1 } = await import("node:crypto");
2296
2007
  const privateKey = readFileSync$1(keyPath, "utf8");
@@ -2320,14 +2031,14 @@ async function receipt(path, seq, keyPath, out) {
2320
2031
  * file and sets the exit code, so scripts can branch on it.
2321
2032
  */
2322
2033
  async function verifyReceiptCmd(path) {
2323
- const { checkReceipt } = await import("./receipt-check-DQK_gVPc.js");
2324
- let parsed;
2034
+ const { checkReceipt } = await import("./receipt-check-CUuEjGHL.js");
2035
+ let parsed$1;
2325
2036
  try {
2326
- parsed = JSON.parse(await readFile(path, "utf8"));
2037
+ parsed$1 = JSON.parse(await readFile(path, "utf8"));
2327
2038
  } catch {
2328
2039
  return fail(`cannot read a receipt from ${path}`);
2329
2040
  }
2330
- const result = checkReceipt(parsed);
2041
+ const result = checkReceipt(parsed$1);
2331
2042
  for (const line of result.lines) if (result.ok) console.log(line);
2332
2043
  else console.error(line);
2333
2044
  if (!result.ok) process.exit(1);
@@ -2345,14 +2056,14 @@ async function test(suitePath, packOverride, asJson) {
2345
2056
  } catch {
2346
2057
  return fail(`cannot read ${suitePath}`);
2347
2058
  }
2348
- let parsed;
2059
+ let parsed$1;
2349
2060
  try {
2350
- parsed = JSON.parse(raw);
2061
+ parsed$1 = JSON.parse(raw);
2351
2062
  } catch (error) {
2352
2063
  return fail(`${suitePath} is not valid JSON: ${error instanceof Error ? error.message : error}`);
2353
2064
  }
2354
- const { parseSuite, renderSuite, runSuite } = await import("./test-run-CCDgJ0Dp.js");
2355
- const suite = parseSuite(parsed);
2065
+ const { parseSuite, renderSuite, runSuite } = await import("./test-run-C8dHjIE2.js");
2066
+ const suite = parseSuite(parsed$1);
2356
2067
  if (!suite.ok) {
2357
2068
  console.error(`prae: ${suitePath} is not a valid test suite`);
2358
2069
  for (const message of suite.errors) console.error(` ${message}`);
@@ -2362,7 +2073,7 @@ async function test(suitePath, packOverride, asJson) {
2362
2073
  if (chosen === void 0) return fail("test needs a pack: set `pack` in the suite or pass --pack");
2363
2074
  const pack = await loadPack(chosen);
2364
2075
  const { capabilityForTool } = await import("capmark");
2365
- const { scan } = await import("./src-CwBEFhww.js");
2076
+ const { scan } = await import("./src-DMoYsdfo.js");
2366
2077
  const inspect = pack.rules.some((rule) => rule.when.secrets !== void 0) ? (path) => {
2367
2078
  try {
2368
2079
  return scan(readFileSync(path, "utf8"));
@@ -2391,14 +2102,14 @@ async function verify$1(path, keyPath, checkpointPath) {
2391
2102
  const last = entries.at(-1);
2392
2103
  console.log(`${result.entries} entries · chain intact${last === void 0 ? "" : ` · seal ${seal(last)}`}`);
2393
2104
  if (checkpointPath !== void 0) {
2394
- const { checkCheckpoint } = await import("./checkpoint-C_q_Rpa5.js");
2105
+ const { checkCheckpoint } = await import("./checkpoint-BqJ8xwky.js");
2395
2106
  const publicKey = keyPath === void 0 ? void 0 : readFileSync(keyPath, "utf8");
2396
2107
  const verdict = checkCheckpoint(entries, JSON.parse(readFileSync(checkpointPath, "utf8")), publicKey);
2397
2108
  for (const line of verdict.lines) (verdict.ok ? console.log : console.error)(`checkpoint: ${line}`);
2398
2109
  if (!verdict.ok) process.exitCode = 1;
2399
2110
  }
2400
2111
  if (keyPath !== void 0) {
2401
- const { verifySignature: verifySignature$1, fingerprint: fingerprint$1 } = await import("./src-7X6dO7gC.js");
2112
+ const { verifySignature: verifySignature$1, fingerprint: fingerprint$1 } = await import("./src-wG36kT6K.js");
2402
2113
  const { readFileSync: readFileSync$1 } = await import("node:fs");
2403
2114
  const publicKey = readFileSync$1(keyPath, "utf8");
2404
2115
  let signed = 0;
@@ -2476,7 +2187,7 @@ async function report(path, out, packPath) {
2476
2187
  * that step is a proposal a human approves, and it starts from this list.
2477
2188
  */
2478
2189
  async function instructions(from, asJson) {
2479
- const { ingest } = await import("./src-d4c_n6wd.js");
2190
+ const { ingest } = await import("./src-CDYyZyeU.js");
2480
2191
  const result = ingest(from);
2481
2192
  if (asJson) {
2482
2193
  console.log(JSON.stringify(result, null, 2));
@@ -2512,7 +2223,7 @@ async function instructions(from, asJson) {
2512
2223
  * on purpose rather than something that happened to their policy.
2513
2224
  */
2514
2225
  async function propose(from, options) {
2515
- const [{ ingest }, { extract }] = await Promise.all([import("./src-d4c_n6wd.js"), import("./src-CBGZVvt7.js")]);
2226
+ const [{ ingest }, { extract }] = await Promise.all([import("./src-CDYyZyeU.js"), import("./src-BlmsTGdY.js")]);
2516
2227
  const read = ingest(from);
2517
2228
  if (read.files.length === 0) {
2518
2229
  console.log(`No instruction files found at or above ${from}.`);
@@ -2572,9 +2283,9 @@ async function consoleOut(path, argv) {
2572
2283
  const instructionsDir = flag(argv, "instructions");
2573
2284
  if (instructionsDir !== void 0) {
2574
2285
  const [{ ingest }, { extract }, { renderInstructions }] = await Promise.all([
2575
- import("./src-d4c_n6wd.js"),
2576
- import("./src-CBGZVvt7.js"),
2577
- import("./instructions-ya0LVnZy.js")
2286
+ import("./src-CDYyZyeU.js"),
2287
+ import("./src-BlmsTGdY.js"),
2288
+ import("./instructions-DihPiiC2.js")
2578
2289
  ]);
2579
2290
  const read = ingest(instructionsDir);
2580
2291
  instructionsView = renderInstructions(data, read, extract(read.statements));
@@ -2615,9 +2326,12 @@ async function consoleOut(path, argv) {
2615
2326
  console.log(`record at ${url}api/record · events at ${url}api/events`);
2616
2327
  console.log("loopback only, read-only. ctrl-c to stop.");
2617
2328
  }
2329
+ /** The bundled baseline: beside the built code, or in examples/ in source. */
2330
+ function bundledBaseline() {
2331
+ return [join(import.meta.dirname, "prae-baseline.prae.json"), join(import.meta.dirname, "..", "..", "..", "examples", "prae-baseline.prae.json")].find((candidate) => existsSync(candidate));
2332
+ }
2618
2333
  async function init(argv) {
2619
- const bundledBaseline = [join(import.meta.dirname, "prae-baseline.prae.json"), join(import.meta.dirname, "..", "..", "..", "examples", "prae-baseline.prae.json")].find((candidate) => existsSync(candidate));
2620
- const packPath = flag(argv, "pack") ?? bundledBaseline;
2334
+ const packPath = flag(argv, "pack") ?? bundledBaseline();
2621
2335
  if (packPath === void 0) fail("init needs --pack <file>; no bundled baseline found");
2622
2336
  if (flag(argv, "pack") === void 0) {
2623
2337
  console.log("No --pack given; mounting the built-in baseline (8 rules).");
@@ -2704,7 +2418,7 @@ async function chainMatrix(path, argv) {
2704
2418
  const catalogue = guardiansPath === void 0 ? [] : JSON.parse(readFileSync(guardiansPath, "utf8")).guardians;
2705
2419
  const guardianOf = (rule) => rule === void 0 ? void 0 : catalogue.find((g) => g.rules.includes(rule) || (g.armed ?? []).includes(rule))?.name;
2706
2420
  const keyPath = flag(argv, "key");
2707
- const { verifySignature: verifySignature$1 } = await import("./src-7X6dO7gC.js");
2421
+ const { verifySignature: verifySignature$1 } = await import("./src-wG36kT6K.js");
2708
2422
  const publicKey = keyPath === void 0 ? void 0 : readFileSync(keyPath, "utf8");
2709
2423
  const matrix = integrityMatrix(entries, pack, guardianOf).map((m) => {
2710
2424
  const entry = entries.find((e) => e.seq === m.seq);
@@ -2735,7 +2449,7 @@ async function chainMatrix(path, argv) {
2735
2449
  if (!chainOk || sigFailed) process.exitCode = 1;
2736
2450
  }
2737
2451
  async function checkpoint(path, keyPath, out) {
2738
- const { makeCheckpoint } = await import("./checkpoint-C_q_Rpa5.js");
2452
+ const { makeCheckpoint } = await import("./checkpoint-BqJ8xwky.js");
2739
2453
  let ledger;
2740
2454
  try {
2741
2455
  ledger = Ledger.open(path);
@@ -2756,9 +2470,9 @@ async function checkpoint(path, keyPath, out) {
2756
2470
  }
2757
2471
  }
2758
2472
  async function exportLedger(path, options) {
2759
- const { toOcsf, ndjson, sendToHec } = await import("./ocsf-DhVGbBUw.js");
2760
- const { createRequire } = await import("node:module");
2761
- const { version } = createRequire(import.meta.url)("../package.json");
2473
+ const { toOcsf, ndjson, sendToHec } = await import("./ocsf-DF9cMiQJ.js");
2474
+ const { createRequire: createRequire$1 } = await import("node:module");
2475
+ const { version } = createRequire$1(import.meta.url)("../package.json");
2762
2476
  let ledger;
2763
2477
  try {
2764
2478
  ledger = Ledger.open(path);
@@ -2885,7 +2599,26 @@ async function main(argv) {
2885
2599
  ...flag(rest, "out") === void 0 ? {} : { out: flag(rest, "out") }
2886
2600
  });
2887
2601
  }
2888
- case "init": return init(rest);
2602
+ case "init":
2603
+ if (rest[0] === "claude-code") {
2604
+ const { initClaudeCode, stableNode } = await import("./init-DRd-IRR6.js");
2605
+ try {
2606
+ return initClaudeCode(rest.slice(1), {
2607
+ cwd: process.cwd(),
2608
+ node: stableNode(process.execPath, process.env.PATH),
2609
+ bin: resolve(process.argv[1] ?? ""),
2610
+ baseline: bundledBaseline()
2611
+ });
2612
+ } catch (error) {
2613
+ return fail(error instanceof Error ? error.message : String(error));
2614
+ }
2615
+ }
2616
+ return init(rest);
2617
+ case "hook": {
2618
+ if (rest[0] !== "claude-code") fail("hook takes claude-code");
2619
+ const { runHook } = await import("./hook-Gll2spTP.js");
2620
+ return runHook(rest.slice(1));
2621
+ }
2889
2622
  case "--help":
2890
2623
  case "-h":
2891
2624
  case void 0: