@event4u/agent-config 9.36.0 → 10.1.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.
Files changed (89) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +117 -2
  3. package/agents/templates/.ai-council.yml.example +31 -51
  4. package/dist/agent-src/commands/analyze/conformance.md +15 -4
  5. package/dist/agent-src/rules/session-canary.md +28 -0
  6. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +17 -0
  7. package/dist/agent-src/skills/adr-create/SKILL.md +5 -0
  8. package/dist/agent-src/skills/check-refs/SKILL.md +5 -0
  9. package/dist/agent-src/skills/lint-skills/SKILL.md +5 -0
  10. package/dist/agent-src/skills/md-language-check/SKILL.md +5 -0
  11. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  12. package/dist/cli-delegate/{chunk-SFGARMAB.js → chunk-2H5SE7DU.js} +24 -4
  13. package/dist/cli-delegate/{chunk-U7CMAIOC.js → chunk-3JUTT26I.js} +1 -1
  14. package/dist/cli-delegate/{chunk-JP53LNVT.js → chunk-HJSKTMIF.js} +6 -14
  15. package/dist/cli-delegate/{chunk-QEEB4LHO.js → chunk-LRXJK6GN.js} +1 -1
  16. package/dist/cli-delegate/{chunk-NR7HSN3P.js → chunk-NX5AXSLD.js} +3 -3
  17. package/dist/cli-delegate/{chunk-AG47LAGQ.js → chunk-TVGXH7VA.js} +1 -1
  18. package/dist/cli-delegate/{chunk-55RI5UXK.js → chunk-XT2YTJCZ.js} +22 -18
  19. package/dist/cli-delegate/{chunk-JEJABGKB.js → chunk-XTC5ARB6.js} +16 -12
  20. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  21. package/dist/cli-delegate/cmd_brand_status.js +1 -1
  22. package/dist/cli-delegate/cmd_conformance.js +1037 -196
  23. package/dist/cli-delegate/cmd_doctor.js +3 -3
  24. package/dist/cli-delegate/cmd_explain.js +2 -2
  25. package/dist/cli-delegate/cmd_export.js +4 -4
  26. package/dist/cli-delegate/cmd_handoff.js +3 -3
  27. package/dist/cli-delegate/cmd_mcp_available.js +1 -1
  28. package/dist/cli-delegate/cmd_migrate.js +2 -2
  29. package/dist/cli-delegate/cmd_packs_active.js +2 -2
  30. package/dist/cli-delegate/cmd_prune.js +4 -4
  31. package/dist/cli-delegate/cmd_route_audit.js +2 -2
  32. package/dist/cli-delegate/cmd_route_explain.js +1 -1
  33. package/dist/cli-delegate/cmd_session_recycle.js +1 -1
  34. package/dist/cli-delegate/cmd_settings_get.js +1 -1
  35. package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
  36. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  37. package/dist/cli-delegate/cmd_sync.js +4 -4
  38. package/dist/cli-delegate/cmd_uninstall.js +4 -4
  39. package/dist/cli-delegate/cmd_update.js +1 -1
  40. package/dist/cli-delegate/cmd_upgrade.js +1 -1
  41. package/dist/cli-delegate/cmd_validate.js +4 -4
  42. package/dist/cli-delegate/cmd_versions.js +1 -1
  43. package/dist/discovery/deprecation-report.md +1 -1
  44. package/dist/discovery/discovery-manifest.json +8 -8
  45. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  46. package/dist/discovery/discovery-manifest.summary.md +1 -1
  47. package/dist/discovery/dormancy-report.md +1 -1
  48. package/dist/discovery/orphan-report.md +1 -1
  49. package/dist/discovery/packs.json +2 -2
  50. package/dist/discovery/trust-report.md +1 -1
  51. package/dist/discovery/workspaces.json +2 -2
  52. package/dist/hooks/dispatch.js +125 -89
  53. package/dist/install/install.mjs +22 -26
  54. package/dist/mcp/registry-manifest.json +1 -1
  55. package/dist/mcp/server.json +2 -2
  56. package/dist/mcp/server.mjs +1579 -616
  57. package/dist/scripts/_lib/agent_settings.js +39 -25
  58. package/dist/scripts/_lib/agent_settings.js.map +1 -1
  59. package/dist/scripts/condense.js +15 -2
  60. package/dist/scripts/condense.js.map +1 -1
  61. package/dist/server/routes/wizard.js +23 -3
  62. package/dist/server/routes/wizard.js.map +1 -1
  63. package/dist/server/schemas/settings.js +6 -6
  64. package/dist/server/schemas/settings.js.map +1 -1
  65. package/docs/contracts/settings-classes.md +14 -9
  66. package/package.json +1 -1
  67. package/src/agent-src/scripts/archive_completed_roadmaps.ts +17 -0
  68. package/src/config/agent-settings.template.yml +6 -36
  69. package/src/config/ci-local-parity.yml +44 -0
  70. package/src/scripts/_lib/agent_settings.ts +39 -25
  71. package/src/scripts/_lib/environment_detector.ts +52 -1
  72. package/src/scripts/_lib/untrusted_content.test.ts +175 -0
  73. package/src/scripts/_lib/untrusted_content.ts +184 -0
  74. package/src/scripts/ai_council/config.ts +62 -24
  75. package/src/scripts/before_complete_hook.ts +39 -0
  76. package/src/scripts/check_bridge_derivation.ts +89 -3
  77. package/src/scripts/check_ci_local_parity.ts +74 -0
  78. package/src/scripts/check_references.ts +15 -1
  79. package/src/scripts/condense.ts +16 -2
  80. package/src/scripts/conformance_scan.ts +200 -9
  81. package/src/scripts/council_cli.ts +95 -3
  82. package/src/scripts/hook_manifest.yaml +12 -6
  83. package/src/scripts/hooks/block_no_verify.ts +82 -9
  84. package/src/scripts/hooks/turn_end_gate_hook.ts +138 -105
  85. package/src/scripts/probe_promissory_closing.ts +180 -0
  86. package/src/scripts/probe_session_canary.ts +180 -0
  87. package/src/scripts/schemas/skill.schema.json +42 -0
  88. package/src/scripts/skill_linter.ts +57 -2
  89. package/src/server/schemas/settings.ts +6 -14
@@ -19,7 +19,7 @@ import {
19
19
  frontmatterPaths,
20
20
  proseEqual,
21
21
  splitFrontmatter
22
- } from "./chunk-SFGARMAB.js";
22
+ } from "./chunk-2H5SE7DU.js";
23
23
  import "./chunk-FTAISGDQ.js";
24
24
  import {
25
25
  atomic_write_json,
@@ -27,7 +27,7 @@ import {
27
27
  is_replay_mode
28
28
  } from "./chunk-EJITJBJX.js";
29
29
  import "./chunk-Q26HT4QT.js";
30
- import "./chunk-55RI5UXK.js";
30
+ import "./chunk-XT2YTJCZ.js";
31
31
  import "./chunk-PZWRPRWP.js";
32
32
  import "./chunk-M2DPGFZ4.js";
33
33
  import {
@@ -37,7 +37,7 @@ import {
37
37
  } from "./chunk-63WSP6YZ.js";
38
38
  import "./chunk-GRRPEPRN.js";
39
39
  import "./chunk-NXFWFAYV.js";
40
- import "./chunk-JEJABGKB.js";
40
+ import "./chunk-XTC5ARB6.js";
41
41
  import "./chunk-NDJLNC6Q.js";
42
42
  import {
43
43
  detectToolPresence
@@ -54,12 +54,12 @@ import {
54
54
 
55
55
  // src/scripts/_cli/cmd_conformance.ts
56
56
  var YAML = __toESM(require_dist(), 1);
57
- import * as fs11 from "node:fs";
58
- import * as os6 from "node:os";
59
- import * as path11 from "node:path";
57
+ import * as fs13 from "node:fs";
58
+ import * as os7 from "node:os";
59
+ import * as path13 from "node:path";
60
60
  import process5 from "node:process";
61
- import { spawnSync } from "node:child_process";
62
- import { fileURLToPath as fileURLToPath11, pathToFileURL as pathToFileURL11 } from "node:url";
61
+ import { spawnSync as spawnSync2 } from "node:child_process";
62
+ import { fileURLToPath as fileURLToPath13, pathToFileURL as pathToFileURL13 } from "node:url";
63
63
 
64
64
  // src/install/txlog.ts
65
65
  import { createReadStream, createWriteStream, existsSync, readFileSync, renameSync, statSync, unlinkSync } from "node:fs";
@@ -120,18 +120,18 @@ function routeTargetPathsPosix(target) {
120
120
  }
121
121
 
122
122
  // src/scripts/report_conformance_funnel.ts
123
- import * as fs10 from "node:fs";
124
- import * as os5 from "node:os";
125
- import * as path10 from "node:path";
123
+ import * as fs12 from "node:fs";
124
+ import * as os6 from "node:os";
125
+ import * as path12 from "node:path";
126
126
  import process4 from "node:process";
127
- import { fileURLToPath as fileURLToPath10, pathToFileURL as pathToFileURL10 } from "node:url";
127
+ import { fileURLToPath as fileURLToPath12, pathToFileURL as pathToFileURL12 } from "node:url";
128
128
 
129
129
  // src/scripts/conformance_scan.ts
130
- import { createHash } from "node:crypto";
131
- import fs6 from "node:fs";
132
- import os from "node:os";
133
- import path6 from "node:path";
134
- import { fileURLToPath as fileURLToPath6, pathToFileURL as pathToFileURL6 } from "node:url";
130
+ import { createHash as createHash3 } from "node:crypto";
131
+ import fs8 from "node:fs";
132
+ import os2 from "node:os";
133
+ import path8 from "node:path";
134
+ import { fileURLToPath as fileURLToPath8, pathToFileURL as pathToFileURL8 } from "node:url";
135
135
 
136
136
  // src/scripts/_lib/transcript_entry.ts
137
137
  function _isObject(v) {
@@ -1037,6 +1037,14 @@ function _empty_state() {
1037
1037
  verified_this_turn: false,
1038
1038
  ci_saw_pending: false,
1039
1039
  nonevidence_this_turn: 0,
1040
+ // Round 7 § Phase 1 — the last CI read, SESSION-scoped on purpose. Every
1041
+ // other counter here is turn-scoped because a stale POSITIVE would wrongly
1042
+ // vouch ("I verified"). This one is a NEGATIVE ("CI was not settled"), and a
1043
+ // stale negative only ever refuses more often — so surviving the turn
1044
+ // boundary preserves the freshness invariant rather than bypassing it. It
1045
+ // has to survive it: the measured failure is a completion claim in a LATER
1046
+ // turn than the poll it rests on.
1047
+ ci_last: null,
1040
1048
  checked_at: _now()
1041
1049
  };
1042
1050
  }
@@ -1164,6 +1172,7 @@ function _update(state, event, envelope) {
1164
1172
  if (session_id && session_id !== state["session_id"]) {
1165
1173
  state["session_id"] = session_id;
1166
1174
  state["verifications_this_session"] = 0;
1175
+ state["ci_last"] = null;
1167
1176
  state = _reset_turn(state, session_id);
1168
1177
  }
1169
1178
  let payload = envelope["payload"];
@@ -1189,6 +1198,12 @@ function _update(state, event, envelope) {
1189
1198
  } else {
1190
1199
  counts = false;
1191
1200
  }
1201
+ state["ci_last"] = {
1202
+ at: _now(),
1203
+ command: cmd.slice(0, 512),
1204
+ pending,
1205
+ settled: pending === 0 && counts === true
1206
+ };
1192
1207
  }
1193
1208
  state["last_verification"] = {
1194
1209
  command: cmd.slice(0, 512),
@@ -1302,15 +1317,758 @@ if (isCliEntry) {
1302
1317
  process.exit(main4(process.argv.slice(2)));
1303
1318
  }
1304
1319
 
1305
- // src/scripts/hooks/evidence_independence.ts
1306
- import crypto from "node:crypto";
1307
- import fs5 from "node:fs";
1308
- import path5 from "node:path";
1320
+ // src/scripts/hooks/turn_end_gate_hook.ts
1321
+ import * as crypto2 from "node:crypto";
1322
+ import * as fs6 from "node:fs";
1323
+ import * as path6 from "node:path";
1324
+ import { fileURLToPath as fileURLToPath6, pathToFileURL as pathToFileURL6 } from "node:url";
1325
+
1326
+ // src/scripts/hooks/end_review_nudge_hook.ts
1327
+ import { spawnSync } from "node:child_process";
1328
+ import * as crypto from "node:crypto";
1329
+ import * as fs5 from "node:fs";
1330
+ import * as os from "node:os";
1331
+ import * as path5 from "node:path";
1309
1332
  import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL5 } from "node:url";
1310
- var EXIT_ALLOW4 = 0;
1333
+
1334
+ // src/scripts/_lib/review_skipped_record.ts
1335
+ function isNonNegInt(n) {
1336
+ return typeof n === "number" && Number.isFinite(n) && Number.isInteger(n) && n >= 0;
1337
+ }
1338
+ var HIGH_RISK_DIFF_LINES = 200;
1339
+ function buildReviewSkippedLine(input) {
1340
+ const errors = [];
1341
+ if (!isNonNegInt(input.diff_lines)) {
1342
+ errors.push("diff_lines must be a non-negative integer");
1343
+ }
1344
+ if (input.mutation_measure !== "exact" && input.mutation_measure !== "capped_approximation") {
1345
+ errors.push('mutation_measure must be "exact" or "capped_approximation"');
1346
+ }
1347
+ if (!input.ts) errors.push("ts (ISO-8601 UTC) is required");
1348
+ if (!input.id) errors.push("id (ULID, UUID, or content hash) is required");
1349
+ if (errors.length) return { line: null, errors };
1350
+ const line = {
1351
+ schema_version: 1,
1352
+ id: input.id,
1353
+ ts: input.ts,
1354
+ work_id: input.work_id ?? `review-skipped-${input.ts}`,
1355
+ phase: "report",
1356
+ outcome: "skipped",
1357
+ confidence_band: "medium",
1358
+ risk_class: input.diff_lines >= HIGH_RISK_DIFF_LINES ? "high" : "medium",
1359
+ memory: { asks: 0, hits: 0 },
1360
+ verify: { claims: 0, first_try_passes: 0 },
1361
+ rules_applied: ["delegation-policy"],
1362
+ persona: null,
1363
+ input_kind: "prompt",
1364
+ type: "note",
1365
+ review_skipped: { diff_lines: input.diff_lines, mutation_measure: input.mutation_measure }
1366
+ };
1367
+ return { line, errors: [] };
1368
+ }
1369
+
1370
+ // src/scripts/hooks/envelope.ts
1371
+ var ENVELOPE_KEYS = [
1372
+ "schema_version",
1373
+ "platform",
1374
+ "event",
1375
+ "payload"
1376
+ ];
1377
+ function _isObject5(obj) {
1378
+ return typeof obj === "object" && obj !== null && !Array.isArray(obj);
1379
+ }
1380
+ function looks_like_envelope(obj) {
1381
+ if (!_isObject5(obj)) {
1382
+ return false;
1383
+ }
1384
+ return ENVELOPE_KEYS.every((key) => key in obj);
1385
+ }
1386
+ function unwrap(stdin_text, default_platform = "generic") {
1387
+ const text = (stdin_text ?? "").trim();
1388
+ if (!text) {
1389
+ return [{}, {}, default_platform];
1390
+ }
1391
+ let decoded;
1392
+ try {
1393
+ decoded = JSON.parse(text);
1394
+ } catch {
1395
+ return [{}, {}, default_platform];
1396
+ }
1397
+ if (looks_like_envelope(decoded)) {
1398
+ const env = decoded;
1399
+ let payload2 = env["payload"];
1400
+ if (!payload2 || !_isObject5(payload2)) {
1401
+ payload2 = {};
1402
+ }
1403
+ const platRaw = env["platform"];
1404
+ const platform = String(platRaw || default_platform);
1405
+ return [env, payload2, platform];
1406
+ }
1407
+ const payload = _isObject5(decoded) ? decoded : {};
1408
+ return [
1409
+ {
1410
+ schema_version: 1,
1411
+ platform: default_platform,
1412
+ event: "",
1413
+ native_event: "",
1414
+ session_id: "",
1415
+ workspace_root: "",
1416
+ payload,
1417
+ settings: {}
1418
+ },
1419
+ payload,
1420
+ default_platform
1421
+ ];
1422
+ }
1423
+
1424
+ // src/scripts/hooks/end_review_nudge_hook.ts
1425
+ var MUTATION_LINE_THRESHOLD = 50;
1426
+ var UNTRACKED_FILE_CAP = 20;
1427
+ var TRANSCRIPT_SCAN_MAX_BYTES = 50 * 1024 * 1024;
1428
+ function isSafeTranscriptPath(rawPath, opts = {}) {
1429
+ if (!rawPath || !/\.jsonl$/i.test(rawPath)) return false;
1430
+ const homeDir = opts.homeDir ?? os.homedir();
1431
+ const maxBytes = opts.maxBytes ?? TRANSCRIPT_SCAN_MAX_BYTES;
1432
+ try {
1433
+ const realHome = fs5.realpathSync(homeDir);
1434
+ const realPath = fs5.realpathSync(rawPath);
1435
+ const relative2 = path5.relative(realHome, realPath);
1436
+ if (relative2 === "" || relative2.startsWith("..") || path5.isAbsolute(relative2)) {
1437
+ return false;
1438
+ }
1439
+ const { size } = fs5.statSync(realPath);
1440
+ return size <= maxBytes;
1441
+ } catch {
1442
+ return false;
1443
+ }
1444
+ }
1445
+ var EXIT_WARN2 = 2;
1446
+ function parseNumstat(raw) {
1447
+ const rows = [];
1448
+ for (const line of raw.split("\n")) {
1449
+ const m = /^(\d+|-)\t(\d+|-)\t(.+)$/.exec(line);
1450
+ if (!m) continue;
1451
+ const addedStr = m[1];
1452
+ const deletedStr = m[2];
1453
+ rows.push({
1454
+ added: addedStr === "-" ? 0 : Number(addedStr),
1455
+ deleted: deletedStr === "-" ? 0 : Number(deletedStr),
1456
+ path: m[3]
1457
+ });
1458
+ }
1459
+ return rows;
1460
+ }
1461
+ var DOC_PATH_RE = /\.md$/i;
1462
+ function isDocPath(p) {
1463
+ return DOC_PATH_RE.test(p);
1464
+ }
1465
+ function nonDocMutatedLines(rows) {
1466
+ return rows.filter((r) => !isDocPath(r.path)).reduce((sum, r) => sum + r.added + r.deleted, 0);
1467
+ }
1468
+ function gitNumstatRows(cwd) {
1469
+ try {
1470
+ const r = spawnSync("git", ["diff", "--numstat", "HEAD"], {
1471
+ cwd,
1472
+ encoding: "utf8",
1473
+ maxBuffer: 16 * 1024 * 1024
1474
+ });
1475
+ if (r.status !== 0) return [];
1476
+ return parseNumstat(r.stdout ?? "");
1477
+ } catch {
1478
+ return [];
1479
+ }
1480
+ }
1481
+ function untrackedNonDocFiles(cwd) {
1482
+ try {
1483
+ const r = spawnSync("git", ["ls-files", "--others", "--exclude-standard"], {
1484
+ cwd,
1485
+ encoding: "utf8",
1486
+ maxBuffer: 16 * 1024 * 1024
1487
+ });
1488
+ if (r.status !== 0) return [];
1489
+ return (r.stdout ?? "").split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !isDocPath(l));
1490
+ } catch {
1491
+ return [];
1492
+ }
1493
+ }
1494
+ function untrackedFileLineCount(cwd, relPath) {
1495
+ try {
1496
+ const r = spawnSync("git", ["diff", "--numstat", "--no-index", "/dev/null", relPath], {
1497
+ cwd,
1498
+ encoding: "utf8",
1499
+ maxBuffer: 16 * 1024 * 1024
1500
+ });
1501
+ return parseNumstat(r.stdout ?? "").reduce((sum, row) => sum + row.added + row.deleted, 0);
1502
+ } catch {
1503
+ return 0;
1504
+ }
1505
+ }
1506
+ function totalNonDocMutatedLinesWithMeasure(cwd) {
1507
+ const trackedLines = nonDocMutatedLines(gitNumstatRows(cwd));
1508
+ const untracked = untrackedNonDocFiles(cwd);
1509
+ if (untracked.length > UNTRACKED_FILE_CAP) {
1510
+ return { lines: MUTATION_LINE_THRESHOLD + 1 + trackedLines, measure: "capped_approximation" };
1511
+ }
1512
+ const untrackedLines = untracked.reduce(
1513
+ (sum, relPath) => sum + untrackedFileLineCount(cwd, relPath),
1514
+ 0
1515
+ );
1516
+ return { lines: trackedLines + untrackedLines, measure: "exact" };
1517
+ }
1518
+ function isObject(v) {
1519
+ return typeof v === "object" && v !== null && !Array.isArray(v);
1520
+ }
1521
+ function str(v) {
1522
+ return typeof v === "string" ? v : "";
1523
+ }
1524
+ var DISPATCH_TOOL_RE = /^(agent|task)$/i;
1525
+ var REVIEW_SHAPED_LABEL_RE = /\b(review|judge|adversarial|critique|second[- ]opinion|blind\s+(pass|review)|cross-model)\b/i;
1526
+ var REVIEW_SHAPED_PROMPT_RE = /\b(code\s+review|neutral\s+review|adversarial|judge|critique|second[- ]opinion|blind\s+(pass|review)|cross-model)\b/i;
1527
+ var REVIEW_SKILL_NAME_RE = /^(review[-:]changes|judge[-:][a-z0-9-]+|adversarial-review|code-review)$/i;
1528
+ function scanTranscriptForReviewer(transcriptText) {
1529
+ for (const raw of transcriptText.split("\n")) {
1530
+ const line = raw.trim();
1531
+ if (!line) continue;
1532
+ let obj;
1533
+ try {
1534
+ obj = JSON.parse(line);
1535
+ } catch {
1536
+ continue;
1537
+ }
1538
+ if (!isObject(obj) || obj["type"] !== "assistant") continue;
1539
+ const msg = obj["message"];
1540
+ if (!isObject(msg)) continue;
1541
+ const content = msg["content"];
1542
+ if (!Array.isArray(content)) continue;
1543
+ for (const blk of content) {
1544
+ if (!isObject(blk) || blk["type"] !== "tool_use") continue;
1545
+ const name = str(blk["name"]);
1546
+ const input = blk["input"];
1547
+ const inputObj = isObject(input) ? input : {};
1548
+ if (DISPATCH_TOOL_RE.test(name)) {
1549
+ const label = [
1550
+ str(inputObj["description"]),
1551
+ str(inputObj["subagent_type"])
1552
+ ].filter(Boolean).join(" ");
1553
+ if (REVIEW_SHAPED_LABEL_RE.test(label)) return true;
1554
+ const prompt = str(inputObj["prompt"]);
1555
+ if (REVIEW_SHAPED_PROMPT_RE.test(prompt)) return true;
1556
+ continue;
1557
+ }
1558
+ if (/^skill$/i.test(name)) {
1559
+ const skillName = str(inputObj["skill"]) || str(inputObj["name"]) || str(inputObj["command"]);
1560
+ if (REVIEW_SKILL_NAME_RE.test(skillName)) return true;
1561
+ continue;
1562
+ }
1563
+ if (REVIEW_SKILL_NAME_RE.test(name)) return true;
1564
+ }
1565
+ }
1566
+ return false;
1567
+ }
1568
+ function buildAdvisoryLine(diffLines) {
1569
+ return `this session mutated ${diffLines} lines without a neutral review; spawn a cross-model reviewer before claiming done (delegation-policy / verify-budget)`;
1570
+ }
1571
+ function appendReviewSkippedTelemetry(workspaceRoot, diffLines, mutationMeasure) {
1572
+ if (is_replay_mode()) return;
1573
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
1574
+ const { line, errors } = buildReviewSkippedLine({
1575
+ diff_lines: diffLines,
1576
+ mutation_measure: mutationMeasure,
1577
+ ts,
1578
+ id: crypto.randomUUID()
1579
+ });
1580
+ if (errors.length || line === null) return;
1581
+ try {
1582
+ const dir = path5.join(workspaceRoot, "agents", "runtime", "state", "audit");
1583
+ fs5.mkdirSync(dir, { recursive: true });
1584
+ const file = path5.join(dir, `${ts.slice(0, 7)}.jsonl`);
1585
+ fs5.appendFileSync(file, `${JSON.stringify(line)}
1586
+ `, "utf8");
1587
+ } catch {
1588
+ }
1589
+ }
1590
+ function sessionStateDir(workspaceRoot) {
1591
+ return path5.join(workspaceRoot, "agents", "runtime", "state", "end-review-nudge");
1592
+ }
1593
+ function sessionStateFile(workspaceRoot, sessionKey) {
1594
+ return path5.join(sessionStateDir(workspaceRoot), `${sessionKey}.json`);
1595
+ }
1596
+ function deriveSessionKey(envelope, payload) {
1597
+ const raw = str(envelope["session_id"]) || str(payload["transcript_path"] ?? payload["transcriptPath"]);
1598
+ const material = raw || "unknown-session";
1599
+ return crypto.createHash("sha256").update(material).digest("hex");
1600
+ }
1601
+ function hasFiredThisSession(workspaceRoot, sessionKey) {
1602
+ try {
1603
+ return fs5.existsSync(sessionStateFile(workspaceRoot, sessionKey));
1604
+ } catch {
1605
+ return false;
1606
+ }
1607
+ }
1608
+ function markFiredThisSession(workspaceRoot, sessionKey, ts) {
1609
+ try {
1610
+ atomic_write_json(sessionStateFile(workspaceRoot, sessionKey), { fired_at: ts });
1611
+ } catch {
1612
+ }
1613
+ }
1614
+ function _read_stdin() {
1615
+ return readHookStdin();
1616
+ }
1617
+ function main5() {
1618
+ const [envelope, payload] = unwrap(_read_stdin(), "claude");
1619
+ const event = String(envelope["event"] ?? "");
1620
+ if (event !== "" && event !== "stop") {
1621
+ return 0;
1622
+ }
1623
+ const workspace_root = String(envelope["workspace_root"] ?? "").trim() || process.cwd();
1624
+ let diffLines;
1625
+ let mutationMeasure;
1626
+ try {
1627
+ const measured = totalNonDocMutatedLinesWithMeasure(workspace_root);
1628
+ diffLines = measured.lines;
1629
+ mutationMeasure = measured.measure;
1630
+ } catch {
1631
+ return 0;
1632
+ }
1633
+ if (diffLines <= MUTATION_LINE_THRESHOLD) {
1634
+ return 0;
1635
+ }
1636
+ const sessionKey = deriveSessionKey(envelope, payload);
1637
+ if (hasFiredThisSession(workspace_root, sessionKey)) {
1638
+ return 0;
1639
+ }
1640
+ const transcriptPath = str(
1641
+ payload["transcript_path"] ?? payload["transcriptPath"]
1642
+ ).trim();
1643
+ let reviewerRan = false;
1644
+ if (transcriptPath && isSafeTranscriptPath(transcriptPath)) {
1645
+ try {
1646
+ reviewerRan = scanTranscriptForReviewer(fs5.readFileSync(transcriptPath, "utf-8"));
1647
+ } catch {
1648
+ reviewerRan = false;
1649
+ }
1650
+ }
1651
+ if (reviewerRan) {
1652
+ return 0;
1653
+ }
1654
+ markFiredThisSession(workspace_root, sessionKey, (/* @__PURE__ */ new Date()).toISOString());
1655
+ try {
1656
+ appendReviewSkippedTelemetry(workspace_root, diffLines, mutationMeasure);
1657
+ } catch {
1658
+ }
1659
+ process.stdout.write(
1660
+ `${JSON.stringify({
1661
+ decision: "warn",
1662
+ reason: `end-review-nudge: session mutated ${diffLines} non-doc lines without a neutral review`,
1663
+ additional_context: buildAdvisoryLine(diffLines)
1664
+ })}
1665
+ `
1666
+ );
1667
+ return EXIT_WARN2;
1668
+ }
1669
+ function _isCliEntry5() {
1670
+ if (true) {
1671
+ return false;
1672
+ }
1673
+ if (process.argv[1] === void 0) {
1674
+ return false;
1675
+ }
1676
+ const argvUrl = pathToFileURL5(path5.resolve(process.argv[1])).href;
1677
+ if (import.meta.url === argvUrl) {
1678
+ return true;
1679
+ }
1680
+ try {
1681
+ const here = fs5.realpathSync(fileURLToPath5(import.meta.url));
1682
+ const argv = fs5.realpathSync(path5.resolve(process.argv[1]));
1683
+ return here === argv;
1684
+ } catch {
1685
+ return false;
1686
+ }
1687
+ }
1688
+ if (_isCliEntry5()) {
1689
+ process.exit(main5());
1690
+ }
1691
+
1692
+ // src/scripts/hooks/turn_end_gate_hook.ts
1311
1693
  var EXIT_BLOCK2 = 1;
1312
- var STATE_FILE4 = path5.join("agents", "state", "evidence-dispatch.json");
1313
- function _isObject5(v) {
1694
+ var EXIT_ALLOW4 = 0;
1695
+ var TRANSCRIPT_READ_MAX_BYTES = 8 * 1024 * 1024;
1696
+ function stripFencedBlocks(text) {
1697
+ const out = [];
1698
+ let open = null;
1699
+ for (const line of text.split("\n")) {
1700
+ const m = /^[ \t]{0,3}([`~]{3,})(.*)$/.exec(line);
1701
+ if (open === null) {
1702
+ if (m) {
1703
+ open = { ch: m[1][0], len: m[1].length };
1704
+ out.push(" ");
1705
+ continue;
1706
+ }
1707
+ out.push(line);
1708
+ continue;
1709
+ }
1710
+ if (m && m[1][0] === open.ch && m[1].length >= open.len && (m[2] ?? "").trim() === "") {
1711
+ open = null;
1712
+ }
1713
+ }
1714
+ return out.join("\n");
1715
+ }
1716
+ function visibleProse(reply) {
1717
+ let text = stripFencedBlocks(reply);
1718
+ text = text.replace(/`[^`\n]*`/g, " ");
1719
+ text = text.replace(/^[ \t]*>.*$/gm, " ");
1720
+ text = text.replace(/^[ \t]*\|.*\|[ \t]*$/gm, " ");
1721
+ text = text.replace(/\bhttps?:\/\/\S+/g, " ");
1722
+ text = text.replace(/\S*\/\S+/g, " ");
1723
+ text = text.replace(/\b[\w.-]+\.(ts|js|md|json|ya?ml|py|php|txt|sh)\b/g, " ");
1724
+ return text;
1725
+ }
1726
+ function finalParagraph(reply) {
1727
+ const prose = visibleProse(reply).trim();
1728
+ if (!prose) return "";
1729
+ const paragraphs = prose.split(/\n\s*\n/).map((p) => p.trim()).filter((p) => p.length > 0);
1730
+ return paragraphs.length === 0 ? "" : paragraphs[paragraphs.length - 1];
1731
+ }
1732
+ var PROMISSORY = [
1733
+ /\bich melde mich\b/i,
1734
+ /\bmelde ich (mich|dir|Dir)\b/i,
1735
+ /\bich melde\b/i,
1736
+ /\bich berichte\b/i,
1737
+ /\bich (sage|gebe) (dir |Dir )?Bescheid\b/i,
1738
+ /\bals n(ä|ae)chstes (werde|mache|baue|pr(ü|ue)fe) ich\b/i,
1739
+ // German puts the infinitive at the END of the clause ("ich werde jetzt
1740
+ // die Tests schreiben"), so the verb cannot be matched adjacent to
1741
+ // "werde" — it has to be looked for across the rest of the sentence.
1742
+ //
1743
+ // The lookahead excludes a stated REFUSAL to act: declining to do
1744
+ // something is not a promise to do it. R2 finding 4 reproduced four live
1745
+ // false refusals against the old `\bnicht\b`-only version — "Ich werde
1746
+ // nichts anfassen" (no word boundary after `nicht`), "keine Tests
1747
+ // schreiben", "niemals raten", and the passive non-promise "Ich werde
1748
+ // gefragt, ob …". Each was a false refusal on a BLOCKING guard, which is
1749
+ // the precision failure the council warning is about.
1750
+ // `\p{L}` with the `u` flag, NOT `\w`: R2 round 2, finding 15 verified that
1751
+ // `\b\w{3,}en\b` cannot reach an infinitive whose post-umlaut fragment is
1752
+ // short, because `ü`/`ö`/`ä` are not `\w` and create a word boundary — so
1753
+ // "prüfen" and "lösen" were silent while the comment above claimed the
1754
+ // verb-final construction was handled.
1755
+ //
1756
+ // Honest about what this proxy matches: any word of 3+ letters ending in
1757
+ // "en" within the clause, which is the infinitive in practice but also hits
1758
+ // a plural noun ("die Zeilen zählen" matches on "Zeilen"). That is
1759
+ // acceptable here — the clause already requires "ich werde" and excludes
1760
+ // negations and passives, so a forward commitment is what remains — but it
1761
+ // is a proxy for the infinitive, not a parse of one.
1762
+ new RegExp("\\bich werde\\b(?![^.!?\\n]*\\b(?:nicht|nichts|kein(?:e|en|em|er|es)?|niemals|nie)\\b)(?![^.!?\\n]*\\b(?:gefragt|gebeten|informiert|benachrichtigt)\\b)[^.!?\\n]*(?<!\\p{L})\\p{L}{3,}en(?!\\p{L})", "iu"),
1763
+ /\bI(?:'| wi)ll (report|let you know|update you|follow up)\b/i,
1764
+ /\bI(?:'| wi)ll (now |then )?\w+ (it|that|this|next)\b/i,
1765
+ /\bnext,? I(?:'| wi)ll\b/i,
1766
+ /\bI am going to\b/i
1767
+ ];
1768
+ var HANDBACK = [
1769
+ /\bdas entscheidest (du|Du)\b/i,
1770
+ /\bdeine Entscheidung\b/i,
1771
+ /\bich fasse .{0,40}nicht ungefragt an\b/i,
1772
+ /\bsag (Bescheid|ein Wort)\b/i,
1773
+ /\bwarte auf (deine|Deine|dein|Dein)\b/i,
1774
+ /\byour call\b/i,
1775
+ /\byou decide\b/i,
1776
+ /\blet me know (which|if|whether)\b/i,
1777
+ /\bwaiting for your\b/i
1778
+ ];
1779
+ function detectPromissory(reply) {
1780
+ const tail = finalParagraph(reply);
1781
+ if (!tail) return null;
1782
+ if (/\?["'’)\]]*\s*$/.test(tail)) return null;
1783
+ if (HANDBACK.some((re) => re.test(tail))) return null;
1784
+ for (const re of PROMISSORY) {
1785
+ const m = tail.match(re);
1786
+ if (m) {
1787
+ return {
1788
+ detector: "promissory",
1789
+ evidence: m[0],
1790
+ reason: "the closing paragraph promises work that has not been performed, and the turn asks the user nothing \u2014 so nothing ends this turn but the promise itself"
1791
+ };
1792
+ }
1793
+ }
1794
+ return null;
1795
+ }
1796
+ function readCiSettled(workspaceRoot) {
1797
+ try {
1798
+ const raw = fs6.readFileSync(path6.join(workspaceRoot, STATE_FILE3), "utf-8");
1799
+ const decoded = JSON.parse(raw);
1800
+ if (typeof decoded === "object" && decoded !== null && !Array.isArray(decoded)) {
1801
+ const ci = decoded["ci_last"];
1802
+ if (typeof ci === "object" && ci !== null && !Array.isArray(ci)) {
1803
+ return { seen: true, settled: ci["settled"] === true };
1804
+ }
1805
+ }
1806
+ } catch {
1807
+ }
1808
+ return { seen: false, settled: false };
1809
+ }
1810
+ var _COMPLETION_RE = /(^|\n)\s*(?:\*\*)?(?:fertig\b|damit ist alles erledigt|aufgabe erledigt|alles erledigt\b|komplett(?:er)? (?:auftrag|abgearbeitet)|der komplette auftrag ist durch|done[.!]|all done\b|task complete)/i;
1811
+ function detectCompletionClaim(reply, ci) {
1812
+ if (!ci.seen || ci.settled) return null;
1813
+ const prose = visibleProse(reply);
1814
+ const m = _COMPLETION_RE.exec(prose);
1815
+ if (!m) return null;
1816
+ const at = m.index;
1817
+ return {
1818
+ detector: "completion",
1819
+ evidence: prose.slice(at, at + 120).trim(),
1820
+ reason: "a completion claim while the last CI read was not settled \u2014 read the verdict, then claim it (verify-before-complete)"
1821
+ };
1822
+ }
1823
+ function readLanguagePin(workspaceRoot) {
1824
+ try {
1825
+ const raw = fs6.readFileSync(path6.join(workspaceRoot, STATE_FILE), "utf-8");
1826
+ const decoded = JSON.parse(raw);
1827
+ if (typeof decoded === "object" && decoded !== null && !Array.isArray(decoded)) {
1828
+ const lang = decoded["language"];
1829
+ if (lang === "de" || lang === "en") return lang;
1830
+ }
1831
+ } catch {
1832
+ }
1833
+ return "und";
1834
+ }
1835
+ function detectLanguage(reply, pinned) {
1836
+ if (pinned !== "de" && pinned !== "en") return null;
1837
+ const prose = visibleProse(reply);
1838
+ const verdict = classify(prose);
1839
+ if (verdict.language === "und") return null;
1840
+ if (verdict.language === pinned) return null;
1841
+ return {
1842
+ detector: "language",
1843
+ evidence: prose.trim().slice(0, 120),
1844
+ reason: `the pinned reply language is "${pinned}" but the reply classifies as "${verdict.language}" (${verdict.de_markers} de / ${verdict.en_markers} en markers; code, quotes, tables, URLs and paths excluded, then scored lead-first by the same classifier that sets the pin)`
1845
+ };
1846
+ }
1847
+ var _EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
1848
+ var _VERIFY_RE = /\b(test|tests|vitest|jest|pytest|phpunit|pest|tsc|eslint|ruff|mypy|pyright|clippy|typecheck|lint|build|ci|preflight|smoke[-_:a-z]*|check[-_:a-z]*|validate[-_:a-z]*)\b|\b(task|npm|pnpm|yarn|composer|cargo|go|make|php artisan|bun)\s+(run\s+)?\S*(test|check|lint|build|ci|typecheck)/i;
1849
+ function isVerificationCommand(command) {
1850
+ return _VERIFY_RE.test(command);
1851
+ }
1852
+ function detectUnverifiedEdit(toolCalls) {
1853
+ let lastEdit = -1;
1854
+ for (let i = 0; i < toolCalls.length; i += 1) {
1855
+ if (_EDIT_TOOLS.has(toolCalls[i].name)) lastEdit = i;
1856
+ }
1857
+ if (lastEdit === -1) return null;
1858
+ for (let i = lastEdit + 1; i < toolCalls.length; i += 1) {
1859
+ const c = toolCalls[i];
1860
+ if (c.name === "Bash" && c.command !== void 0 && isVerificationCommand(c.command)) {
1861
+ return null;
1862
+ }
1863
+ }
1864
+ const edited = toolCalls[lastEdit].path;
1865
+ return {
1866
+ detector: "verification",
1867
+ // The path, never the diff: the evidence span is quoted into a refusal,
1868
+ // and `ToolCall` is shaped so a file body cannot reach it.
1869
+ evidence: edited ?? toolCalls[lastEdit].name,
1870
+ reason: "this turn changed a file and then ran no verification command \u2014 no test, type-check, lint or build call follows the last edit (verify-before-complete: a claim without a fresh run is unverified)"
1871
+ };
1872
+ }
1873
+ function stateDir(workspaceRoot) {
1874
+ return path6.join(workspaceRoot, "agents", "runtime", "state", "turn-end-gate");
1875
+ }
1876
+ function sessionStateFile2(workspaceRoot, sessionKey) {
1877
+ return path6.join(stateDir(workspaceRoot), `${sessionKey}.json`);
1878
+ }
1879
+ function deriveSessionKey2(sessionId) {
1880
+ return crypto2.createHash("sha256").update(sessionId).digest("hex").slice(0, 32);
1881
+ }
1882
+ function alreadyRefusedTurn(workspaceRoot, sessionKey, turnOrdinal) {
1883
+ try {
1884
+ const raw = fs6.readFileSync(sessionStateFile2(workspaceRoot, sessionKey), "utf-8");
1885
+ const decoded = JSON.parse(raw);
1886
+ if (typeof decoded === "object" && decoded !== null && !Array.isArray(decoded)) {
1887
+ return decoded["refused_turn"] === turnOrdinal;
1888
+ }
1889
+ } catch {
1890
+ }
1891
+ return false;
1892
+ }
1893
+ function markRefusedTurn(workspaceRoot, sessionKey, turnOrdinal, detector) {
1894
+ if (is_replay_mode()) return;
1895
+ try {
1896
+ atomic_write_json(sessionStateFile2(workspaceRoot, sessionKey), {
1897
+ refused_at: (/* @__PURE__ */ new Date()).toISOString(),
1898
+ refused_turn: turnOrdinal,
1899
+ detector
1900
+ });
1901
+ } catch {
1902
+ }
1903
+ }
1904
+ function readTranscriptTail(transcriptPath, opts = {}) {
1905
+ const empty = { lastAssistant: "", turnOrdinal: 0, toolCalls: [] };
1906
+ if (!transcriptPath || !isSafeTranscriptPath(transcriptPath, opts)) return empty;
1907
+ let lines;
1908
+ try {
1909
+ if (opts.maxBytes !== void 0 && fs6.statSync(transcriptPath).size > opts.maxBytes) {
1910
+ return empty;
1911
+ }
1912
+ lines = fs6.readFileSync(transcriptPath, "utf-8").split("\n");
1913
+ } catch {
1914
+ return empty;
1915
+ }
1916
+ let lastAssistant = "";
1917
+ let turnOrdinal = 0;
1918
+ let toolCalls = [];
1919
+ for (const rawLine of lines) {
1920
+ const line = rawLine.trim();
1921
+ if (!line) continue;
1922
+ let obj;
1923
+ try {
1924
+ obj = JSON.parse(line);
1925
+ } catch {
1926
+ continue;
1927
+ }
1928
+ if (typeof obj !== "object" || obj === null || Array.isArray(obj)) continue;
1929
+ const entry = obj;
1930
+ if (entry["isSidechain"] === true) continue;
1931
+ const role = entry["type"];
1932
+ if (role !== "assistant" && role !== "user") continue;
1933
+ const msg = entry["message"];
1934
+ if (typeof msg !== "object" || msg === null || Array.isArray(msg)) continue;
1935
+ const content = msg["content"];
1936
+ if (role === "assistant") {
1937
+ toolCalls.push(..._toolCalls(content));
1938
+ }
1939
+ const text = _messageText(content);
1940
+ if (text === null) continue;
1941
+ if (role === "assistant") {
1942
+ lastAssistant = text;
1943
+ } else if (!isSyntheticPrompt(text)) {
1944
+ turnOrdinal += 1;
1945
+ toolCalls = [];
1946
+ }
1947
+ }
1948
+ return { lastAssistant: lastAssistant.trim(), turnOrdinal, toolCalls };
1949
+ }
1950
+ function _toolCalls(content) {
1951
+ if (!Array.isArray(content)) return [];
1952
+ const out = [];
1953
+ for (const blk of content) {
1954
+ if (typeof blk !== "object" || blk === null || Array.isArray(blk)) continue;
1955
+ const b = blk;
1956
+ if (b["type"] !== "tool_use") continue;
1957
+ const name = b["name"];
1958
+ if (typeof name !== "string") continue;
1959
+ const input = b["input"];
1960
+ const call = { name };
1961
+ if (typeof input === "object" && input !== null && !Array.isArray(input)) {
1962
+ const inp = input;
1963
+ const cmd = inp["command"];
1964
+ if (typeof cmd === "string") call.command = cmd;
1965
+ const p = inp["file_path"] ?? inp["path"] ?? inp["notebook_path"];
1966
+ if (typeof p === "string") call.path = p;
1967
+ }
1968
+ out.push(call);
1969
+ }
1970
+ return out;
1971
+ }
1972
+ function _messageText(content) {
1973
+ if (typeof content === "string") return content;
1974
+ if (!Array.isArray(content)) return null;
1975
+ const parts = [];
1976
+ for (const blk of content) {
1977
+ if (typeof blk !== "object" || blk === null || Array.isArray(blk)) continue;
1978
+ const b = blk;
1979
+ if (b["type"] !== "text") continue;
1980
+ const t = b["text"];
1981
+ if (typeof t === "string") parts.push(t);
1982
+ }
1983
+ return parts.length > 0 ? parts.join("\n") : null;
1984
+ }
1985
+ function str2(v) {
1986
+ return typeof v === "string" ? v : "";
1987
+ }
1988
+ function main6() {
1989
+ let envelope;
1990
+ let payload;
1991
+ try {
1992
+ [envelope, payload] = unwrap(readHookStdin(), "claude");
1993
+ } catch {
1994
+ return EXIT_ALLOW4;
1995
+ }
1996
+ if (payload["stop_hook_active"] === true) return EXIT_ALLOW4;
1997
+ const workspaceRoot = str2(envelope["workspace_root"]) || process.cwd();
1998
+ const transcriptPath = str2(
1999
+ payload["transcript_path"] ?? payload["transcriptPath"]
2000
+ );
2001
+ const { lastAssistant, turnOrdinal, toolCalls } = readTranscriptTail(transcriptPath, {
2002
+ maxBytes: TRANSCRIPT_READ_MAX_BYTES
2003
+ });
2004
+ if (!lastAssistant) return EXIT_ALLOW4;
2005
+ const sessionKey = deriveSessionKey2(
2006
+ str2(envelope["session_id"]) || "unknown-session"
2007
+ );
2008
+ if (alreadyRefusedTurn(workspaceRoot, sessionKey, turnOrdinal)) return EXIT_ALLOW4;
2009
+ const findings = [];
2010
+ for (const f of [
2011
+ detectPromissory(lastAssistant),
2012
+ detectLanguage(lastAssistant, readLanguagePin(workspaceRoot)),
2013
+ detectUnverifiedEdit(toolCalls),
2014
+ // Round 7 § Phase 1 — detector D, in the same unconditional list as the
2015
+ // other three. It shipped with its own settings flag one commit earlier
2016
+ // and that flag is gone: `hooks.turn_end_gate.*` was deleted on
2017
+ // 2026-08-12 because a default-off safety gate is an absent one. Its
2018
+ // gating is where the comment above says gating belongs — inside the
2019
+ // detector: no CI observed, or a settled read, or no completion claim
2020
+ // ⇒ no finding.
2021
+ detectCompletionClaim(lastAssistant, readCiSettled(workspaceRoot))
2022
+ ]) {
2023
+ if (f) findings.push(f);
2024
+ }
2025
+ if (findings.length === 0) return EXIT_ALLOW4;
2026
+ markRefusedTurn(workspaceRoot, sessionKey, turnOrdinal, findings[0].detector);
2027
+ const lines = findings.map(
2028
+ (f) => ` \xB7 ${f.detector}: ${f.reason}
2029
+ evidence: ${JSON.stringify(f.evidence)}`
2030
+ );
2031
+ process.stderr.write(
2032
+ `turn-end-gate: REFUSED \u2014 this turn is not finished.
2033
+ ${lines.join("\n")}
2034
+ Do the promised work now, correct the language, run the verification the edit needs, or read the CI verdict before claiming it \u2014 then end the turn.
2035
+ This turn will not be refused a second time.
2036
+ `
2037
+ );
2038
+ return EXIT_BLOCK2;
2039
+ }
2040
+ function _isCliEntry6() {
2041
+ if (true) {
2042
+ return false;
2043
+ }
2044
+ if (process.argv[1] === void 0) {
2045
+ return false;
2046
+ }
2047
+ const argvUrl = pathToFileURL6(path6.resolve(process.argv[1])).href;
2048
+ if (import.meta.url === argvUrl) {
2049
+ return true;
2050
+ }
2051
+ try {
2052
+ const here = fs6.realpathSync(fileURLToPath6(import.meta.url));
2053
+ const argv = fs6.realpathSync(path6.resolve(process.argv[1]));
2054
+ return here === argv;
2055
+ } catch {
2056
+ return false;
2057
+ }
2058
+ }
2059
+ if (_isCliEntry6()) {
2060
+ process.exit(main6());
2061
+ }
2062
+
2063
+ // src/scripts/hooks/evidence_independence.ts
2064
+ import crypto3 from "node:crypto";
2065
+ import fs7 from "node:fs";
2066
+ import path7 from "node:path";
2067
+ import { fileURLToPath as fileURLToPath7, pathToFileURL as pathToFileURL7 } from "node:url";
2068
+ var EXIT_ALLOW5 = 0;
2069
+ var EXIT_BLOCK3 = 1;
2070
+ var STATE_FILE4 = path7.join("agents", "state", "evidence-dispatch.json");
2071
+ function _isObject6(v) {
1314
2072
  return typeof v === "object" && v !== null && !Array.isArray(v);
1315
2073
  }
1316
2074
  var DISPATCH_TOOLS = /* @__PURE__ */ new Set([
@@ -1363,10 +2121,10 @@ function preloadedVerdict(prompt) {
1363
2121
  return null;
1364
2122
  }
1365
2123
  function extractDispatch(envelope) {
1366
- const payload = _isObject5(envelope["payload"]) ? envelope["payload"] : envelope;
2124
+ const payload = _isObject6(envelope["payload"]) ? envelope["payload"] : envelope;
1367
2125
  const toolRaw = payload["tool_name"] ?? payload["toolName"] ?? payload["tool"];
1368
2126
  const tool = typeof toolRaw === "string" ? toolRaw : null;
1369
- const input = _isObject5(payload["tool_input"]) ? payload["tool_input"] : _isObject5(payload["toolInput"]) ? payload["toolInput"] : payload;
2127
+ const input = _isObject6(payload["tool_input"]) ? payload["tool_input"] : _isObject6(payload["toolInput"]) ? payload["toolInput"] : payload;
1370
2128
  for (const key of ["prompt", "instructions", "task", "description"]) {
1371
2129
  const v = input[key];
1372
2130
  if (typeof v === "string" && v.trim()) {
@@ -1377,8 +2135,8 @@ function extractDispatch(envelope) {
1377
2135
  }
1378
2136
  function _ledgerStamp(consumer_root) {
1379
2137
  try {
1380
- const raw = fs5.readFileSync(
1381
- path5.join(consumer_root, "agents", "state", "git-authorization.json"),
2138
+ const raw = fs7.readFileSync(
2139
+ path7.join(consumer_root, "agents", "state", "git-authorization.json"),
1382
2140
  "utf8"
1383
2141
  );
1384
2142
  const d = JSON.parse(raw);
@@ -1389,8 +2147,8 @@ function _ledgerStamp(consumer_root) {
1389
2147
  }
1390
2148
  function _load(target) {
1391
2149
  try {
1392
- const decoded = JSON.parse(fs5.readFileSync(target, "utf8"));
1393
- if (_isObject5(decoded) && Array.isArray(decoded["evaluations"])) {
2150
+ const decoded = JSON.parse(fs7.readFileSync(target, "utf8"));
2151
+ if (_isObject6(decoded) && Array.isArray(decoded["evaluations"])) {
1394
2152
  return decoded;
1395
2153
  }
1396
2154
  } catch {
@@ -1400,12 +2158,12 @@ function _load(target) {
1400
2158
  var WARN_TEXT = "First evaluation dispatch this turn. Two shapes fabricated a binding honest-null in the audited sessions \u2014 check your prompt for both before it goes out: (1) a verdict pre-loaded into the prompt, and (2) a scope narrowed to files you chose. A review you commissioned on your own work is admissible as gate evidence only when the prompt is recorded alongside the verdict.";
1401
2159
  function decide2(tool, prompt, priorEvaluations) {
1402
2160
  if (!isDispatchTool(tool) || !isEvaluationPrompt(prompt)) {
1403
- return { exit: EXIT_ALLOW4, stdout: "", stderr: "", evaluations: priorEvaluations };
2161
+ return { exit: EXIT_ALLOW5, stdout: "", stderr: "", evaluations: priorEvaluations };
1404
2162
  }
1405
2163
  const preloaded = preloadedVerdict(prompt);
1406
2164
  if (preloaded !== null) {
1407
2165
  return {
1408
- exit: EXIT_BLOCK2,
2166
+ exit: EXIT_BLOCK3,
1409
2167
  stdout: "",
1410
2168
  stderr: `Blocked: this evaluation prompt pre-loads its verdict ("${preloaded}"). That is the construct that produced a fabricated NO-FINDINGS committed as binding gate evidence, over a delta an unsteered pass then found a live critical in. Remove the expectation from the prompt and let the evaluator reach its own verdict.
1411
2169
  `,
@@ -1413,11 +2171,11 @@ function decide2(tool, prompt, priorEvaluations) {
1413
2171
  };
1414
2172
  }
1415
2173
  if (!isSelfScoped(prompt)) {
1416
- return { exit: EXIT_ALLOW4, stdout: "", stderr: "", evaluations: priorEvaluations };
2174
+ return { exit: EXIT_ALLOW5, stdout: "", stderr: "", evaluations: priorEvaluations };
1417
2175
  }
1418
2176
  if (priorEvaluations >= 1) {
1419
2177
  return {
1420
- exit: EXIT_BLOCK2,
2178
+ exit: EXIT_BLOCK3,
1421
2179
  stdout: "",
1422
2180
  stderr: `Blocked: second evaluation dispatch of your own work in this turn (verdict shopping). One evaluation has already run; commissioning another with a different prompt or scope selects the answer instead of measuring it. Report what the first pass returned, then re-plan.
1423
2181
  `,
@@ -1425,7 +2183,7 @@ function decide2(tool, prompt, priorEvaluations) {
1425
2183
  };
1426
2184
  }
1427
2185
  return {
1428
- exit: EXIT_ALLOW4,
2186
+ exit: EXIT_ALLOW5,
1429
2187
  stdout: `${JSON.stringify({ decision: "warn", reason: WARN_TEXT })}
1430
2188
  `,
1431
2189
  stderr: "",
@@ -1437,14 +2195,14 @@ function run5(stdin_text, options) {
1437
2195
  if (stdin_text.trim()) {
1438
2196
  try {
1439
2197
  const decoded = JSON.parse(stdin_text);
1440
- if (_isObject5(decoded)) {
2198
+ if (_isObject6(decoded)) {
1441
2199
  envelope = decoded;
1442
2200
  }
1443
2201
  } catch {
1444
- return EXIT_ALLOW4;
2202
+ return EXIT_ALLOW5;
1445
2203
  }
1446
2204
  }
1447
- const target = path5.join(options.consumer_root, STATE_FILE4);
2205
+ const target = path7.join(options.consumer_root, STATE_FILE4);
1448
2206
  const state = _load(target);
1449
2207
  const session_id = typeof envelope["session_id"] === "string" ? envelope["session_id"] : "";
1450
2208
  const turnMarker = `${session_id}:${_ledgerStamp(options.consumer_root)}`;
@@ -1458,7 +2216,7 @@ function run5(stdin_text, options) {
1458
2216
  if (decision.evaluations > state.evaluations.length) {
1459
2217
  state.evaluations.push({
1460
2218
  at: (/* @__PURE__ */ new Date()).toISOString(),
1461
- digest: crypto.createHash("sha256").update(prompt).digest("hex").slice(0, 16),
2219
+ digest: crypto3.createHash("sha256").update(prompt).digest("hex").slice(0, 16),
1462
2220
  prompt_chars: prompt.length
1463
2221
  });
1464
2222
  try {
@@ -1474,7 +2232,7 @@ function run5(stdin_text, options) {
1474
2232
  }
1475
2233
  return decision.exit;
1476
2234
  }
1477
- function main5(argv) {
2235
+ function main7(argv) {
1478
2236
  const args = argv ?? process.argv.slice(2);
1479
2237
  let consumer_root = process.cwd();
1480
2238
  for (let i = 0; i < args.length; i += 1) {
@@ -1488,34 +2246,54 @@ function main5(argv) {
1488
2246
  }
1489
2247
  return run5(readHookStdin(), { consumer_root });
1490
2248
  }
1491
- function _isCliEntry5() {
2249
+ function _isCliEntry7() {
1492
2250
  if (true) {
1493
2251
  return false;
1494
2252
  }
1495
2253
  if (process.argv[1] === void 0) {
1496
2254
  return false;
1497
2255
  }
1498
- const argvUrl = pathToFileURL5(path5.resolve(process.argv[1])).href;
2256
+ const argvUrl = pathToFileURL7(path7.resolve(process.argv[1])).href;
1499
2257
  if (import.meta.url === argvUrl) {
1500
2258
  return true;
1501
2259
  }
1502
2260
  try {
1503
- const here = fs5.realpathSync(fileURLToPath5(import.meta.url));
1504
- const argv = fs5.realpathSync(path5.resolve(process.argv[1]));
2261
+ const here = fs7.realpathSync(fileURLToPath7(import.meta.url));
2262
+ const argv = fs7.realpathSync(path7.resolve(process.argv[1]));
1505
2263
  return here === argv;
1506
2264
  } catch {
1507
2265
  return false;
1508
2266
  }
1509
2267
  }
1510
- if (_isCliEntry5()) {
1511
- process.exit(main5());
2268
+ if (_isCliEntry7()) {
2269
+ process.exit(main7());
1512
2270
  }
1513
2271
 
1514
2272
  // src/scripts/conformance_scan.ts
1515
- var REPO_ROOT = path6.resolve(path6.dirname(fileURLToPath6(import.meta.url)), "..", "..");
2273
+ var REPO_ROOT = path8.resolve(path8.dirname(fileURLToPath8(import.meta.url)), "..", "..");
1516
2274
  var OBSERVED_BAND = { low: 9.1, high: 39.2 };
1517
2275
  var BAND_MIN_TURNS = 1978;
1518
- function bandVerdict(rate_pct, assistant_turns) {
2276
+ var CARRIER_CHANGES = [
2277
+ { at: "2026-08-07T00:00:00Z", what: "language-pin bound on user_prompt_submit (round 5)" }
2278
+ ];
2279
+ function spansCarrierChange(firstAts) {
2280
+ const stamps = firstAts.filter((s) => s !== "").sort();
2281
+ if (stamps.length === 0) {
2282
+ return { spans: false, what: "" };
2283
+ }
2284
+ const lo = stamps[0];
2285
+ const hi = stamps[stamps.length - 1];
2286
+ for (const c of CARRIER_CHANGES) {
2287
+ if (lo < c.at && hi >= c.at) {
2288
+ return { spans: true, what: c.what };
2289
+ }
2290
+ }
2291
+ return { spans: false, what: "" };
2292
+ }
2293
+ function bandVerdict(rate_pct, assistant_turns, opts = {}) {
2294
+ if (opts.spansCarrier === true) {
2295
+ return "era-spanning";
2296
+ }
1519
2297
  if (assistant_turns < BAND_MIN_TURNS) {
1520
2298
  return "corpus-too-small";
1521
2299
  }
@@ -1525,13 +2303,15 @@ var CHECK_IDS = [
1525
2303
  "language-pin",
1526
2304
  "git-authorization",
1527
2305
  "vacuous-evidence",
1528
- "evidence-steering"
2306
+ "evidence-steering",
2307
+ "completion-claim"
1529
2308
  ];
1530
2309
  var CHECK_MEANINGS = {
1531
2310
  "language-pin": "An assistant turn whose user-visible prose is not in the language that turn's own pin named. Carries provenance (turns_since_prompt, compaction_since_prompt) so a pin that was ABSENT because compaction removed it stays distinguishable from one that was present and ignored.",
1532
2311
  "git-authorization": "A gated git operation performed in a session where no authorization for that operation was observed. Authorization is per-operation and never carries from one to the next.",
1533
2312
  "vacuous-evidence": "A CI poll read as settled without ever observing an in-flight run \u2014 the poll gap that reports a green settle over a window in which nothing was pending yet.",
1534
- "evidence-steering": "A self-commissioned evaluation whose prompt pre-loaded its own verdict, or a second self-scoped evaluation of the same subject within one turn."
2313
+ "evidence-steering": "A self-commissioned evaluation whose prompt pre-loaded its own verdict, or a second self-scoped evaluation of the same subject within one turn.",
2314
+ "completion-claim": "An assistant turn claiming the work is complete while the last CI read in that session was NOT settled. Round 7 measured 14 instances by reading, each followed by the user handing the work back; this is the same predicate `turn-end-gate`'s completion detector refuses on, so the rate and the gate cannot disagree. A session that never polled CI can never hit."
1535
2315
  };
1536
2316
  function renderWhy(report, id) {
1537
2317
  const hits = report.per_session.flatMap((s) => s.violations.filter((v) => v.check === id));
@@ -1561,7 +2341,7 @@ function renderWhy(report, id) {
1561
2341
  }
1562
2342
  return lines.join("\n");
1563
2343
  }
1564
- function _isObject6(v) {
2344
+ function _isObject7(v) {
1565
2345
  return typeof v === "object" && v !== null && !Array.isArray(v);
1566
2346
  }
1567
2347
  function userText(entry) {
@@ -1585,15 +2365,15 @@ function assistantText(entry) {
1585
2365
  }
1586
2366
  function toolUses2(entry) {
1587
2367
  const msg = entry["message"];
1588
- const content = _isObject6(msg) ? msg["content"] : void 0;
2368
+ const content = _isObject7(msg) ? msg["content"] : void 0;
1589
2369
  if (!Array.isArray(content)) {
1590
2370
  return [];
1591
2371
  }
1592
- return content.filter((b) => _isObject6(b) && b["type"] === "tool_use").map((b) => {
2372
+ return content.filter((b) => _isObject7(b) && b["type"] === "tool_use").map((b) => {
1593
2373
  const o = b;
1594
2374
  return {
1595
2375
  name: String(o["name"] ?? ""),
1596
- input: _isObject6(o["input"]) ? o["input"] : {}
2376
+ input: _isObject7(o["input"]) ? o["input"] : {}
1597
2377
  };
1598
2378
  });
1599
2379
  }
@@ -1610,7 +2390,9 @@ function scanSession(sessionId, lines) {
1610
2390
  session: sessionId,
1611
2391
  user_turns: 0,
1612
2392
  assistant_turns: 0,
1613
- violations: []
2393
+ violations: [],
2394
+ de_pin_turns: 0,
2395
+ first_at: ""
1614
2396
  };
1615
2397
  let pinned = null;
1616
2398
  let pendingPoll = null;
@@ -1619,6 +2401,8 @@ function scanSession(sessionId, lines) {
1619
2401
  let evaluationsThisTurn = 0;
1620
2402
  let turnsSincePrompt = 0;
1621
2403
  let compactionSincePrompt = false;
2404
+ let ciSeen = false;
2405
+ let ciSettled = false;
1622
2406
  for (const line of lines) {
1623
2407
  if (!line.trim()) {
1624
2408
  continue;
@@ -1629,6 +2413,9 @@ function scanSession(sessionId, lines) {
1629
2413
  } catch {
1630
2414
  continue;
1631
2415
  }
2416
+ if (report.first_at === "" && typeof entry["timestamp"] === "string") {
2417
+ report.first_at = entry["timestamp"];
2418
+ }
1632
2419
  if (entry["type"] === "user" && entry["toolUseResult"] !== void 0 && pendingPoll !== null) {
1633
2420
  const raw = entry["toolUseResult"];
1634
2421
  const out = typeof raw === "string" ? raw : JSON.stringify(raw);
@@ -1643,6 +2430,8 @@ function scanSession(sessionId, lines) {
1643
2430
  detail: `CI poll read as settled without an in-flight observation: ${out.slice(0, 90)}`
1644
2431
  });
1645
2432
  }
2433
+ ciSeen = true;
2434
+ ciSettled = pending === 0 && sawPending && !isVacuousOutput(out);
1646
2435
  pendingPoll = null;
1647
2436
  continue;
1648
2437
  }
@@ -1682,6 +2471,20 @@ function scanSession(sessionId, lines) {
1682
2471
  if (prose !== null) {
1683
2472
  report.assistant_turns += 1;
1684
2473
  turnsSincePrompt += 1;
2474
+ if (pinned === "de") {
2475
+ report.de_pin_turns += 1;
2476
+ }
2477
+ if (!HARNESS_TEXT.test((prose.split("\n").find((l) => l.trim()) ?? "").trim())) {
2478
+ const cc = detectCompletionClaim(prose, { seen: ciSeen, settled: ciSettled });
2479
+ if (cc !== null) {
2480
+ report.violations.push({
2481
+ check: "completion-claim",
2482
+ session: sessionId,
2483
+ at,
2484
+ detail: `completion claimed over an unsettled CI read: "${cc.evidence.slice(0, 110)}"`
2485
+ });
2486
+ }
2487
+ }
1685
2488
  if (pinned === "de") {
1686
2489
  const first = prose.split("\n").find((l) => l.trim()) ?? "";
1687
2490
  if (HARNESS_TEXT.test(first.trim())) {
@@ -1742,7 +2545,7 @@ function scanSession(sessionId, lines) {
1742
2545
  }
1743
2546
  return report;
1744
2547
  }
1745
- var DEFAULT_RATE_SERIES = path6.join(
2548
+ var DEFAULT_RATE_SERIES = path8.join(
1746
2549
  REPO_ROOT,
1747
2550
  "agents",
1748
2551
  "runtime",
@@ -1751,7 +2554,7 @@ var DEFAULT_RATE_SERIES = path6.join(
1751
2554
  );
1752
2555
  function defaultStore(projectDir) {
1753
2556
  const slug = projectDir.replace(/[/.]/g, "-");
1754
- return path6.join(process.env["HOME"] ?? "", ".claude", "projects", slug);
2557
+ return path8.join(process.env["HOME"] ?? "", ".claude", "projects", slug);
1755
2558
  }
1756
2559
  function _tokens(chars) {
1757
2560
  return Math.round(chars / 4);
@@ -1762,13 +2565,13 @@ function measureDelivered(projectRulesDir, globalRulesDir) {
1762
2565
  return {
1763
2566
  project: {
1764
2567
  dir: projectRulesDir,
1765
- present: fs6.existsSync(projectRulesDir),
2568
+ present: fs8.existsSync(projectRulesDir),
1766
2569
  files: p.files,
1767
2570
  tokens: _tokens(p.chars)
1768
2571
  },
1769
2572
  global: {
1770
2573
  dir: globalRulesDir,
1771
- present: fs6.existsSync(globalRulesDir),
2574
+ present: fs8.existsSync(globalRulesDir),
1772
2575
  files: g.files,
1773
2576
  tokens: _tokens(g.chars)
1774
2577
  },
@@ -1776,13 +2579,13 @@ function measureDelivered(projectRulesDir, globalRulesDir) {
1776
2579
  };
1777
2580
  }
1778
2581
  function storeKey(store) {
1779
- return createHash("sha256").update(path6.resolve(store)).digest("hex").slice(0, 12);
2582
+ return createHash3("sha256").update(path8.resolve(store)).digest("hex").slice(0, 12);
1780
2583
  }
1781
2584
  function scanStore(store, limit) {
1782
- const files = fs6.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => ({ f, m: fs6.statSync(path6.join(store, f)).mtimeMs })).sort((a, b) => b.m - a.m).slice(0, limit).map((x) => x.f);
2585
+ const files = fs8.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => ({ f, m: fs8.statSync(path8.join(store, f)).mtimeMs })).sort((a, b) => b.m - a.m).slice(0, limit).map((x) => x.f);
1783
2586
  const per_session = [];
1784
2587
  for (const f of files) {
1785
- const lines = fs6.readFileSync(path6.join(store, f), "utf8").split("\n");
2588
+ const lines = fs8.readFileSync(path8.join(store, f), "utf8").split("\n");
1786
2589
  const r = scanSession(f.replace(/\.jsonl$/, "").slice(0, 8), lines);
1787
2590
  if (r.assistant_turns > 0) {
1788
2591
  per_session.push(r);
@@ -1792,7 +2595,8 @@ function scanStore(store, limit) {
1792
2595
  "language-pin": 0,
1793
2596
  "git-authorization": 0,
1794
2597
  "vacuous-evidence": 0,
1795
- "evidence-steering": 0
2598
+ "evidence-steering": 0,
2599
+ "completion-claim": 0
1796
2600
  };
1797
2601
  for (const s of per_session) {
1798
2602
  for (const v of s.violations) {
@@ -1802,11 +2606,14 @@ function scanStore(store, limit) {
1802
2606
  const assistant_turns = per_session.reduce((n, s) => n + s.assistant_turns, 0);
1803
2607
  const language_pin = totals["language-pin"] ?? 0;
1804
2608
  const rate_pct = assistant_turns === 0 ? 0 : Number((100 * language_pin / assistant_turns).toFixed(1));
2609
+ const de_pin_turns = per_session.reduce((n, s) => n + s.de_pin_turns, 0);
2610
+ const rate_pct_de_pin = de_pin_turns === 0 ? 0 : Number((100 * language_pin / de_pin_turns).toFixed(1));
2611
+ const era = spansCarrierChange(per_session.map((s) => s.first_at));
1805
2612
  const delivered = measureDelivered(
1806
- path6.join(REPO_ROOT, ".claude", "rules"),
1807
- path6.join(process.env["HOME"] ?? os.homedir(), ".claude", "rules")
2613
+ path8.join(REPO_ROOT, ".claude", "rules"),
2614
+ path8.join(process.env["HOME"] ?? os2.homedir(), ".claude", "rules")
1808
2615
  );
1809
- const ownStore = path6.resolve(store) === path6.resolve(defaultStore(REPO_ROOT));
2616
+ const ownStore = path8.resolve(store) === path8.resolve(defaultStore(REPO_ROOT));
1810
2617
  return {
1811
2618
  scanned_at: (/* @__PURE__ */ new Date()).toISOString(),
1812
2619
  store,
@@ -1820,7 +2627,11 @@ function scanStore(store, limit) {
1820
2627
  assistant_turns,
1821
2628
  language_pin,
1822
2629
  rate_pct,
1823
- band: bandVerdict(rate_pct, assistant_turns),
2630
+ de_pin_turns,
2631
+ rate_pct_de_pin,
2632
+ era_spanning: era.spans,
2633
+ era_reason: era.what,
2634
+ band: bandVerdict(rate_pct, assistant_turns, { spansCarrier: era.spans }),
1824
2635
  // Only attach the carrier figures when the rate and the carriers describe
1825
2636
  // the same project.
1826
2637
  delivered_project_tokens: ownStore ? delivered.project.tokens : null,
@@ -1829,8 +2640,8 @@ function scanStore(store, limit) {
1829
2640
  };
1830
2641
  }
1831
2642
  function recordRate(file, scanned_at, rate) {
1832
- fs6.mkdirSync(path6.dirname(file), { recursive: true });
1833
- fs6.appendFileSync(file, `${JSON.stringify({ measured_at: scanned_at, ...rate })}
2643
+ fs8.mkdirSync(path8.dirname(file), { recursive: true });
2644
+ fs8.appendFileSync(file, `${JSON.stringify({ measured_at: scanned_at, ...rate })}
1834
2645
  `, "utf8");
1835
2646
  }
1836
2647
  function render(report) {
@@ -1861,7 +2672,26 @@ function render(report) {
1861
2672
  lines.push(
1862
2673
  ` language-pin rate ${report.rate.rate_pct.toFixed(1)}% (${report.rate.language_pin} of ${report.rate.assistant_turns} assistant turns)`
1863
2674
  );
1864
- if (report.rate.band === "corpus-too-small") {
2675
+ const dePinTurns = report.rate.de_pin_turns;
2676
+ const dePinRate = report.rate.rate_pct_de_pin;
2677
+ if (typeof dePinTurns === "number" && typeof dePinRate === "number") {
2678
+ lines.push(
2679
+ ` ${dePinRate.toFixed(1)}% (${report.rate.language_pin} of ${dePinTurns} turns under a GERMAN pin \u2014 the only turns this check can fire on)`
2680
+ );
2681
+ lines.push(
2682
+ " The band (M5, n=3) was computed on the ALL-turns denominator, so the first figure"
2683
+ );
2684
+ lines.push(" is the comparable one and the second is the honest one. Both are printed.");
2685
+ }
2686
+ if (report.rate.band === "era-spanning") {
2687
+ lines.push(
2688
+ ` Not compared: the corpus SPANS a carrier landing \u2014 ${report.rate.era_reason || "see CARRIER_CHANGES"}.`
2689
+ );
2690
+ lines.push(" A rate pooled across it is a weighted average of two different systems, not");
2691
+ lines.push(" a reading about behaviour. Round 7 measured 23.1% before and 0.0% after that");
2692
+ lines.push(" exact carrier; the pooled 6.5% was announced as the falsifier firing.");
2693
+ lines.push(" Split the window at the carrier date and read each era on its own.");
2694
+ } else if (report.rate.band === "corpus-too-small") {
1865
2695
  lines.push(
1866
2696
  ` Not compared: ${report.rate.assistant_turns} turns is below the ${BAND_MIN_TURNS} of the`
1867
2697
  );
@@ -1883,11 +2713,22 @@ function render(report) {
1883
2713
  lines.push(" as new. Store novelty is not project novelty.");
1884
2714
  }
1885
2715
  lines.push("");
1886
- lines.push(" Scanned checks are exactly the mechanised ones. Ask-shape, session-canary,");
1887
- lines.push(" promissory closings and checkbox batching are left as prose and NOT measured.");
2716
+ lines.push(" Scanned checks are exactly the mechanised ones. What is NOT in the counts above:");
2717
+ lines.push(" ask-shape (trailing free-text offer) \u2014 measured NOWHERE. ~11 findings in round 7's");
2718
+ lines.push(" reading half; the discriminator needs judgement, so it stays advisory on purpose.");
2719
+ lines.push(" session-canary (opening greeting) \u2014 not here, but PROBEABLE:");
2720
+ lines.push(" `./scripts-run src/scripts/probe_session_canary`. Round 7: 25/28 sessions,");
2721
+ lines.push(" 24/25 post-carrier. The per-TASK instance is undecidable \u2014 no task boundary");
2722
+ lines.push(" is recorded in a transcript.");
2723
+ lines.push(" promissory closings \u2014 not here, but ALREADY GATED: `turn-end-gate`");
2724
+ lines.push(" ships a blocking `detectPromissory`. Round 7 probe: 1 of 120 hand-back turns");
2725
+ lines.push(" (the 163 this line first printed was the retracted denominator \u2014 it counted");
2726
+ lines.push(" synthetic user turns as hand-back closers).");
2727
+ lines.push(" `./scripts-run src/scripts/probe_promissory_closing`.");
2728
+ lines.push(" checkbox batching \u2014 measured NOWHERE. ~5 low-severity findings.");
1888
2729
  return lines.join("\n");
1889
2730
  }
1890
- function main6(argv) {
2731
+ function main8(argv) {
1891
2732
  const args = argv ?? process.argv.slice(2);
1892
2733
  let store = null;
1893
2734
  let limit = 30;
@@ -1919,14 +2760,14 @@ function main6(argv) {
1919
2760
  return 2;
1920
2761
  }
1921
2762
  const resolved = store ?? defaultStore(process.cwd());
1922
- if (!fs6.existsSync(resolved)) {
2763
+ if (!fs8.existsSync(resolved)) {
1923
2764
  process.stderr.write(`conformance:behavior: no transcript store at ${resolved}
1924
2765
  `);
1925
2766
  return 1;
1926
2767
  }
1927
2768
  const report = scanStore(resolved, Number.isFinite(limit) && limit > 0 ? limit : 30);
1928
2769
  if (out) {
1929
- fs6.writeFileSync(out, `${JSON.stringify(report, null, 2)}
2770
+ fs8.writeFileSync(out, `${JSON.stringify(report, null, 2)}
1930
2771
  `, "utf8");
1931
2772
  }
1932
2773
  if (record) {
@@ -1934,7 +2775,7 @@ function main6(argv) {
1934
2775
  }
1935
2776
  if (why !== null) {
1936
2777
  const id = why;
1937
- fs6.writeSync(
2778
+ fs8.writeSync(
1938
2779
  1,
1939
2780
  json ? `${JSON.stringify(
1940
2781
  {
@@ -1954,48 +2795,48 @@ function main6(argv) {
1954
2795
  );
1955
2796
  return 0;
1956
2797
  }
1957
- fs6.writeSync(1, json ? `${JSON.stringify(report, null, 2)}
2798
+ fs8.writeSync(1, json ? `${JSON.stringify(report, null, 2)}
1958
2799
  ` : `${render(report)}
1959
2800
  `);
1960
2801
  return 0;
1961
2802
  }
1962
- function _isCliEntry6() {
2803
+ function _isCliEntry8() {
1963
2804
  if (true) {
1964
2805
  return false;
1965
2806
  }
1966
2807
  if (process.argv[1] === void 0) {
1967
2808
  return false;
1968
2809
  }
1969
- const argvUrl = pathToFileURL6(path6.resolve(process.argv[1])).href;
2810
+ const argvUrl = pathToFileURL8(path8.resolve(process.argv[1])).href;
1970
2811
  if (import.meta.url === argvUrl) {
1971
2812
  return true;
1972
2813
  }
1973
2814
  try {
1974
- const here = fs6.realpathSync(fileURLToPath6(import.meta.url));
1975
- const argv = fs6.realpathSync(path6.resolve(process.argv[1]));
2815
+ const here = fs8.realpathSync(fileURLToPath8(import.meta.url));
2816
+ const argv = fs8.realpathSync(path8.resolve(process.argv[1]));
1976
2817
  return here === argv;
1977
2818
  } catch {
1978
2819
  return false;
1979
2820
  }
1980
2821
  }
1981
- if (_isCliEntry6()) {
1982
- process.exit(main6());
2822
+ if (_isCliEntry8()) {
2823
+ process.exit(main8());
1983
2824
  }
1984
2825
 
1985
2826
  // src/scripts/report_carrier_divergence.ts
1986
- import * as fs7 from "node:fs";
1987
- import * as os2 from "node:os";
1988
- import * as path7 from "node:path";
2827
+ import * as fs9 from "node:fs";
2828
+ import * as os3 from "node:os";
2829
+ import * as path9 from "node:path";
1989
2830
  import process2 from "node:process";
1990
- import { fileURLToPath as fileURLToPath7, pathToFileURL as pathToFileURL7 } from "node:url";
1991
- var _HERE = fileURLToPath7(import.meta.url);
1992
- var REPO_ROOT2 = path7.resolve(path7.dirname(_HERE), "..", "..");
1993
- var PROJECT_RULES = path7.join(".claude", "rules");
1994
- var PROJECTION_SOURCE = path7.join("dist", "agent-src", "rules");
1995
- var GLOBAL_RULES = path7.join(".claude", "rules");
2831
+ import { fileURLToPath as fileURLToPath9, pathToFileURL as pathToFileURL9 } from "node:url";
2832
+ var _HERE = fileURLToPath9(import.meta.url);
2833
+ var REPO_ROOT2 = path9.resolve(path9.dirname(_HERE), "..", "..");
2834
+ var PROJECT_RULES = path9.join(".claude", "rules");
2835
+ var PROJECTION_SOURCE = path9.join("dist", "agent-src", "rules");
2836
+ var GLOBAL_RULES = path9.join(".claude", "rules");
1996
2837
  function _listRules(dir) {
1997
2838
  try {
1998
- return fs7.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
2839
+ return fs9.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
1999
2840
  } catch {
2000
2841
  return [];
2001
2842
  }
@@ -2003,7 +2844,7 @@ function _listRules(dir) {
2003
2844
  function _ruleType(file) {
2004
2845
  let text;
2005
2846
  try {
2006
- text = fs7.readFileSync(file, "utf-8");
2847
+ text = fs9.readFileSync(file, "utf-8");
2007
2848
  } catch {
2008
2849
  return "";
2009
2850
  }
@@ -2014,8 +2855,8 @@ function compareCarriers(projectDir, globalDir, sourceDir) {
2014
2855
  const out = {
2015
2856
  projectDir,
2016
2857
  globalDir,
2017
- globalPresent: fs7.existsSync(globalDir),
2018
- projectPresent: fs7.existsSync(projectDir),
2858
+ globalPresent: fs9.existsSync(globalDir),
2859
+ projectPresent: fs9.existsSync(projectDir),
2019
2860
  shared: 0,
2020
2861
  identical: [],
2021
2862
  provenanceOnly: [],
@@ -2038,7 +2879,7 @@ function compareCarriers(projectDir, globalDir, sourceDir) {
2038
2879
  }
2039
2880
  if (!inP && inG) {
2040
2881
  out.globalOnly.push(rule);
2041
- if (sourceDir !== void 0 && _ruleType(path7.join(sourceDir, rule)) === "manual") {
2882
+ if (sourceDir !== void 0 && _ruleType(path9.join(sourceDir, rule)) === "manual") {
2042
2883
  out.manualOnlyGlobal.push(rule);
2043
2884
  }
2044
2885
  continue;
@@ -2048,8 +2889,8 @@ function compareCarriers(projectDir, globalDir, sourceDir) {
2048
2889
  let projectCopy;
2049
2890
  let globalCopy;
2050
2891
  try {
2051
- projectCopy = fs7.readFileSync(path7.join(projectDir, rule));
2052
- globalCopy = fs7.readFileSync(path7.join(globalDir, rule));
2892
+ projectCopy = fs9.readFileSync(path9.join(projectDir, rule));
2893
+ globalCopy = fs9.readFileSync(path9.join(globalDir, rule));
2053
2894
  verdict = comparePair(projectCopy, globalCopy);
2054
2895
  } catch {
2055
2896
  out.shared -= 1;
@@ -2194,11 +3035,11 @@ function render2(d) {
2194
3035
  }
2195
3036
  return lines.join("\n");
2196
3037
  }
2197
- function main7(argv) {
3038
+ function main9(argv) {
2198
3039
  const args = argv ?? process2.argv.slice(2);
2199
- let projectDir = path7.join(REPO_ROOT2, PROJECT_RULES);
2200
- let globalDir = path7.join(os2.homedir(), GLOBAL_RULES);
2201
- let sourceDir = path7.join(REPO_ROOT2, PROJECTION_SOURCE);
3040
+ let projectDir = path9.join(REPO_ROOT2, PROJECT_RULES);
3041
+ let globalDir = path9.join(os3.homedir(), GLOBAL_RULES);
3042
+ let sourceDir = path9.join(REPO_ROOT2, PROJECTION_SOURCE);
2202
3043
  const value = (i) => {
2203
3044
  const v = args[i + 1];
2204
3045
  return v === void 0 || v.startsWith("-") ? null : v;
@@ -2212,7 +3053,7 @@ function main7(argv) {
2212
3053
  `);
2213
3054
  return 1;
2214
3055
  }
2215
- const resolved = path7.resolve(v);
3056
+ const resolved = path9.resolve(v);
2216
3057
  if (a === "--project") projectDir = resolved;
2217
3058
  else if (a === "--global") globalDir = resolved;
2218
3059
  else sourceDir = resolved;
@@ -2232,44 +3073,44 @@ function main7(argv) {
2232
3073
  `);
2233
3074
  return 0;
2234
3075
  }
2235
- function _isCliEntry7() {
3076
+ function _isCliEntry9() {
2236
3077
  if (process2.argv[1] === void 0) return false;
2237
- if (import.meta.url === pathToFileURL7(path7.resolve(process2.argv[1])).href) return true;
3078
+ if (import.meta.url === pathToFileURL9(path9.resolve(process2.argv[1])).href) return true;
2238
3079
  try {
2239
- return fs7.realpathSync(_HERE) === fs7.realpathSync(path7.resolve(process2.argv[1]));
3080
+ return fs9.realpathSync(_HERE) === fs9.realpathSync(path9.resolve(process2.argv[1]));
2240
3081
  } catch {
2241
3082
  return false;
2242
3083
  }
2243
3084
  }
2244
- if (_isCliEntry7()) {
2245
- process2.exit(main7());
3085
+ if (_isCliEntry9()) {
3086
+ process2.exit(main9());
2246
3087
  }
2247
3088
 
2248
3089
  // src/scripts/report_skill_activation.ts
2249
- import * as fs8 from "node:fs";
2250
- import * as os3 from "node:os";
2251
- import * as path8 from "node:path";
2252
- import { fileURLToPath as fileURLToPath8, pathToFileURL as pathToFileURL8 } from "node:url";
2253
- var _HERE2 = fileURLToPath8(import.meta.url);
2254
- var REPO_ROOT3 = path8.resolve(path8.dirname(_HERE2), "..", "..");
3090
+ import * as fs10 from "node:fs";
3091
+ import * as os4 from "node:os";
3092
+ import * as path10 from "node:path";
3093
+ import { fileURLToPath as fileURLToPath10, pathToFileURL as pathToFileURL10 } from "node:url";
3094
+ var _HERE2 = fileURLToPath10(import.meta.url);
3095
+ var REPO_ROOT3 = path10.resolve(path10.dirname(_HERE2), "..", "..");
2255
3096
  var SKILLS_ROOT = "src/skills";
2256
3097
  var TRIGGER_KEYS = ["triggers:", "trigger_description:", "file_pattern:", "path_prefix:"];
2257
3098
  var DETERMINISTIC_RE = /^\s*(?:[-*]\s*)?(?:\*\*)?(MUST|NEVER|ALWAYS)\b/m;
2258
3099
  function censusSkills(root) {
2259
3100
  const out = { total: 0, withTriggerKey: [], withDeterministicObligation: [] };
2260
- if (!fs8.existsSync(root)) {
3101
+ if (!fs10.existsSync(root)) {
2261
3102
  return out;
2262
3103
  }
2263
- for (const entry of fs8.readdirSync(root, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
3104
+ for (const entry of fs10.readdirSync(root, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
2264
3105
  if (!entry.isDirectory()) {
2265
3106
  continue;
2266
3107
  }
2267
- const file = path8.join(root, entry.name, "SKILL.md");
2268
- if (!fs8.existsSync(file)) {
3108
+ const file = path10.join(root, entry.name, "SKILL.md");
3109
+ if (!fs10.existsSync(file)) {
2269
3110
  continue;
2270
3111
  }
2271
3112
  out.total += 1;
2272
- const text = fs8.readFileSync(file, "utf8");
3113
+ const text = fs10.readFileSync(file, "utf8");
2273
3114
  const fmEnd = text.startsWith("---") ? text.indexOf("\n---", 3) : -1;
2274
3115
  const fm = fmEnd === -1 ? "" : text.slice(0, fmEnd);
2275
3116
  if (TRIGGER_KEYS.some((k) => new RegExp(`^${k}`, "m").test(fm))) {
@@ -2283,17 +3124,17 @@ function censusSkills(root) {
2283
3124
  return out;
2284
3125
  }
2285
3126
  function defaultStore2(cwd) {
2286
- return path8.join(os3.homedir(), ".claude", "projects", cwd.replace(/[/.]/g, "-"));
3127
+ return path10.join(os4.homedir(), ".claude", "projects", cwd.replace(/[/.]/g, "-"));
2287
3128
  }
2288
3129
  function measureUsage(store, limit) {
2289
3130
  const rep = { store, sessions: 0, assistantTurns: 0, invocations: 0, bySkill: {} };
2290
- if (!fs8.existsSync(store)) {
3131
+ if (!fs10.existsSync(store)) {
2291
3132
  return rep;
2292
3133
  }
2293
- const files = fs8.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => path8.join(store, f)).sort((a, b) => fs8.statSync(b).mtimeMs - fs8.statSync(a).mtimeMs).slice(0, limit);
3134
+ const files = fs10.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => path10.join(store, f)).sort((a, b) => fs10.statSync(b).mtimeMs - fs10.statSync(a).mtimeMs).slice(0, limit);
2294
3135
  rep.sessions = files.length;
2295
3136
  for (const file of files) {
2296
- for (const line of fs8.readFileSync(file, "utf8").split("\n")) {
3137
+ for (const line of fs10.readFileSync(file, "utf8").split("\n")) {
2297
3138
  if (!line.trim()) {
2298
3139
  continue;
2299
3140
  }
@@ -2356,7 +3197,7 @@ function render3(census, usage) {
2356
3197
  }
2357
3198
  totalInv += u.invocations;
2358
3199
  lines.push(
2359
- ` ${path8.basename(u.store).slice(-38).padEnd(38)} sessions=${String(u.sessions).padStart(3)} asst=${String(u.assistantTurns).padStart(6)} Skill calls=${String(u.invocations).padStart(3)}`
3200
+ ` ${path10.basename(u.store).slice(-38).padEnd(38)} sessions=${String(u.sessions).padStart(3)} asst=${String(u.assistantTurns).padStart(6)} Skill calls=${String(u.invocations).padStart(3)}`
2360
3201
  );
2361
3202
  }
2362
3203
  lines.push("");
@@ -2381,7 +3222,7 @@ function render3(census, usage) {
2381
3222
  lines.push(" catalogue once per session, then count descriptions against bare names.");
2382
3223
  return lines.join("\n");
2383
3224
  }
2384
- function main8(argv) {
3225
+ function main10(argv) {
2385
3226
  let limit = 30;
2386
3227
  const stores = [];
2387
3228
  for (let i = 0; i < argv.length; i += 1) {
@@ -2404,24 +3245,24 @@ function main8(argv) {
2404
3245
  if (stores.length === 0) {
2405
3246
  stores.push(defaultStore2(REPO_ROOT3));
2406
3247
  }
2407
- const census = censusSkills(path8.join(REPO_ROOT3, SKILLS_ROOT));
3248
+ const census = censusSkills(path10.join(REPO_ROOT3, SKILLS_ROOT));
2408
3249
  const usage = stores.map((s) => measureUsage(s, limit));
2409
3250
  process.stdout.write(`${render3(census, usage)}
2410
3251
  `);
2411
3252
  return 0;
2412
3253
  }
2413
- if (process.argv[1] !== void 0 && import.meta.url === pathToFileURL8(path8.resolve(process.argv[1])).href) {
2414
- process.exit(main8(process.argv.slice(2)));
3254
+ if (process.argv[1] !== void 0 && import.meta.url === pathToFileURL10(path10.resolve(process.argv[1])).href) {
3255
+ process.exit(main10(process.argv.slice(2)));
2415
3256
  }
2416
3257
 
2417
3258
  // src/scripts/report_skill_obligation_violations.ts
2418
- import * as fs9 from "node:fs";
2419
- import * as os4 from "node:os";
2420
- import * as path9 from "node:path";
3259
+ import * as fs11 from "node:fs";
3260
+ import * as os5 from "node:os";
3261
+ import * as path11 from "node:path";
2421
3262
  import process3 from "node:process";
2422
- import { fileURLToPath as fileURLToPath9, pathToFileURL as pathToFileURL9 } from "node:url";
2423
- var _HERE3 = fileURLToPath9(import.meta.url);
2424
- var REPO_ROOT4 = path9.resolve(path9.dirname(_HERE3), "..", "..");
3263
+ import { fileURLToPath as fileURLToPath11, pathToFileURL as pathToFileURL11 } from "node:url";
3264
+ var _HERE3 = fileURLToPath11(import.meta.url);
3265
+ var REPO_ROOT4 = path11.resolve(path11.dirname(_HERE3), "..", "..");
2425
3266
  var OBLIGATION_LINE_RE = new RegExp(`${DETERMINISTIC_RE.source}.*`, "gm");
2426
3267
  var PATH_RE = /`([A-Za-z0-9_./-]*\/[A-Za-z0-9_.-]+\.[a-z]{2,6})`/g;
2427
3268
  var CMD_RE = /`([a-z][a-z0-9-]*(?: [a-z][a-z0-9:._-]*){1,3})`/g;
@@ -2459,7 +3300,7 @@ function _artefacts(line) {
2459
3300
  return out;
2460
3301
  }
2461
3302
  function extractObligations(repoRoot) {
2462
- const skillsRoot = path9.join(repoRoot, SKILLS_ROOT);
3303
+ const skillsRoot = path11.join(repoRoot, SKILLS_ROOT);
2463
3304
  const census = censusSkills(skillsRoot);
2464
3305
  const out = {
2465
3306
  skills: census.withDeterministicObligation,
@@ -2473,7 +3314,7 @@ function extractObligations(repoRoot) {
2473
3314
  for (const skill of census.withDeterministicObligation) {
2474
3315
  let text;
2475
3316
  try {
2476
- text = fs9.readFileSync(path9.join(skillsRoot, skill, "SKILL.md"), "utf-8");
3317
+ text = fs11.readFileSync(path11.join(skillsRoot, skill, "SKILL.md"), "utf-8");
2477
3318
  } catch {
2478
3319
  continue;
2479
3320
  }
@@ -2589,7 +3430,7 @@ function scanSessionForViolations(session, lines, forbidden, parsed) {
2589
3430
  return flags;
2590
3431
  }
2591
3432
  function defaultStore3(cwd) {
2592
- return path9.join(os4.homedir(), ".claude", "projects", cwd.replace(/[/.]/g, "-"));
3433
+ return path11.join(os5.homedir(), ".claude", "projects", cwd.replace(/[/.]/g, "-"));
2593
3434
  }
2594
3435
  function scanStore2(repoRoot, store, limit) {
2595
3436
  const census = extractObligations(repoRoot);
@@ -2602,14 +3443,14 @@ function scanStore2(repoRoot, store, limit) {
2602
3443
  };
2603
3444
  let files;
2604
3445
  try {
2605
- files = fs9.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => ({ p: path9.join(store, f), m: fs9.statSync(path9.join(store, f)).mtimeMs })).sort((a, b) => b.m - a.m).slice(0, limit).map((x) => x.p);
3446
+ files = fs11.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => ({ p: path11.join(store, f), m: fs11.statSync(path11.join(store, f)).mtimeMs })).sort((a, b) => b.m - a.m).slice(0, limit).map((x) => x.p);
2606
3447
  } catch {
2607
3448
  return report;
2608
3449
  }
2609
3450
  report.sessions = files.length;
2610
3451
  for (const file of files) {
2611
- const lines = fs9.readFileSync(file, "utf-8").split("\n");
2612
- const id = path9.basename(file).replace(/\.jsonl$/, "").slice(0, 8);
3452
+ const lines = fs11.readFileSync(file, "utf-8").split("\n");
3453
+ const id = path11.basename(file).replace(/\.jsonl$/, "").slice(0, 8);
2613
3454
  const entries = _parseSession(lines);
2614
3455
  if (loadedSkillsFrom(entries).size > 0) report.sessionsWithASkill += 1;
2615
3456
  report.flags.push(...scanSessionForViolations(id, lines, census.forbidden, entries));
@@ -2679,7 +3520,7 @@ function render4(r) {
2679
3520
  lines.push(" ships as detection-only, and this one says so.");
2680
3521
  return lines.join("\n");
2681
3522
  }
2682
- function main9(argv) {
3523
+ function main11(argv) {
2683
3524
  const args = argv ?? process3.argv.slice(2);
2684
3525
  let limit = 30;
2685
3526
  let json = false;
@@ -2732,35 +3573,35 @@ function main9(argv) {
2732
3573
  }
2733
3574
  return 0;
2734
3575
  }
2735
- function _isCliEntry8() {
3576
+ function _isCliEntry10() {
2736
3577
  if (process3.argv[1] === void 0) return false;
2737
- if (import.meta.url === pathToFileURL9(path9.resolve(process3.argv[1])).href) return true;
3578
+ if (import.meta.url === pathToFileURL11(path11.resolve(process3.argv[1])).href) return true;
2738
3579
  try {
2739
- return fs9.realpathSync(_HERE3) === fs9.realpathSync(path9.resolve(process3.argv[1]));
3580
+ return fs11.realpathSync(_HERE3) === fs11.realpathSync(path11.resolve(process3.argv[1]));
2740
3581
  } catch {
2741
3582
  return false;
2742
3583
  }
2743
3584
  }
2744
- if (_isCliEntry8()) {
2745
- process3.exit(main9());
3585
+ if (_isCliEntry10()) {
3586
+ process3.exit(main11());
2746
3587
  }
2747
3588
 
2748
3589
  // src/scripts/report_conformance_funnel.ts
2749
- var _HERE4 = fileURLToPath10(import.meta.url);
2750
- var REPO_ROOT5 = path10.resolve(path10.dirname(_HERE4), "..", "..");
3590
+ var _HERE4 = fileURLToPath12(import.meta.url);
3591
+ var REPO_ROOT5 = path12.resolve(path12.dirname(_HERE4), "..", "..");
2751
3592
  function buildFunnel(opts = {}) {
2752
3593
  const repoRoot = opts.repoRoot ?? REPO_ROOT5;
2753
- const projectRulesDir = opts.projectRulesDir ?? path10.join(repoRoot, PROJECT_RULES);
2754
- const globalRulesDir = opts.globalRulesDir ?? path10.join(os5.homedir(), GLOBAL_RULES);
2755
- const sourceDir = opts.sourceDir ?? path10.join(repoRoot, PROJECTION_SOURCE);
3594
+ const projectRulesDir = opts.projectRulesDir ?? path12.join(repoRoot, PROJECT_RULES);
3595
+ const globalRulesDir = opts.globalRulesDir ?? path12.join(os6.homedir(), GLOBAL_RULES);
3596
+ const sourceDir = opts.sourceDir ?? path12.join(repoRoot, PROJECTION_SOURCE);
2756
3597
  const store = opts.store ?? defaultStore(repoRoot);
2757
3598
  const limit = opts.limit ?? 30;
2758
3599
  return {
2759
3600
  delivered: measureDelivered(projectRulesDir, globalRulesDir),
2760
3601
  divergence: compareCarriers(projectRulesDir, globalRulesDir, sourceDir),
2761
- skills: censusSkills(path10.join(repoRoot, SKILLS_ROOT)),
3602
+ skills: censusSkills(path12.join(repoRoot, SKILLS_ROOT)),
2762
3603
  usage: measureUsage(store, limit),
2763
- storePresent: fs10.existsSync(store),
3604
+ storePresent: fs12.existsSync(store),
2764
3605
  sk2: scanStore2(repoRoot, store, limit)
2765
3606
  };
2766
3607
  }
@@ -2885,7 +3726,7 @@ function render5(r) {
2885
3726
  lines.push("workflow without a measured false-positive rate first (locked policy).");
2886
3727
  return lines.join("\n");
2887
3728
  }
2888
- function main10(argv) {
3729
+ function main12(argv) {
2889
3730
  const args = argv ?? process4.argv.slice(2);
2890
3731
  const opts = {};
2891
3732
  const value = (i) => {
@@ -2901,7 +3742,7 @@ function main10(argv) {
2901
3742
  `);
2902
3743
  return 1;
2903
3744
  }
2904
- const resolved = path10.resolve(v);
3745
+ const resolved = path12.resolve(v);
2905
3746
  if (a === "--store") opts.store = resolved;
2906
3747
  else if (a === "--project") opts.projectRulesDir = resolved;
2907
3748
  else if (a === "--global") opts.globalRulesDir = resolved;
@@ -2931,21 +3772,21 @@ function main10(argv) {
2931
3772
  `);
2932
3773
  return 0;
2933
3774
  }
2934
- function _isCliEntry9() {
3775
+ function _isCliEntry11() {
2935
3776
  if (process4.argv[1] === void 0) return false;
2936
- if (import.meta.url === pathToFileURL10(path10.resolve(process4.argv[1])).href) return true;
3777
+ if (import.meta.url === pathToFileURL12(path12.resolve(process4.argv[1])).href) return true;
2937
3778
  try {
2938
- return fs10.realpathSync(_HERE4) === fs10.realpathSync(path10.resolve(process4.argv[1]));
3779
+ return fs12.realpathSync(_HERE4) === fs12.realpathSync(path12.resolve(process4.argv[1]));
2939
3780
  } catch {
2940
3781
  return false;
2941
3782
  }
2942
3783
  }
2943
- if (_isCliEntry9()) {
2944
- process4.exit(main10());
3784
+ if (_isCliEntry11()) {
3785
+ process4.exit(main12());
2945
3786
  }
2946
3787
 
2947
3788
  // src/scripts/_cli/cmd_conformance.ts
2948
- var _HERE5 = fileURLToPath11(import.meta.url);
3789
+ var _HERE5 = fileURLToPath13(import.meta.url);
2949
3790
  var PACKAGE_ROOT = resolvePackageRoot(import.meta.url);
2950
3791
  var CONFORMANCE_CHECK_IDS = [
2951
3792
  "txlog-clean",
@@ -2968,17 +3809,17 @@ function conformanceLogPath() {
2968
3809
  if (override && override.trim().length > 0) {
2969
3810
  return override;
2970
3811
  }
2971
- return path11.join(os6.homedir(), ".event4u", "agent-config", "conformance-log.jsonl");
3812
+ return path13.join(os7.homedir(), ".event4u", "agent-config", "conformance-log.jsonl");
2972
3813
  }
2973
3814
  function installLogPath() {
2974
3815
  const override = process5.env["AGENT_CONFIG_INSTALL_LOG"];
2975
3816
  if (override && override.trim().length > 0) {
2976
3817
  return override;
2977
3818
  }
2978
- return path11.join(os6.homedir(), ".event4u", "agent-config", "install-log.jsonl");
3819
+ return path13.join(os7.homedir(), ".event4u", "agent-config", "install-log.jsonl");
2979
3820
  }
2980
3821
  function _check_txlog_clean(logPath = installLogPath()) {
2981
- if (!fs11.existsSync(logPath)) {
3822
+ if (!fs13.existsSync(logPath)) {
2982
3823
  return {
2983
3824
  id: "txlog-clean",
2984
3825
  status: "ok",
@@ -3012,11 +3853,11 @@ function _check_txlog_clean(logPath = installLogPath()) {
3012
3853
  };
3013
3854
  }
3014
3855
  function routeTargetPaths(target) {
3015
- return routeTargetPathsPosix(target).map((rel) => path11.join(...rel.split("/")));
3856
+ return routeTargetPathsPosix(target).map((rel) => path13.join(...rel.split("/")));
3016
3857
  }
3017
3858
  function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
3018
- const routerPath = path11.join(packageRoot, "dist", "router.json");
3019
- if (!fs11.existsSync(routerPath)) {
3859
+ const routerPath = path13.join(packageRoot, "dist", "router.json");
3860
+ if (!fs13.existsSync(routerPath)) {
3020
3861
  return {
3021
3862
  id: "router-pointers",
3022
3863
  status: "fail",
@@ -3026,7 +3867,7 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
3026
3867
  }
3027
3868
  let router;
3028
3869
  try {
3029
- router = JSON.parse(fs11.readFileSync(routerPath, "utf8"));
3870
+ router = JSON.parse(fs13.readFileSync(routerPath, "utf8"));
3030
3871
  } catch (exc) {
3031
3872
  return {
3032
3873
  id: "router-pointers",
@@ -3035,13 +3876,13 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
3035
3876
  remedy: "reinstall the package \u2014 dist/router.json is corrupt"
3036
3877
  };
3037
3878
  }
3038
- const rulesDir = path11.join(packageRoot, "dist", "agent-src", "rules");
3879
+ const rulesDir = path13.join(packageRoot, "dist", "agent-src", "rules");
3039
3880
  const broken = [];
3040
3881
  let ids = 0;
3041
3882
  let targets = 0;
3042
3883
  for (const kernelId of router["kernel"] ?? []) {
3043
3884
  ids += 1;
3044
- if (!fs11.existsSync(path11.join(rulesDir, `${kernelId}.md`))) {
3885
+ if (!fs13.existsSync(path13.join(rulesDir, `${kernelId}.md`))) {
3045
3886
  broken.push(`kernel:${kernelId} \u2192 rules/${kernelId}.md missing`);
3046
3887
  }
3047
3888
  }
@@ -3049,7 +3890,7 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
3049
3890
  for (const entry of router[tier] ?? []) {
3050
3891
  const ruleId = String(entry["id"] ?? "");
3051
3892
  ids += 1;
3052
- if (!fs11.existsSync(path11.join(rulesDir, `${ruleId}.md`))) {
3893
+ if (!fs13.existsSync(path13.join(rulesDir, `${ruleId}.md`))) {
3053
3894
  broken.push(`${tier}:${ruleId} \u2192 rules/${ruleId}.md missing`);
3054
3895
  }
3055
3896
  for (const target of entry["routes_to"] ?? []) {
@@ -3059,7 +3900,7 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
3059
3900
  broken.push(`${ruleId} \u2192 unparseable routes_to ${JSON.stringify(target)}`);
3060
3901
  continue;
3061
3902
  }
3062
- if (!candidates.some((rel) => fs11.existsSync(path11.join(packageRoot, rel)))) {
3903
+ if (!candidates.some((rel) => fs13.existsSync(path13.join(packageRoot, rel)))) {
3063
3904
  broken.push(`${ruleId} \u2192 ${target} (${candidates[0]} missing)`);
3064
3905
  }
3065
3906
  }
@@ -3091,9 +3932,9 @@ function detectSmokePlatform(projectRoot) {
3091
3932
  return "claude";
3092
3933
  }
3093
3934
  function dispatcherRunner(packageRoot) {
3094
- const localTsx = path11.join(packageRoot, "node_modules", ".bin", "tsx");
3095
- const script = path11.join(packageRoot, "src", "scripts", "hooks", "dispatch_hook.ts");
3096
- if (fs11.existsSync(localTsx)) {
3935
+ const localTsx = path13.join(packageRoot, "node_modules", ".bin", "tsx");
3936
+ const script = path13.join(packageRoot, "src", "scripts", "hooks", "dispatch_hook.ts");
3937
+ if (fs13.existsSync(localTsx)) {
3097
3938
  return [localTsx, script];
3098
3939
  }
3099
3940
  return ["npx", "tsx", script];
@@ -3110,7 +3951,7 @@ function _check_hook_dispatcher(projectRoot, packageRoot = PACKAGE_ROOT, opts =
3110
3951
  ...event === "stop" ? { stop_reason: "conformance-smoke" } : { source: "startup" }
3111
3952
  });
3112
3953
  const argv = [...runner, "--platform", platform, "--event", event];
3113
- const proc = spawnSync(argv[0], argv.slice(1), {
3954
+ const proc = spawnSync2(argv[0], argv.slice(1), {
3114
3955
  input: payload,
3115
3956
  cwd: projectRoot,
3116
3957
  encoding: "utf8",
@@ -3143,12 +3984,12 @@ function _check_hook_dispatcher(projectRoot, packageRoot = PACKAGE_ROOT, opts =
3143
3984
  };
3144
3985
  }
3145
3986
  function readSettingsMode(projectRoot) {
3146
- const settingsPath = path11.join(projectRoot, ".agent-settings.yml");
3147
- if (!fs11.existsSync(settingsPath)) {
3987
+ const settingsPath = path13.join(projectRoot, ".agent-settings.yml");
3988
+ if (!fs13.existsSync(settingsPath)) {
3148
3989
  return "eager-all";
3149
3990
  }
3150
3991
  try {
3151
- const parsed = YAML.parse(fs11.readFileSync(settingsPath, "utf8"));
3992
+ const parsed = YAML.parse(fs13.readFileSync(settingsPath, "utf8"));
3152
3993
  const lean = parsed?.["lean_projection"] ?? null;
3153
3994
  const mode = lean?.["mode"];
3154
3995
  return typeof mode === "string" && mode.length > 0 ? mode : "eager-all";
@@ -3159,7 +4000,7 @@ function readSettingsMode(projectRoot) {
3159
4000
  function kernelIdsFromRouter(packageRoot) {
3160
4001
  try {
3161
4002
  const router = JSON.parse(
3162
- fs11.readFileSync(path11.join(packageRoot, "dist", "router.json"), "utf8")
4003
+ fs13.readFileSync(path13.join(packageRoot, "dist", "router.json"), "utf8")
3163
4004
  );
3164
4005
  return new Set((router["kernel"] ?? []).map(String));
3165
4006
  } catch {
@@ -3176,8 +4017,8 @@ function _check_lean_projection(projectRoot, packageRoot = PACKAGE_ROOT, project
3176
4017
  remedy: "set lean_projection.mode to 'eager-all' or 'thin' in .agent-settings.yml"
3177
4018
  };
3178
4019
  }
3179
- const rulesDir = projectedRulesDir ?? path11.join(projectRoot, ".augment", "rules");
3180
- if (!fs11.existsSync(rulesDir)) {
4020
+ const rulesDir = projectedRulesDir ?? path13.join(projectRoot, ".augment", "rules");
4021
+ if (!fs13.existsSync(rulesDir)) {
3181
4022
  return {
3182
4023
  id: "lean-projection",
3183
4024
  status: "skipped",
@@ -3186,7 +4027,7 @@ function _check_lean_projection(projectRoot, packageRoot = PACKAGE_ROOT, project
3186
4027
  };
3187
4028
  }
3188
4029
  const kernel = kernelIdsFromRouter(packageRoot);
3189
- const files = fs11.readdirSync(rulesDir).filter((f) => f.endsWith(".md") && f !== "INDEX.md").sort();
4030
+ const files = fs13.readdirSync(rulesDir).filter((f) => f.endsWith(".md") && f !== "INDEX.md").sort();
3190
4031
  const nonKernel = files.filter((f) => !kernel.has(f.replace(/\.md$/, "")));
3191
4032
  if (nonKernel.length === 0) {
3192
4033
  return {
@@ -3200,7 +4041,7 @@ function _check_lean_projection(projectRoot, packageRoot = PACKAGE_ROOT, project
3200
4041
  for (const f of nonKernel) {
3201
4042
  let text = "";
3202
4043
  try {
3203
- text = fs11.readFileSync(path11.join(rulesDir, f), "utf8");
4044
+ text = fs13.readFileSync(path13.join(rulesDir, f), "utf8");
3204
4045
  } catch {
3205
4046
  continue;
3206
4047
  }
@@ -3231,8 +4072,8 @@ function _check_lean_projection(projectRoot, packageRoot = PACKAGE_ROOT, project
3231
4072
  function _check_host_manifest(projectRoot) {
3232
4073
  const detected = Object.entries(detectToolPresence(projectRoot)).filter(([, v]) => v === true).map(([k]) => k);
3233
4074
  const detectedNote = detected.length > 0 ? `detected surfaces: ${detected.join(", ")}` : "no surfaces detected";
3234
- const settingsPath = path11.join(projectRoot, ".agent-settings.yml");
3235
- if (!fs11.existsSync(settingsPath)) {
4075
+ const settingsPath = path13.join(projectRoot, ".agent-settings.yml");
4076
+ if (!fs13.existsSync(settingsPath)) {
3236
4077
  return {
3237
4078
  id: "host-manifest",
3238
4079
  status: "ok",
@@ -3242,7 +4083,7 @@ function _check_host_manifest(projectRoot) {
3242
4083
  }
3243
4084
  let parsed = null;
3244
4085
  try {
3245
- parsed = YAML.parse(fs11.readFileSync(settingsPath, "utf8"));
4086
+ parsed = YAML.parse(fs13.readFileSync(settingsPath, "utf8"));
3246
4087
  } catch (exc) {
3247
4088
  return {
3248
4089
  id: "host-manifest",
@@ -3341,7 +4182,7 @@ function _parse(argv) {
3341
4182
  }
3342
4183
  return opts;
3343
4184
  }
3344
- function main11(argv = null) {
4185
+ function main13(argv = null) {
3345
4186
  const opts = _parse(argv !== null ? Array.from(argv) : process5.argv.slice(2));
3346
4187
  if (opts.funnel) {
3347
4188
  print(render5(buildFunnel()));
@@ -3412,25 +4253,25 @@ function main11(argv = null) {
3412
4253
  }
3413
4254
  return failed ? 1 : 0;
3414
4255
  }
3415
- function _isCliEntry10() {
4256
+ function _isCliEntry12() {
3416
4257
  if (process5.argv[1] === void 0) {
3417
4258
  return false;
3418
4259
  }
3419
- const argvUrl = pathToFileURL11(path11.resolve(process5.argv[1])).href;
4260
+ const argvUrl = pathToFileURL13(path13.resolve(process5.argv[1])).href;
3420
4261
  if (import.meta.url === argvUrl) {
3421
4262
  return true;
3422
4263
  }
3423
4264
  try {
3424
- const here = fs11.realpathSync(fileURLToPath11(import.meta.url));
3425
- const argv = fs11.realpathSync(path11.resolve(process5.argv[1]));
4265
+ const here = fs13.realpathSync(fileURLToPath13(import.meta.url));
4266
+ const argv = fs13.realpathSync(path13.resolve(process5.argv[1]));
3426
4267
  return here === argv;
3427
4268
  } catch {
3428
4269
  return false;
3429
4270
  }
3430
4271
  }
3431
- if (_isCliEntry10() || process5.argv[1] === _HERE5) {
4272
+ if (_isCliEntry12() || process5.argv[1] === _HERE5) {
3432
4273
  try {
3433
- process5.exitCode = main11(process5.argv.slice(2));
4274
+ process5.exitCode = main13(process5.argv.slice(2));
3434
4275
  } catch (e) {
3435
4276
  if (e instanceof ArgparseExit) {
3436
4277
  process5.exitCode = e.code;
@@ -3453,7 +4294,7 @@ export {
3453
4294
  conformanceLogPath,
3454
4295
  detectSmokePlatform,
3455
4296
  installLogPath,
3456
- main11 as main,
4297
+ main13 as main,
3457
4298
  routeTargetPaths,
3458
4299
  runConformanceChecks
3459
4300
  };