@event4u/agent-config 9.29.0 → 9.30.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 (122) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +108 -7
  3. package/README.md +1 -0
  4. package/dist/agent-src/commands/analyze/inbox.md +22 -1
  5. package/dist/agent-src/commands/explain-run.md +31 -1
  6. package/dist/agent-src/commands/team/delegate.md +1 -0
  7. package/dist/agent-src/contexts/execution/orchestration-telemetry.md +4 -1
  8. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +17 -0
  9. package/dist/agent-src/contexts/execution/subagent-response-contract.md +11 -1
  10. package/dist/agent-src/rules/preservation-guard.md +9 -0
  11. package/dist/agent-src/scripts/roadmap_gates.ts +312 -0
  12. package/dist/agent-src/scripts/update_roadmap_progress.ts +72 -4
  13. package/dist/agent-src/skills/subagent-orchestration/SKILL.md +10 -0
  14. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  15. package/dist/cli/registry.js +2 -0
  16. package/dist/cli/registry.js.map +1 -1
  17. package/dist/cli-delegate/{chunk-OJM3MBJM.js → chunk-BTJNRUX3.js} +21 -34
  18. package/dist/cli-delegate/{chunk-MMXA5GY7.js → chunk-DDUNNFYX.js} +14 -0
  19. package/dist/cli-delegate/{chunk-NZ5N2XQL.js → chunk-EQY4FLJB.js} +1 -1
  20. package/dist/cli-delegate/{chunk-RCIA67XN.js → chunk-IYTGIG2Y.js} +44 -1
  21. package/dist/cli-delegate/chunk-MMJL4TIX.js +18 -0
  22. package/dist/cli-delegate/chunk-O3CO5BFB.js +161 -0
  23. package/dist/cli-delegate/{chunk-REKCMYOU.js → chunk-PFBV6XRT.js} +36 -20
  24. package/dist/cli-delegate/chunk-RVGBH4XK.js +142 -0
  25. package/dist/cli-delegate/cmd_conformance.js +2798 -95
  26. package/dist/cli-delegate/cmd_doctor.js +2 -1
  27. package/dist/cli-delegate/cmd_export.js +2 -2
  28. package/dist/cli-delegate/cmd_handoff.js +86 -6
  29. package/dist/cli-delegate/cmd_prune.js +2 -2
  30. package/dist/cli-delegate/cmd_route_audit.js +2 -1
  31. package/dist/cli-delegate/cmd_session_recycle.js +177 -0
  32. package/dist/cli-delegate/cmd_settings_get.js +1 -1
  33. package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
  34. package/dist/cli-delegate/cmd_settings_set.js +2 -2
  35. package/dist/cli-delegate/cmd_sync.js +2 -2
  36. package/dist/cli-delegate/cmd_uninstall.js +2 -2
  37. package/dist/cli-delegate/cmd_validate.js +2 -2
  38. package/dist/discovery/deprecation-report.md +1 -1
  39. package/dist/discovery/discovery-manifest.json +7 -7
  40. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  41. package/dist/discovery/discovery-manifest.summary.md +1 -1
  42. package/dist/discovery/orphan-report.md +1 -1
  43. package/dist/discovery/packs.json +2 -2
  44. package/dist/discovery/trust-report.md +1 -1
  45. package/dist/discovery/workspaces.json +2 -2
  46. package/dist/hooks/dispatch.js +1904 -575
  47. package/dist/install/install.mjs +14 -0
  48. package/dist/mcp/registry-manifest.json +2 -2
  49. package/dist/mcp/server.json +2 -2
  50. package/dist/mcp/server.mjs +3760 -726
  51. package/dist/server/schemas/settings.js +6 -0
  52. package/dist/server/schemas/settings.js.map +1 -1
  53. package/dist/ui/assets/index-B5qGYOJo.js +26 -0
  54. package/dist/ui/assets/index-B5qGYOJo.js.map +1 -0
  55. package/dist/ui/index.html +1 -1
  56. package/docs/contracts/settings-classes.md +21 -5
  57. package/package.json +1 -1
  58. package/src/agent-src/scripts/roadmap_gates.ts +312 -0
  59. package/src/agent-src/scripts/update_roadmap_progress.ts +72 -4
  60. package/src/config/agent-settings.template.yml +36 -0
  61. package/src/config/dispatch-economy-metrics.json +61 -0
  62. package/src/config/evaluator-budgets.json +4 -4
  63. package/src/config/hook-token-budget.json +91 -0
  64. package/src/config/recycle-threshold-budget.json +12 -0
  65. package/src/config/rule-enforcement-baseline.json +86 -0
  66. package/src/scripts/_cli/cmd_conformance.ts +17 -2
  67. package/src/scripts/_cli/cmd_session_recycle.ts +226 -0
  68. package/src/scripts/_dispatch.bash +23 -0
  69. package/src/scripts/_lib/judgment_ladder.ts +254 -2
  70. package/src/scripts/_lib/orchestration_record.ts +41 -10
  71. package/src/scripts/_lib/recycle_envelope_paths.ts +35 -0
  72. package/src/scripts/_lib/self_repair.ts +194 -0
  73. package/src/scripts/_lib/session_eol.ts +230 -0
  74. package/src/scripts/_lib/session_role.ts +42 -0
  75. package/src/scripts/_lib/subagent_capsule.ts +159 -0
  76. package/src/scripts/_lib/subagent_response.ts +75 -0
  77. package/src/scripts/ai_council/clients.ts +50 -1
  78. package/src/scripts/ask_transport.ts +221 -0
  79. package/src/scripts/bench_hook_injection.ts +234 -0
  80. package/src/scripts/check_adr_frontmatter.ts +84 -40
  81. package/src/scripts/check_branch_freshness.ts +1 -0
  82. package/src/scripts/check_corpus_staleness.ts +18 -4
  83. package/src/scripts/check_detector_corpus.ts +17 -0
  84. package/src/scripts/check_gitignore_freshness.ts +42 -8
  85. package/src/scripts/check_hook_bundle_freshness.ts +20 -0
  86. package/src/scripts/check_kernel_prefix_stability.ts +1 -0
  87. package/src/scripts/check_standing_rule_delivery.ts +73 -32
  88. package/src/scripts/check_static_layer_stability.ts +154 -0
  89. package/src/scripts/check_suppression_hygiene.ts +48 -0
  90. package/src/scripts/dispatch_economy_report.ts +448 -0
  91. package/src/scripts/dispatch_r2_reviewer.ts +15 -2
  92. package/src/scripts/explain_run.ts +206 -4
  93. package/src/scripts/generate_settings_reference.ts +1 -0
  94. package/src/scripts/handoff_context_hook.ts +112 -4
  95. package/src/scripts/hook_manifest.yaml +117 -10
  96. package/src/scripts/hooks/concern_registry.ts +8 -0
  97. package/src/scripts/hooks/delegation_nudge_hook.ts +40 -3
  98. package/src/scripts/hooks/dispatch_hook.ts +39 -2
  99. package/src/scripts/hooks/edit_shape_hook.ts +303 -0
  100. package/src/scripts/hooks/orchestration_record_hook.ts +19 -1
  101. package/src/scripts/hooks/reread_guard_hook.ts +314 -0
  102. package/src/scripts/hooks/rtk_wrap_hook.ts +133 -14
  103. package/src/scripts/hooks/session_eol_hook.ts +268 -0
  104. package/src/scripts/hooks/turn_end_gate_hook.ts +507 -0
  105. package/src/scripts/install-hooks.sh +1 -1
  106. package/src/scripts/language_mirror_hook.ts +89 -0
  107. package/src/scripts/lint_decision_impact.ts +14 -1
  108. package/src/scripts/lint_hook_manifest.ts +82 -0
  109. package/src/scripts/lint_readme_serial_comma.ts +1 -0
  110. package/src/scripts/lint_rule_enforcement_declaration.ts +347 -0
  111. package/src/scripts/measure_turn_end_gate.ts +194 -0
  112. package/src/scripts/orchestration_record.ts +4 -1
  113. package/src/scripts/rebuild_hook_bundle.ts +1 -0
  114. package/src/scripts/report_conformance_funnel.ts +284 -0
  115. package/src/scripts/report_imperative_density.ts +1 -0
  116. package/src/scripts/routing_doctor.ts +97 -0
  117. package/src/scripts/self_repair_cli.ts +51 -3
  118. package/src/scripts/session_eol_report.ts +261 -0
  119. package/src/server/schemas/settings.ts +14 -0
  120. package/src/templates/compact-instructions.md +40 -0
  121. package/dist/ui/assets/index-JDAF00bC.js +0 -26
  122. package/dist/ui/assets/index-JDAF00bC.js.map +0 -1
@@ -1,4 +1,7 @@
1
1
  import { createRequire as __acCreateRequire } from 'node:module'; const require = globalThis.require ?? __acCreateRequire(import.meta.url);
2
+ import {
3
+ readHookStdin
4
+ } from "./chunk-MMJL4TIX.js";
2
5
  import {
3
6
  ArgparseExit,
4
7
  STATUS_SYMBOLS,
@@ -11,8 +14,11 @@ import {
11
14
  _run_checks,
12
15
  _run_checks_no_manifest,
13
16
  _scan_foreign
14
- } from "./chunk-RCIA67XN.js";
17
+ } from "./chunk-IYTGIG2Y.js";
15
18
  import "./chunk-FTAISGDQ.js";
19
+ import {
20
+ atomic_write_json
21
+ } from "./chunk-O3CO5BFB.js";
16
22
  import "./chunk-LVRYBKTI.js";
17
23
  import "./chunk-736JCWLM.js";
18
24
  import {
@@ -39,73 +45,2764 @@ import {
39
45
 
40
46
  // src/scripts/_cli/cmd_conformance.ts
41
47
  var YAML = __toESM(require_dist(), 1);
48
+ import * as fs12 from "node:fs";
49
+ import * as os6 from "node:os";
50
+ import * as path12 from "node:path";
51
+ import process5 from "node:process";
52
+ import { spawnSync } from "node:child_process";
53
+ import { fileURLToPath as fileURLToPath11, pathToFileURL as pathToFileURL11 } from "node:url";
54
+
55
+ // src/install/txlog.ts
56
+ import { createReadStream, createWriteStream, existsSync, readFileSync, renameSync, statSync, unlinkSync } from "node:fs";
57
+ var ROTATION_MAX_BYTES = 10 * 1024 * 1024;
58
+ var ROTATION_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
59
+ var RECOVERY_DEPTH_CAP = 500;
60
+ function readRecentEntries(logPath) {
61
+ if (!existsSync(logPath)) {
62
+ return [];
63
+ }
64
+ const raw = readFileSync(logPath, "utf8");
65
+ const lines = raw.split("\n").filter((l) => l.length > 0);
66
+ const tail = lines.slice(-RECOVERY_DEPTH_CAP);
67
+ const out = [];
68
+ for (const line of tail) {
69
+ const parsed = tryParseEntry(line);
70
+ if (parsed !== null) {
71
+ out.push(parsed);
72
+ }
73
+ }
74
+ return out;
75
+ }
76
+ function tryParseEntry(line) {
77
+ try {
78
+ const obj = JSON.parse(line);
79
+ if (typeof obj.ts !== "string" || typeof obj.kind !== "string" || typeof obj.path !== "string") {
80
+ return null;
81
+ }
82
+ return obj;
83
+ } catch {
84
+ return null;
85
+ }
86
+ }
87
+
88
+ // src/scripts/router_target_paths.mjs
89
+ var ROUTE_TARGET_TEMPLATES = Object.freeze({
90
+ skill: Object.freeze(["dist/agent-src/skills/{id}/SKILL.md"]),
91
+ command: Object.freeze(["dist/agent-src/commands/{id}.md"]),
92
+ guideline: Object.freeze(["docs/guidelines/{id}.md"]),
93
+ contract: Object.freeze([
94
+ "docs/contracts/{id}.md",
95
+ "dist/agent-src/contexts/contracts/{id}.md"
96
+ ])
97
+ });
98
+ function parseRouteTarget(target) {
99
+ const sep = target.indexOf(":");
100
+ if (sep <= 0) return null;
101
+ const kind = target.slice(0, sep);
102
+ const id = target.slice(sep + 1);
103
+ if (!id) return null;
104
+ if (!Object.prototype.hasOwnProperty.call(ROUTE_TARGET_TEMPLATES, kind)) return null;
105
+ return { kind, id };
106
+ }
107
+ function routeTargetPathsPosix(target) {
108
+ const parsed = parseRouteTarget(target);
109
+ if (parsed === null) return [];
110
+ return ROUTE_TARGET_TEMPLATES[parsed.kind].map((tpl) => tpl.replace("{id}", parsed.id));
111
+ }
112
+
113
+ // src/scripts/report_conformance_funnel.ts
114
+ import * as fs11 from "node:fs";
115
+ import * as os5 from "node:os";
116
+ import * as path11 from "node:path";
117
+ import process4 from "node:process";
118
+ import { fileURLToPath as fileURLToPath10, pathToFileURL as pathToFileURL10 } from "node:url";
119
+
120
+ // src/scripts/conformance_scan.ts
121
+ import { createHash } from "node:crypto";
122
+ import fs7 from "node:fs";
123
+ import os from "node:os";
124
+ import path7 from "node:path";
125
+ import { fileURLToPath as fileURLToPath6, pathToFileURL as pathToFileURL6 } from "node:url";
126
+
127
+ // src/scripts/_lib/carrier_divergence.ts
42
128
  import * as fs from "node:fs";
43
- import * as os from "node:os";
44
129
  import * as path from "node:path";
45
- import process from "node:process";
46
- import { spawnSync } from "node:child_process";
130
+ var OWNERSHIP_KEYS = ["package", "source_path"];
131
+ function stripOwnershipKeys(text) {
132
+ return text.split("\n").filter((line) => !OWNERSHIP_KEYS.some((k) => line.startsWith(`${k}:`))).join("\n");
133
+ }
134
+ function comparePair(a, b) {
135
+ if (a.equals(b)) {
136
+ return "identical";
137
+ }
138
+ if (stripOwnershipKeys(a.toString("utf-8")) === stripOwnershipKeys(b.toString("utf-8"))) {
139
+ return "provenance-only";
140
+ }
141
+ return "body-diff";
142
+ }
143
+ function censusRuleDir(dir) {
144
+ let files = 0;
145
+ let chars = 0;
146
+ let entries;
147
+ try {
148
+ entries = fs.readdirSync(dir);
149
+ } catch {
150
+ return { files: 0, chars: 0 };
151
+ }
152
+ for (const name of entries) {
153
+ if (!name.endsWith(".md")) continue;
154
+ try {
155
+ chars += fs.statSync(path.join(dir, name)).size;
156
+ files += 1;
157
+ } catch {
158
+ }
159
+ }
160
+ return { files, chars };
161
+ }
162
+
163
+ // src/scripts/_lib/transcript_entry.ts
164
+ function _isObject(v) {
165
+ return typeof v === "object" && v !== null && !Array.isArray(v);
166
+ }
167
+ function isSidechain(entry) {
168
+ return entry["isSidechain"] === true;
169
+ }
170
+ function entryText(entry) {
171
+ const msg = entry["message"];
172
+ const content = _isObject(msg) ? msg["content"] : void 0;
173
+ if (typeof content === "string") {
174
+ return content;
175
+ }
176
+ if (!Array.isArray(content)) {
177
+ return "";
178
+ }
179
+ return content.filter((b) => _isObject(b) && b["type"] === "text").map((b) => String(b["text"] ?? "")).join("\n");
180
+ }
181
+ function toolUses(entry) {
182
+ const msg = entry["message"];
183
+ const content = _isObject(msg) ? msg["content"] : void 0;
184
+ if (!Array.isArray(content)) {
185
+ return [];
186
+ }
187
+ const out = [];
188
+ for (const part of content) {
189
+ if (!_isObject(part) || part["type"] !== "tool_use") {
190
+ continue;
191
+ }
192
+ out.push({
193
+ name: typeof part["name"] === "string" ? part["name"] : "",
194
+ input: _isObject(part["input"]) ? part["input"] : {}
195
+ });
196
+ }
197
+ return out;
198
+ }
199
+
200
+ // src/scripts/language_mirror_hook.ts
201
+ import fs2 from "node:fs";
202
+ import path2 from "node:path";
47
203
  import { fileURLToPath, pathToFileURL } from "node:url";
48
204
 
49
- // src/install/txlog.ts
50
- import { createReadStream, createWriteStream, existsSync, readFileSync, renameSync, statSync, unlinkSync } from "node:fs";
51
- var ROTATION_MAX_BYTES = 10 * 1024 * 1024;
52
- var ROTATION_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
53
- var RECOVERY_DEPTH_CAP = 500;
54
- function readRecentEntries(logPath) {
55
- if (!existsSync(logPath)) {
205
+ // src/scripts/_lib/prompt_shape.ts
206
+ function isSyntheticPrompt(prompt) {
207
+ const head = prompt.trimStart().slice(0, 400);
208
+ return head.startsWith("[SYSTEM NOTIFICATION - NOT USER INPUT]") || head.startsWith("<task-notification>") || head.startsWith("<system-reminder>") || head.startsWith("<local-command-caveat>") || /^\[SYSTEM NOTIFICATION\b/.test(head);
209
+ }
210
+ var DOCUMENT_HEAD = /^\s*(#{1,6}\s|-{3,}\s*$|={3,}\s*$|<[A-Za-z][\w-]*>\s*$)/;
211
+ function humanAuthoredLead(text) {
212
+ const kept = [];
213
+ for (const line of text.split("\n")) {
214
+ if (DOCUMENT_HEAD.test(line)) {
215
+ break;
216
+ }
217
+ kept.push(line);
218
+ }
219
+ return kept.join("\n");
220
+ }
221
+
222
+ // src/scripts/language_mirror_hook.ts
223
+ var EXIT_ALLOW = 0;
224
+ var EXIT_WARN = 2;
225
+ var STATE_FILE = path2.join("agents", "state", "language-mirror.json");
226
+ var PIN_LOST_MARKER = path2.join("agents", "state", "language-mirror.pin-lost");
227
+ function _isObject2(v) {
228
+ return typeof v === "object" && v !== null && !Array.isArray(v);
229
+ }
230
+ var DE_MARKERS = /\b(der|die|das|den|dem|und|ist|sind|nicht|kein|keine|mach|mache|bitte|warum|wieso|weshalb|weiter|kannst|sollst|soll|musst|muss|hast|habe|haben|wir|ich|du|dann|noch|auch|schon|wenn|aber|alle|mit|ohne|damit|dass|wurde|werden|gibt|nochmal|erstell|erstelle|erstelle|fehler|regeln|arbeite|analysiere|fixe|nimm|lege|prüfe|pruefe|schau|zeig|mir|dir|sich|eine|einen|einem|einer|vom|zum|zur|beim|im)\b|[äöüÄÖÜß]/gi;
231
+ var EN_MARKERS = /\b(the|and|is|are|was|were|not|please|why|because|should|would|could|there|these|those|with|from|about|which|that|this|will|have|has|been|they|them|their|your|our|what|when|where|make|need|want|does|doesn't|don't|it's|let's)\b/gi;
232
+ var MIN_MARKERS = 2;
233
+ function instructionText(prompt) {
234
+ const OUTPUT_HEAD = /^\s*(To\s+\S+|remote:|error:|fatal:|hint:|warning:|npm ERR!|Error:|Traceback|Exception|[A-Za-z]*Error\b|at\s+\S+:\d+|\s{4,}\S|[-+]{3}\s|@@\s|\$\s|>\s|\||\d+\s*\|)/i;
235
+ const kept = [];
236
+ let inPaste = false;
237
+ for (const line of prompt.replace(/```[\s\S]*?```/g, "\n\n").split("\n")) {
238
+ if (!line.trim()) {
239
+ inPaste = false;
240
+ kept.push(line);
241
+ continue;
242
+ }
243
+ if (OUTPUT_HEAD.test(line)) {
244
+ inPaste = true;
245
+ continue;
246
+ }
247
+ if (inPaste) {
248
+ continue;
249
+ }
250
+ kept.push(line);
251
+ }
252
+ return kept.join("\n");
253
+ }
254
+ function classify(prompt) {
255
+ const text = instructionText(prompt);
256
+ const lead = humanAuthoredLead(text);
257
+ const leadVerdict = _score(lead);
258
+ if (leadVerdict.language !== "und") {
259
+ return leadVerdict;
260
+ }
261
+ return _score(text);
262
+ }
263
+ function _score(text) {
264
+ const de = (text.match(DE_MARKERS) ?? []).length;
265
+ const en = (text.match(EN_MARKERS) ?? []).length;
266
+ if (de < MIN_MARKERS && en < MIN_MARKERS) {
267
+ return { language: "und", de_markers: de, en_markers: en };
268
+ }
269
+ return { language: de >= en ? "de" : "en", de_markers: de, en_markers: en };
270
+ }
271
+ var LANGUAGE_NAME = {
272
+ de: "German (Deutsch)",
273
+ en: "English"
274
+ };
275
+ var LOCALE_ENV_KEYS = ["LC_ALL", "LC_MESSAGES", "LANG", "LANGUAGE"];
276
+ function systemLocaleVerdict(env = process.env) {
277
+ for (const key of LOCALE_ENV_KEYS) {
278
+ const raw = (env[key] ?? "").trim();
279
+ if (!raw) {
280
+ continue;
281
+ }
282
+ const first = raw.split(":")[0] ?? "";
283
+ const primary = (first.split(/[._@-]/)[0] ?? "").toLowerCase();
284
+ if (primary === "c" || primary === "posix" || !primary) {
285
+ continue;
286
+ }
287
+ if (primary === "de") {
288
+ return "de";
289
+ }
290
+ if (primary === "en") {
291
+ return "en";
292
+ }
293
+ return "und";
294
+ }
295
+ return "und";
296
+ }
297
+ function noticeText(language, source) {
298
+ const name = LANGUAGE_NAME[language];
299
+ if (source === "system-locale") {
300
+ return `Reply language for this turn: ${name} \u2014 the prompt carried no language markers, so this fell back to the system locale. Write in another language and the next turn follows the prompt instead.`;
301
+ }
302
+ return `Reply language for this turn: ${name}.`;
303
+ }
304
+ function pinText(language, source = "prompt") {
305
+ const name = LANGUAGE_NAME[language];
306
+ if (source === "system-locale") {
307
+ return `<language-pin>
308
+ No language markers were found in this turn's prompt and no language was pinned yet, so the mirror target falls back to the environment's locale: ${name}. Use it for EVERY user-visible token this turn.
309
+
310
+ This is the WEAKER of the two provenances \u2014 it reflects the machine, not the user's own words. The first prompt that carries real markers replaces it. Do not treat it as a standing preference, and do not argue with the user about it if they write in something else.
311
+ </language-pin>`;
312
+ }
313
+ return `<language-pin>
314
+ The user submitted this turn's prompt in ${name}. That is the mirror target for EVERY user-visible token of your replies this turn \u2014 including the short inter-tool lines between tool calls, headings, table cells, bullets, status lines, and the recommendation label under any numbered-options block.
315
+
316
+ This pin exists because the trigger is not observable from the transcript alone: slash-command and skill bodies arrive in the user role and can be long and English, which is what drove the 626 wrong-language turns measured in the 30-session audit this hook was built from. Tool output, file contents, and an injected skill body are NOT the trigger \u2014 this pin is.
317
+ </language-pin>`;
318
+ }
319
+ function _loadState(target) {
320
+ try {
321
+ const raw = fs2.readFileSync(target, "utf8");
322
+ const decoded = JSON.parse(raw);
323
+ return _isObject2(decoded) ? decoded : {};
324
+ } catch {
325
+ return {};
326
+ }
327
+ }
328
+ function nextState(previous, classification, prompt_chars, session_id, now, locale = "und") {
329
+ if (classification.language === "und") {
330
+ if (previous.language === "de" || previous.language === "en") {
331
+ return null;
332
+ }
333
+ if (locale === "und") {
334
+ return null;
335
+ }
336
+ return {
337
+ language: locale,
338
+ source: "system-locale",
339
+ detected_at: now,
340
+ prompt_chars,
341
+ de_markers: classification.de_markers,
342
+ en_markers: classification.en_markers,
343
+ session_id
344
+ };
345
+ }
346
+ return {
347
+ language: classification.language,
348
+ source: "prompt",
349
+ detected_at: now,
350
+ prompt_chars,
351
+ de_markers: classification.de_markers,
352
+ en_markers: classification.en_markers,
353
+ session_id
354
+ };
355
+ }
356
+ function _extractPrompt(payload) {
357
+ for (const key of ["prompt", "userPrompt", "user_prompt", "message", "text"]) {
358
+ const v = payload[key];
359
+ if (typeof v === "string" && v.trim()) {
360
+ return v;
361
+ }
362
+ }
363
+ return "";
364
+ }
365
+ function _setPinLost(consumer_root) {
366
+ try {
367
+ const target = path2.join(consumer_root, PIN_LOST_MARKER);
368
+ fs2.mkdirSync(path2.dirname(target), { recursive: true });
369
+ fs2.writeFileSync(target, (/* @__PURE__ */ new Date()).toISOString());
370
+ } catch {
371
+ }
372
+ }
373
+ function _pinLost(consumer_root) {
374
+ try {
375
+ return fs2.existsSync(path2.join(consumer_root, PIN_LOST_MARKER));
376
+ } catch {
377
+ return false;
378
+ }
379
+ }
380
+ function _clearPinLost(consumer_root) {
381
+ try {
382
+ fs2.rmSync(path2.join(consumer_root, PIN_LOST_MARKER), { force: true });
383
+ } catch {
384
+ }
385
+ }
386
+ function _reEmitAfterCompaction(consumer_root) {
387
+ if (!_pinLost(consumer_root)) {
388
+ return EXIT_ALLOW;
389
+ }
390
+ const previous = _loadState(path2.join(consumer_root, STATE_FILE));
391
+ const language = previous.language;
392
+ const source = previous.source === "system-locale" ? "system-locale" : "prompt";
393
+ _clearPinLost(consumer_root);
394
+ if (language !== "de" && language !== "en") {
395
+ return EXIT_ALLOW;
396
+ }
397
+ process.stdout.write(
398
+ `${JSON.stringify({
399
+ decision: "warn",
400
+ reason: noticeText(language, source),
401
+ additional_context: pinText(language, source)
402
+ })}
403
+ `
404
+ );
405
+ return EXIT_WARN;
406
+ }
407
+ function run(stdin_text, options) {
408
+ let envelope = {};
409
+ if (stdin_text.trim()) {
410
+ try {
411
+ const decoded = JSON.parse(stdin_text);
412
+ if (_isObject2(decoded)) {
413
+ envelope = decoded;
414
+ }
415
+ } catch {
416
+ return EXIT_ALLOW;
417
+ }
418
+ }
419
+ const payload = _isObject2(envelope["payload"]) ? envelope["payload"] : envelope;
420
+ const event = typeof envelope["event"] === "string" ? envelope["event"] : "";
421
+ if (event === "pre_compact") {
422
+ _setPinLost(options.consumer_root);
423
+ return EXIT_ALLOW;
424
+ }
425
+ if (event === "post_tool_use") {
426
+ return _reEmitAfterCompaction(options.consumer_root);
427
+ }
428
+ const prompt = _extractPrompt(payload);
429
+ if (!prompt) {
430
+ return EXIT_ALLOW;
431
+ }
432
+ if (isSyntheticPrompt(prompt)) {
433
+ return EXIT_ALLOW;
434
+ }
435
+ const session_id = typeof envelope["session_id"] === "string" ? envelope["session_id"] : "";
436
+ const target = path2.join(options.consumer_root, STATE_FILE);
437
+ const previous = _loadState(target);
438
+ const classification = classify(prompt);
439
+ const next = nextState(
440
+ previous,
441
+ classification,
442
+ prompt.length,
443
+ session_id,
444
+ (/* @__PURE__ */ new Date()).toISOString(),
445
+ // Injectable so the suite can pin a locale instead of inheriting the
446
+ // machine's — a test whose verdict depends on the developer's `LANG` is a
447
+ // test that passes here and fails in CI for reasons unrelated to the diff.
448
+ systemLocaleVerdict(options.env ?? process.env)
449
+ );
450
+ const effective = next?.language ?? previous.language;
451
+ const effectiveSource = next?.source ?? (previous.source === "system-locale" ? "system-locale" : "prompt");
452
+ if (next) {
453
+ try {
454
+ atomic_write_json(target, next);
455
+ } catch {
456
+ }
457
+ }
458
+ if (effective === "de" || effective === "en") {
459
+ process.stdout.write(
460
+ `${JSON.stringify({ decision: "warn", reason: noticeText(effective, effectiveSource), additional_context: pinText(effective, effectiveSource) })}
461
+ `
462
+ );
463
+ return EXIT_WARN;
464
+ }
465
+ return EXIT_ALLOW;
466
+ }
467
+ function main(argv) {
468
+ const args = argv ?? process.argv.slice(2);
469
+ let consumer_root = process.cwd();
470
+ for (let i = 0; i < args.length; i += 1) {
471
+ const a = args[i];
472
+ if (a === "--project-dir" && args[i + 1] !== void 0) {
473
+ consumer_root = args[i + 1];
474
+ i += 1;
475
+ } else if (a !== void 0 && a.startsWith("--project-dir=")) {
476
+ consumer_root = a.slice("--project-dir=".length);
477
+ }
478
+ }
479
+ return run(readHookStdin(), { consumer_root });
480
+ }
481
+ function _isCliEntry() {
482
+ if (true) {
483
+ return false;
484
+ }
485
+ if (process.argv[1] === void 0) {
486
+ return false;
487
+ }
488
+ const argvUrl = pathToFileURL(path2.resolve(process.argv[1])).href;
489
+ if (import.meta.url === argvUrl) {
490
+ return true;
491
+ }
492
+ try {
493
+ const here = fs2.realpathSync(fileURLToPath(import.meta.url));
494
+ const argv = fs2.realpathSync(path2.resolve(process.argv[1]));
495
+ return here === argv;
496
+ } catch {
497
+ return false;
498
+ }
499
+ }
500
+ if (_isCliEntry()) {
501
+ process.exit(main());
502
+ }
503
+
504
+ // src/scripts/git_authorization_hook.ts
505
+ import fs3 from "node:fs";
506
+ import path3 from "node:path";
507
+ import { fileURLToPath as fileURLToPath2, pathToFileURL as pathToFileURL2 } from "node:url";
508
+ var EXIT_ALLOW2 = 0;
509
+ var STATE_FILE2 = path3.join("agents", "state", "git-authorization.json");
510
+ function _isObject3(v) {
511
+ return typeof v === "object" && v !== null && !Array.isArray(v);
512
+ }
513
+ var PHRASES = [
514
+ { op: "commit", re: /\b(commit(e|et|te|ten)?|committe|einchecken)\b/i },
515
+ { op: "push", re: /\b(push(e|en|t)?|hochladen|hochschieben|raufschieben)\b/i },
516
+ { op: "branch", re: /\b(branch|feature-branch|zweig)\b/i },
517
+ // A creation verb is required — "schau dir den PR an" is not "open a PR".
518
+ {
519
+ op: "pr-create",
520
+ re: /\b(erstell(e|en)?|mach(e|en)?|leg(e|en)?\s+an|(er)?(ö|oe)ffne|open|create|raise|aufmachen)\b[^.\n]{0,30}\b(pr|pull[- ]request)\b|\b(pr|pull[- ]request)\b[^.\n]{0,20}\b(erstellen|aufmachen|anlegen|(er)?(ö|oe)ffnen)\b/i
521
+ },
522
+ // `merge` as an ACTION, never as the noun in "merge conflict" / "merge commit".
523
+ { op: "pr-merge", re: /\b(merge|merg(e|en|st|t)|zusammenf(ü|ue)hren|reinmergen)\b(?!\s*[- ]?(conflict|konflikt|commit|base|queue|state|status))/i },
524
+ // A tag is an ACTION here — bare "Tag" is the German word for day, and
525
+ // "Version" is an ordinary noun. Both authorized a BLOCK op before this.
526
+ { op: "tag", re: /\b(tagge(n|st)?|tag\s+(setzen|anlegen|erstellen)|git\s+tag|--tags|--follow-tags)\b/i },
527
+ // "die release notes sind falsch" is a noun phrase, not an authorization.
528
+ {
529
+ op: "release",
530
+ re: /\b(releasen?|ver(ö|oe)ffentlich(e|en))\b(?!\s*[- ]?(notes?|branch|candidate|pr\b|datum|date|zweig))/i
531
+ },
532
+ { op: "publish", re: /\b(publish(e|en)?|publiziere[n]?)\b/i }
533
+ ];
534
+ var OUTPUT_LINE = /^\s*(To\s+\S+|remote:|error:|fatal:|hint:|warning:|!\s|\?\?\s|\s*\^|[-+]{3}\s|@@\s|\$\s|>\s|\d+\s+(pass|fail)|npm ERR!|Error:|Traceback|at\s+\S+:\d+)/i;
535
+ function isInterrogative(prose) {
536
+ const t = prose.trim();
537
+ if (!t) {
538
+ return false;
539
+ }
540
+ const hasImperative = /\b(mach|mache|bitte|leg|lege|erstell|erstelle|f(ü|ue)hr|f(ü|ue)hre|setz|setze|starte|los|jetzt|go ahead|do it|ja[,.]?\s|ok[,.]?\s)\b/i.test(
541
+ t
542
+ );
543
+ if (hasImperative) {
544
+ return false;
545
+ }
546
+ return /\?\s*$/.test(t) || /^(was|wie|warum|wieso|weshalb|wann|wer|welche[rs]?|wo|ist|sind|kann|kannst|k(ö|oe)nnen|soll|sollen|darf|d(ü|ue)rfen|why|what|how|when|which|who|is|are|can|could|should|does|do)\b/i.test(
547
+ t
548
+ );
549
+ }
550
+ var PASTED_COMMANDS = [
551
+ { op: "publish", re: /\bnpm\s+publish\b/i },
552
+ { op: "tag", re: /\bgit\s+push\s+[^\n]*--tags\b|\bgit\s+tag\s+-a\b/i },
553
+ { op: "release", re: /\bgh\s+release\s+create\b/i },
554
+ { op: "pr-merge", re: /\bgh\s+pr\s+merge\b/i },
555
+ { op: "pr-create", re: /\bgh\s+pr\s+create\b/i },
556
+ { op: "push", re: /\bgit\s+push\b/i },
557
+ { op: "commit", re: /\bgit\s+commit\b/i },
558
+ { op: "branch", re: /\bgit\s+(checkout\s+-b|switch\s+-c|branch)\b/i }
559
+ ];
560
+ function splitFences(prompt) {
561
+ const fences = [];
562
+ const prose = prompt.replace(/```[^\n]*\n([\s\S]*?)```/g, (_m, body) => {
563
+ fences.push(body);
564
+ return " ";
565
+ });
566
+ return { prose, fences };
567
+ }
568
+ function _commandLines(fence) {
569
+ return fence.split("\n").map((l) => l.trim()).filter((l) => l.length > 0).filter((l) => !/^(remote:|error:|hint:|fatal:|warning:|To |!\s|\s*\^)/i.test(l)).map((l) => l.replace(/^[$>#]\s*/, "")).filter((l) => /^(git|gh|npm|pnpm|yarn|task)\b/.test(l));
570
+ }
571
+ function classifyAuthorization(prompt) {
572
+ const { prose, fences } = splitFences(prompt);
573
+ const authorized = /* @__PURE__ */ new Set();
574
+ const evidence = {};
575
+ const instruction = prose.split("\n").filter((l) => !OUTPUT_LINE.test(l)).join("\n");
576
+ for (const { op, re } of isInterrogative(instruction) ? [] : PHRASES) {
577
+ const m = re.exec(instruction);
578
+ if (m) {
579
+ authorized.add(op);
580
+ evidence[op] = `prose: "${m[0]}"`;
581
+ }
582
+ }
583
+ for (const fence of fences) {
584
+ if (fence.split("\n").some((l) => OUTPUT_LINE.test(l))) {
585
+ continue;
586
+ }
587
+ for (const line of _commandLines(fence)) {
588
+ for (const { op, re } of PASTED_COMMANDS) {
589
+ if (re.test(line)) {
590
+ authorized.add(op);
591
+ evidence[op] = `pasted command: "${line.slice(0, 80)}"`;
592
+ }
593
+ }
594
+ }
595
+ }
596
+ return { authorized: [...authorized], evidence };
597
+ }
598
+ function run2(stdin_text, options) {
599
+ let envelope = {};
600
+ if (stdin_text.trim()) {
601
+ try {
602
+ const decoded = JSON.parse(stdin_text);
603
+ if (_isObject3(decoded)) {
604
+ envelope = decoded;
605
+ }
606
+ } catch {
607
+ return EXIT_ALLOW2;
608
+ }
609
+ }
610
+ const payload = _isObject3(envelope["payload"]) ? envelope["payload"] : envelope;
611
+ let prompt = "";
612
+ for (const key of ["prompt", "userPrompt", "user_prompt", "message", "text"]) {
613
+ const v = payload[key];
614
+ if (typeof v === "string" && v.trim()) {
615
+ prompt = v;
616
+ break;
617
+ }
618
+ }
619
+ if (!prompt) {
620
+ return EXIT_ALLOW2;
621
+ }
622
+ const { authorized, evidence } = classifyAuthorization(prompt);
623
+ const ledger = {
624
+ session_id: typeof envelope["session_id"] === "string" ? envelope["session_id"] : "",
625
+ detected_at: (/* @__PURE__ */ new Date()).toISOString(),
626
+ authorized,
627
+ evidence,
628
+ prompt_chars: prompt.length
629
+ };
630
+ try {
631
+ atomic_write_json(path3.join(options.consumer_root, STATE_FILE2), ledger);
632
+ } catch {
633
+ }
634
+ return EXIT_ALLOW2;
635
+ }
636
+ function main2(argv) {
637
+ const args = argv ?? process.argv.slice(2);
638
+ let consumer_root = process.cwd();
639
+ for (let i = 0; i < args.length; i += 1) {
640
+ const a = args[i];
641
+ if (a === "--project-dir" && args[i + 1] !== void 0) {
642
+ consumer_root = args[i + 1];
643
+ i += 1;
644
+ } else if (a !== void 0 && a.startsWith("--project-dir=")) {
645
+ consumer_root = a.slice("--project-dir=".length);
646
+ }
647
+ }
648
+ return run2(readHookStdin(), { consumer_root });
649
+ }
650
+ function _isCliEntry2() {
651
+ if (true) {
652
+ return false;
653
+ }
654
+ if (process.argv[1] === void 0) {
655
+ return false;
656
+ }
657
+ const argvUrl = pathToFileURL2(path3.resolve(process.argv[1])).href;
658
+ if (import.meta.url === argvUrl) {
659
+ return true;
660
+ }
661
+ try {
662
+ const here = fs3.realpathSync(fileURLToPath2(import.meta.url));
663
+ const argv = fs3.realpathSync(path3.resolve(process.argv[1]));
664
+ return here === argv;
665
+ } catch {
666
+ return false;
667
+ }
668
+ }
669
+ if (_isCliEntry2()) {
670
+ process.exit(main2());
671
+ }
672
+
673
+ // src/scripts/hooks/block_unauthorized_git.ts
674
+ import fs4 from "node:fs";
675
+ import path4 from "node:path";
676
+ import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL3 } from "node:url";
677
+ var EXIT_ALLOW3 = 0;
678
+ var EXIT_BLOCK = 1;
679
+ function _isObject4(v) {
680
+ return typeof v === "object" && v !== null && !Array.isArray(v);
681
+ }
682
+ var BLOCK_OPS = /* @__PURE__ */ new Set([
683
+ "publish",
684
+ "tag",
685
+ "release",
686
+ "pr-merge"
687
+ ]);
688
+ var WARN_OPS = /* @__PURE__ */ new Set([
689
+ "commit",
690
+ "push",
691
+ "pr-create",
692
+ "branch"
693
+ ]);
694
+ var G = "(?:-[A-Za-z-]+(?:=\\S+)?(?:\\s+\\S+)?\\s+)*";
695
+ var COMMAND_OPS = [
696
+ { op: "publish", re: new RegExp(`\\b(npm|pnpm|yarn)\\s+${G}publish\\b`, "i") },
697
+ { op: "release", re: /\bgh\s+release\s+create\b|\bgh\s+api\b[^\n]*\/releases\b/i },
698
+ {
699
+ op: "tag",
700
+ re: new RegExp(
701
+ `\\bgit\\s+${G}push\\b[^\\n;|&]*(--tags|--follow-tags|refs\\/tags\\/)`,
702
+ "i"
703
+ )
704
+ },
705
+ { op: "pr-merge", re: /\bgh\s+pr\s+merge\b|\bgh\s+api\b[^\n]*\/pulls\/\d+\/merge\b/i },
706
+ { op: "pr-create", re: /\bgh\s+pr\s+create\b|\bgh\s+api\b[^\n]*-X\s+POST[^\n]*\/pulls\b/i },
707
+ { op: "push", re: new RegExp(`\\bgit\\s+${G}push\\b`, "i") },
708
+ { op: "commit", re: new RegExp(`\\bgit\\s+${G}commit\\b`, "i") },
709
+ { op: "branch", re: new RegExp(`\\bgit\\s+${G}(checkout\\s+-b|switch\\s+-c)\\b`, "i") }
710
+ ];
711
+ var COMMAND_TOOLS = /* @__PURE__ */ new Set([
712
+ "launch-process",
713
+ "launch_process",
714
+ "Bash",
715
+ "BashTool",
716
+ "run-process",
717
+ "runProcess",
718
+ "shell",
719
+ "execute_shell",
720
+ "RunShellCommand"
721
+ ]);
722
+ function extractCommand(envelope) {
723
+ const payload = _isObject4(envelope["payload"]) ? envelope["payload"] : envelope;
724
+ const toolRaw = payload["tool_name"] ?? payload["toolName"] ?? payload["tool"];
725
+ const tool = typeof toolRaw === "string" ? toolRaw : null;
726
+ if (tool !== null && !COMMAND_TOOLS.has(tool)) {
727
+ return null;
728
+ }
729
+ const input = _isObject4(payload["tool_input"]) ? payload["tool_input"] : _isObject4(payload["toolInput"]) ? payload["toolInput"] : payload;
730
+ for (const key of ["command", "cmd", "script"]) {
731
+ const v = input[key];
732
+ if (typeof v === "string" && v.trim()) {
733
+ return v;
734
+ }
735
+ }
736
+ return null;
737
+ }
738
+ function splitOutsideQuotes(command) {
739
+ const parts = [];
740
+ let cur = "";
741
+ let quote = null;
742
+ for (let i = 0; i < command.length; i += 1) {
743
+ const ch = command[i];
744
+ if (quote !== null) {
745
+ if (quote === "$'" && ch === "\\" && i + 1 < command.length) {
746
+ cur += ch + command[i + 1];
747
+ i += 1;
748
+ continue;
749
+ }
750
+ cur += ch;
751
+ if (ch === (quote === "$'" ? "'" : quote)) {
752
+ quote = null;
753
+ } else if (ch === "\\" && quote === '"' && i + 1 < command.length) {
754
+ cur += command[i + 1];
755
+ i += 1;
756
+ }
757
+ continue;
758
+ }
759
+ if (ch === "$" && (command[i + 1] === "'" || command[i + 1] === '"')) {
760
+ quote = command[i + 1] === "'" ? "$'" : '"';
761
+ cur += ch + command[i + 1];
762
+ i += 1;
763
+ continue;
764
+ }
765
+ if (ch === '"' || ch === "'") {
766
+ quote = ch;
767
+ cur += ch;
768
+ continue;
769
+ }
770
+ if (ch === "\\" && i + 1 < command.length) {
771
+ cur += ch + command[i + 1];
772
+ i += 1;
773
+ continue;
774
+ }
775
+ if (ch === "\n" || ch === ";" || ch === "|" || ch === "&") {
776
+ if ((ch === "|" || ch === "&") && command[i + 1] === ch) {
777
+ i += 1;
778
+ }
779
+ parts.push(cur);
780
+ cur = "";
781
+ continue;
782
+ }
783
+ cur += ch;
784
+ }
785
+ if (quote !== null) {
786
+ parts.push(...splitIgnoringQuotes(cur));
787
+ return parts;
788
+ }
789
+ parts.push(cur);
790
+ return parts;
791
+ }
792
+ function splitIgnoringQuotes(fragment) {
793
+ const parts = [];
794
+ let cur = "";
795
+ for (let i = 0; i < fragment.length; i += 1) {
796
+ const ch = fragment[i];
797
+ if (ch === "\\" && i + 1 < fragment.length) {
798
+ cur += ch + fragment[i + 1];
799
+ i += 1;
800
+ continue;
801
+ }
802
+ if (ch === "\n" || ch === ";" || ch === "|" || ch === "&") {
803
+ if ((ch === "|" || ch === "&") && fragment[i + 1] === ch) {
804
+ i += 1;
805
+ }
806
+ parts.push(cur);
807
+ cur = "";
808
+ continue;
809
+ }
810
+ cur += ch;
811
+ }
812
+ parts.push(cur);
813
+ return parts;
814
+ }
815
+ function substitutionPayloads(segment) {
816
+ const out = [];
817
+ for (let i = 0; i < segment.length; i += 1) {
818
+ const ch = segment[i];
819
+ if (ch === "\\") {
820
+ i += 1;
821
+ continue;
822
+ }
823
+ if ((ch === "$" || ch === "<" || ch === ">") && segment[i + 1] === "(") {
824
+ let open = 1;
825
+ let j = i + 2;
826
+ const start = j;
827
+ for (; j < segment.length && open > 0; j += 1) {
828
+ const c = segment[j];
829
+ if (c === "\\") {
830
+ j += 1;
831
+ } else if (c === "(") {
832
+ open += 1;
833
+ } else if (c === ")") {
834
+ open -= 1;
835
+ }
836
+ }
837
+ out.push(segment.slice(start, open === 0 ? j - 1 : segment.length));
838
+ i = j - 1;
839
+ continue;
840
+ }
841
+ if (ch === "`") {
842
+ const end = segment.indexOf("`", i + 1);
843
+ out.push(segment.slice(i + 1, end === -1 ? segment.length : end));
844
+ i = end === -1 ? segment.length : end;
845
+ }
846
+ }
847
+ return out;
848
+ }
849
+ function invokedSegments(command, depth = 0) {
850
+ if (depth > 3) {
851
+ return [];
852
+ }
853
+ const out = [];
854
+ const withoutHeredocs = command.replace(
855
+ /<<-?\s*(['"]?)([A-Za-z_][A-Za-z0-9_]*)\1[\s\S]*?^\s*\2\s*$/gm,
856
+ "<<HEREDOC"
857
+ );
858
+ for (const raw of splitOutsideQuotes(withoutHeredocs)) {
859
+ let seg = raw.trim();
860
+ if (!seg) {
861
+ continue;
862
+ }
863
+ seg = seg.replace(/^(?:[A-Za-z_][A-Za-z0-9_]*=(?:"[^"]*"|'[^']*'|\S*)\s+)+/, "");
864
+ for (const payload of substitutionPayloads(seg)) {
865
+ out.push(...invokedSegments(payload, depth + 1));
866
+ }
867
+ const wrapped = /^(?:\S*\/)?(?:ba|z|k)?sh\s+-[a-z]*c\s+(?:"([^"]*)"|'([^']*)'|(\S+))/.exec(seg);
868
+ if (wrapped) {
869
+ out.push(...invokedSegments(wrapped[1] ?? wrapped[2] ?? wrapped[3] ?? "", depth + 1));
870
+ continue;
871
+ }
872
+ const evaled = /^eval\s+(?:"([^"]*)"|'([^']*)'|(.+))$/.exec(seg);
873
+ if (evaled) {
874
+ out.push(...invokedSegments(evaled[1] ?? evaled[2] ?? evaled[3] ?? "", depth + 1));
875
+ continue;
876
+ }
877
+ out.push(seg.replace(/^(\S+)/, (w) => w.replace(/['"]/g, "")));
878
+ }
879
+ return out;
880
+ }
881
+ function commandOp(command) {
882
+ let found = null;
883
+ for (const seg of invokedSegments(command)) {
884
+ if (!/^(?:\S*\/)?(git|gh|npm|pnpm|yarn)\b/.test(seg)) {
885
+ continue;
886
+ }
887
+ for (const { op, re } of COMMAND_OPS) {
888
+ if (re.test(seg)) {
889
+ if (BLOCK_OPS.has(op)) {
890
+ return op;
891
+ }
892
+ found ??= op;
893
+ break;
894
+ }
895
+ }
896
+ }
897
+ return found;
898
+ }
899
+ var LEDGER_MAX_AGE_MS = 30 * 60 * 1e3;
900
+ function _loadLedger(consumer_root, session_id, now) {
901
+ try {
902
+ const raw = fs4.readFileSync(path4.join(consumer_root, STATE_FILE2), "utf8");
903
+ const decoded = JSON.parse(raw);
904
+ if (_isObject4(decoded) && Array.isArray(decoded["authorized"])) {
905
+ const ledgerSession = typeof decoded["session_id"] === "string" ? decoded["session_id"] : "";
906
+ if (session_id && ledgerSession && ledgerSession !== session_id) {
907
+ return { authorized: /* @__PURE__ */ new Set(), present: false };
908
+ }
909
+ const at = Date.parse(String(decoded["detected_at"] ?? ""));
910
+ if (Number.isFinite(at) && now - at > LEDGER_MAX_AGE_MS) {
911
+ return { authorized: /* @__PURE__ */ new Set(), present: false };
912
+ }
913
+ return {
914
+ authorized: new Set(decoded["authorized"].filter(Boolean)),
915
+ present: true
916
+ };
917
+ }
918
+ } catch {
919
+ }
920
+ return { authorized: /* @__PURE__ */ new Set(), present: false };
921
+ }
922
+ function decide(command, ledger) {
923
+ if (command === null) {
924
+ return { exit: EXIT_ALLOW3, stdout: "", stderr: "" };
925
+ }
926
+ const op = commandOp(command);
927
+ if (op === null || ledger.authorized.has(op)) {
928
+ return { exit: EXIT_ALLOW3, stdout: "", stderr: "" };
929
+ }
930
+ const shown = command.length > 120 ? `${command.slice(0, 117)}\u2026` : command;
931
+ if (BLOCK_OPS.has(op)) {
932
+ const reason = `Blocked: \`${op}\` with no authorization in this turn's prompt. This operation is irreversible and non-destructive-by-default already declares it never-autonomous. Command: ${shown}. Ask the user, in one numbered-options block, and run it only after they answer this turn.`;
933
+ return { exit: EXIT_BLOCK, stdout: "", stderr: `${reason}
934
+ ` };
935
+ }
936
+ if (WARN_OPS.has(op)) {
937
+ const reason = `No \`${op}\` authorization found in this turn's prompt (commit-policy: a one-shot authorization is spent on the operation it named). If the user did authorize it this turn, proceed; otherwise stop and ask. Command: ${shown}`;
938
+ return {
939
+ exit: EXIT_ALLOW3,
940
+ stdout: `${JSON.stringify({ decision: "warn", reason })}
941
+ `,
942
+ stderr: ""
943
+ };
944
+ }
945
+ return { exit: EXIT_ALLOW3, stdout: "", stderr: "" };
946
+ }
947
+ function run3(stdin_text, options) {
948
+ let envelope = {};
949
+ if (stdin_text.trim()) {
950
+ try {
951
+ const decoded = JSON.parse(stdin_text);
952
+ if (_isObject4(decoded)) {
953
+ envelope = decoded;
954
+ }
955
+ } catch {
956
+ return EXIT_ALLOW3;
957
+ }
958
+ }
959
+ const session_id = typeof envelope["session_id"] === "string" ? envelope["session_id"] : "";
960
+ const decision = decide(
961
+ extractCommand(envelope),
962
+ _loadLedger(options.consumer_root, session_id, Date.now())
963
+ );
964
+ if (decision.stdout) {
965
+ process.stdout.write(decision.stdout);
966
+ }
967
+ if (decision.stderr) {
968
+ process.stderr.write(decision.stderr);
969
+ }
970
+ return decision.exit;
971
+ }
972
+ function main3(argv) {
973
+ const args = argv ?? process.argv.slice(2);
974
+ let consumer_root = process.cwd();
975
+ for (let i = 0; i < args.length; i += 1) {
976
+ const a = args[i];
977
+ if (a === "--project-dir" && args[i + 1] !== void 0) {
978
+ consumer_root = args[i + 1];
979
+ i += 1;
980
+ } else if (a !== void 0 && a.startsWith("--project-dir=")) {
981
+ consumer_root = a.slice("--project-dir=".length);
982
+ }
983
+ }
984
+ return run3(readHookStdin(), { consumer_root });
985
+ }
986
+ function _isCliEntry3() {
987
+ if (true) {
988
+ return false;
989
+ }
990
+ if (process.argv[1] === void 0) {
991
+ return false;
992
+ }
993
+ const argvUrl = pathToFileURL3(path4.resolve(process.argv[1])).href;
994
+ if (import.meta.url === argvUrl) {
995
+ return true;
996
+ }
997
+ try {
998
+ const here = fs4.realpathSync(fileURLToPath3(import.meta.url));
999
+ const argv = fs4.realpathSync(path4.resolve(process.argv[1]));
1000
+ return here === argv;
1001
+ } catch {
1002
+ return false;
1003
+ }
1004
+ }
1005
+ if (_isCliEntry3()) {
1006
+ process.exit(main3());
1007
+ }
1008
+
1009
+ // src/scripts/before_complete_hook.ts
1010
+ import fs5 from "node:fs";
1011
+ import path5 from "node:path";
1012
+ import { fileURLToPath as fileURLToPath4, pathToFileURL as pathToFileURL4 } from "node:url";
1013
+ var STATE_FILE3 = path5.join("agents", "state", "verify-before-complete.json");
1014
+ var COMMAND_TOOLS2 = /* @__PURE__ */ new Set([
1015
+ "launch-process",
1016
+ "launch_process",
1017
+ // Augment
1018
+ "Bash",
1019
+ "BashTool",
1020
+ // Claude Code
1021
+ "run-process",
1022
+ "runProcess",
1023
+ // variants
1024
+ "shell",
1025
+ "execute_shell",
1026
+ // generic / Cline
1027
+ "RunShellCommand"
1028
+ // Cursor
1029
+ ]);
1030
+ var _VERIFICATION_RE = new RegExp(
1031
+ "(?:^|[\\s;&|`(])(task\\s+(?:ci|test|tests|lint|check|qa|phpstan|rector|ecs|pest|pytest)|(?:\\./|\\.venv/bin/|vendor/bin/)?(?:pest|phpunit|phpstan|psalm|rector|ecs)\\b|(?:python3?|\\.venv/bin/python3?)\\s+-m\\s+pytest|pytest\\b|(?:npm|pnpm|yarn|bun)\\s+(?:run\\s+)?(?:test|check|lint|typecheck|tsc)|cargo\\s+(?:test|check|clippy)|go\\s+test|make\\s+(?:test|check|lint)|composer\\s+(?:test|check|lint|phpstan)|(?:php\\s+)?artisan\\s+test)",
1032
+ "i"
1033
+ );
1034
+ function _now() {
1035
+ return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "+00:00");
1036
+ }
1037
+ function _empty_state() {
1038
+ return {
1039
+ schema_version: 1,
1040
+ session_id: "",
1041
+ turn_started_at: null,
1042
+ last_verification: null,
1043
+ verifications_this_turn: 0,
1044
+ verifications_this_session: 0,
1045
+ last_stop_at: null,
1046
+ verified_this_turn: false,
1047
+ ci_saw_pending: false,
1048
+ nonevidence_this_turn: 0,
1049
+ checked_at: _now()
1050
+ };
1051
+ }
1052
+ function _load_state(target) {
1053
+ let isFile = false;
1054
+ try {
1055
+ isFile = fs5.statSync(target).isFile();
1056
+ } catch {
1057
+ isFile = false;
1058
+ }
1059
+ if (!isFile) {
1060
+ return _empty_state();
1061
+ }
1062
+ try {
1063
+ const decoded = JSON.parse(fs5.readFileSync(target, "utf-8"));
1064
+ if (typeof decoded === "object" && decoded !== null && !Array.isArray(decoded)) {
1065
+ return { ..._empty_state(), ...decoded };
1066
+ }
1067
+ } catch {
1068
+ }
1069
+ return _empty_state();
1070
+ }
1071
+ function _extract_command(payload) {
1072
+ const toolRaw = payload["tool_name"] || payload["toolName"] || payload["tool"];
1073
+ if (!(typeof toolRaw === "string" && COMMAND_TOOLS2.has(toolRaw))) {
1074
+ return [typeof toolRaw === "string" ? toolRaw : null, null];
1075
+ }
1076
+ const tool = toolRaw;
1077
+ const ti = payload["tool_input"];
1078
+ if (typeof ti === "object" && ti !== null && !Array.isArray(ti)) {
1079
+ for (const key of ["command", "cmd", "shell_command"]) {
1080
+ const v = ti[key];
1081
+ if (typeof v === "string" && v) {
1082
+ return [tool, v];
1083
+ }
1084
+ }
1085
+ }
1086
+ for (const key of ["command", "cmd"]) {
1087
+ const v = payload[key];
1088
+ if (typeof v === "string" && v) {
1089
+ return [tool, v];
1090
+ }
1091
+ }
1092
+ return [tool, null];
1093
+ }
1094
+ function _is_verification(command) {
1095
+ return _VERIFICATION_RE.test(command) || isCiPoll(command);
1096
+ }
1097
+ var _VACUOUS_PATTERNS = [
1098
+ // gh pr checks / gh run — no checks registered at all.
1099
+ /\b0\s+(?:checks?|runs?)\b/i,
1100
+ // Test runners reporting an empty run.
1101
+ /\bno tests? (?:ran|found|to run|were run)\b/i,
1102
+ /\bNo test files found\b/i,
1103
+ /\b0\s+pass(?:ed|ing)?\b[\s,|·]*\b0\s+fail(?:ed|ing|ures)?\b/i,
1104
+ /\bTests?\s+0\s+passed\b/i,
1105
+ /\bRan 0 tests?\b/i,
1106
+ // Linters / scanners with an empty target set.
1107
+ /\bno files? (?:matched|to (?:lint|check|scan)|found)\b/i,
1108
+ /\b0\s+files?\s+(?:checked|scanned|linted)\b/i,
1109
+ /\bnothing to check\b/i
1110
+ ];
1111
+ function isVacuousOutput(output) {
1112
+ const text = output.trim();
1113
+ if (!text) {
1114
+ return false;
1115
+ }
1116
+ const lines = text.split("\n").filter((l) => l.trim());
1117
+ return lines.every((l) => _VACUOUS_PATTERNS.some((re) => re.test(l)));
1118
+ }
1119
+ var _CI_POLL_RE = /\bgh\s+(?:pr\s+checks|run\s+(?:watch|list|view))\b/i;
1120
+ function isCiPoll(command) {
1121
+ return _CI_POLL_RE.test(command);
1122
+ }
1123
+ function pendingCount(output) {
1124
+ const m = /(\d+)\s+(?:checks? )?pending\b/i.exec(output) ?? /\bpending[:=]\s*(\d+)/i.exec(output);
1125
+ if (m?.[1] !== void 0) {
1126
+ return Number(m[1]);
1127
+ }
1128
+ const rows = output.match(/^\S.*\b(pending|in_progress|queued)\b/gim);
1129
+ if (rows) {
1130
+ return rows.length;
1131
+ }
1132
+ if (/\bno checks reported\b/i.test(output)) {
1133
+ return null;
1134
+ }
1135
+ if (/^\S+\s+(pass|fail|skipping|successful|failing)\b/im.test(output)) {
1136
+ return 0;
1137
+ }
1138
+ return null;
1139
+ }
1140
+ function _extract_output(payload) {
1141
+ for (const key of ["tool_response", "toolResponse", "output", "stdout", "result"]) {
1142
+ const v = payload[key];
1143
+ if (typeof v === "string") {
1144
+ return v;
1145
+ }
1146
+ if (v !== null && typeof v === "object") {
1147
+ return JSON.stringify(v);
1148
+ }
1149
+ }
1150
+ return null;
1151
+ }
1152
+ function _reset_turn(state, session_id) {
1153
+ state["session_id"] = session_id || state["session_id"] || "";
1154
+ state["turn_started_at"] = _now();
1155
+ state["verifications_this_turn"] = 0;
1156
+ state["verified_this_turn"] = false;
1157
+ state["ci_saw_pending"] = false;
1158
+ state["nonevidence_this_turn"] = 0;
1159
+ return state;
1160
+ }
1161
+ function _asInt(v) {
1162
+ if (!v) {
1163
+ return 0;
1164
+ }
1165
+ if (typeof v === "number") {
1166
+ return Math.trunc(v);
1167
+ }
1168
+ const n = Number(v);
1169
+ return Number.isFinite(n) ? Math.trunc(n) : 0;
1170
+ }
1171
+ function _update(state, event, envelope) {
1172
+ const session_id = envelope["session_id"] || state["session_id"] || "";
1173
+ if (session_id && session_id !== state["session_id"]) {
1174
+ state["session_id"] = session_id;
1175
+ state["verifications_this_session"] = 0;
1176
+ state = _reset_turn(state, session_id);
1177
+ }
1178
+ let payload = envelope["payload"];
1179
+ if (!(typeof payload === "object" && payload !== null && !Array.isArray(payload))) {
1180
+ payload = {};
1181
+ }
1182
+ const pl = payload;
1183
+ if (event === "session_start" || event === "user_prompt_submit") {
1184
+ state = _reset_turn(state, session_id);
1185
+ } else if (event === "post_tool_use") {
1186
+ const [tool, cmd] = _extract_command(pl);
1187
+ if (cmd && _is_verification(cmd)) {
1188
+ const output = _extract_output(pl);
1189
+ const vacuous = output !== null && isVacuousOutput(output);
1190
+ let counts = !vacuous;
1191
+ if (isCiPoll(cmd)) {
1192
+ const pending = output === null ? null : pendingCount(output);
1193
+ if (pending !== null && pending > 0) {
1194
+ state["ci_saw_pending"] = true;
1195
+ counts = false;
1196
+ } else if (pending === 0) {
1197
+ counts = !vacuous && state["ci_saw_pending"] === true;
1198
+ } else {
1199
+ counts = false;
1200
+ }
1201
+ }
1202
+ state["last_verification"] = {
1203
+ command: cmd.slice(0, 512),
1204
+ tool,
1205
+ at: _now(),
1206
+ platform: envelope["platform"] || "",
1207
+ vacuous,
1208
+ counted: counts
1209
+ };
1210
+ if (counts) {
1211
+ state["verifications_this_turn"] = _asInt(state["verifications_this_turn"]) + 1;
1212
+ state["verifications_this_session"] = _asInt(state["verifications_this_session"]) + 1;
1213
+ state["verified_this_turn"] = true;
1214
+ } else {
1215
+ state["nonevidence_this_turn"] = _asInt(state["nonevidence_this_turn"]) + 1;
1216
+ }
1217
+ }
1218
+ } else if (event === "stop") {
1219
+ state["last_stop_at"] = _now();
1220
+ }
1221
+ state["checked_at"] = _now();
1222
+ return state;
1223
+ }
1224
+ function run4(stdin_text, options) {
1225
+ const { consumer_root } = options;
1226
+ const verbose = options.verbose ?? false;
1227
+ let envelope = {};
1228
+ if (stdin_text.trim()) {
1229
+ try {
1230
+ const decoded = JSON.parse(stdin_text);
1231
+ if (typeof decoded === "object" && decoded !== null && !Array.isArray(decoded)) {
1232
+ envelope = decoded;
1233
+ }
1234
+ } catch {
1235
+ envelope = {};
1236
+ }
1237
+ }
1238
+ const event = envelope["event"] || "";
1239
+ const target = path5.join(consumer_root, STATE_FILE3);
1240
+ let state = _load_state(target);
1241
+ state = _update(state, event, envelope);
1242
+ try {
1243
+ atomic_write_json(target, state);
1244
+ } catch {
1245
+ if (verbose) {
1246
+ process.stderr.write("verify-before-complete-hook: state write failed\n");
1247
+ }
1248
+ return 0;
1249
+ }
1250
+ if (verbose) {
1251
+ process.stderr.write(
1252
+ `verify-before-complete-hook: event=${event} verified_this_turn=${pyRepr(state["verified_this_turn"])} verifications_this_turn=${pyRepr(state["verifications_this_turn"])}
1253
+ `
1254
+ );
1255
+ }
1256
+ return 0;
1257
+ }
1258
+ function pyRepr(value) {
1259
+ if (value === null || value === void 0) {
1260
+ return "None";
1261
+ }
1262
+ if (typeof value === "boolean") {
1263
+ return value ? "True" : "False";
1264
+ }
1265
+ return String(value);
1266
+ }
1267
+ function parse_args(argv) {
1268
+ let platform = "generic";
1269
+ let verbose = false;
1270
+ for (let i = 0; i < argv.length; i += 1) {
1271
+ const arg = argv[i];
1272
+ if (arg === "--platform") {
1273
+ platform = argv[i + 1] ?? "generic";
1274
+ i += 1;
1275
+ } else if (arg !== void 0 && arg.startsWith("--platform=")) {
1276
+ platform = arg.slice("--platform=".length);
1277
+ } else if (arg === "--verbose") {
1278
+ verbose = true;
1279
+ }
1280
+ }
1281
+ return { platform, verbose };
1282
+ }
1283
+ function _readStdin() {
1284
+ return readHookStdin();
1285
+ }
1286
+ function main4(argv) {
1287
+ const args = parse_args(argv ?? process.argv.slice(2));
1288
+ return run4(_readStdin(), { consumer_root: process.cwd(), verbose: args.verbose });
1289
+ }
1290
+ function _isCliEntry4() {
1291
+ if (true) {
1292
+ return false;
1293
+ }
1294
+ if (process.argv[1] === void 0) {
1295
+ return false;
1296
+ }
1297
+ const argvUrl = pathToFileURL4(path5.resolve(process.argv[1])).href;
1298
+ if (import.meta.url === argvUrl) {
1299
+ return true;
1300
+ }
1301
+ try {
1302
+ const here = fs5.realpathSync(fileURLToPath4(import.meta.url));
1303
+ const argv = fs5.realpathSync(path5.resolve(process.argv[1]));
1304
+ return here === argv;
1305
+ } catch {
1306
+ return false;
1307
+ }
1308
+ }
1309
+ var isCliEntry = _isCliEntry4();
1310
+ if (isCliEntry) {
1311
+ process.exit(main4(process.argv.slice(2)));
1312
+ }
1313
+
1314
+ // src/scripts/hooks/evidence_independence.ts
1315
+ import crypto from "node:crypto";
1316
+ import fs6 from "node:fs";
1317
+ import path6 from "node:path";
1318
+ import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL5 } from "node:url";
1319
+ var EXIT_ALLOW4 = 0;
1320
+ var EXIT_BLOCK2 = 1;
1321
+ var STATE_FILE4 = path6.join("agents", "state", "evidence-dispatch.json");
1322
+ function _isObject5(v) {
1323
+ return typeof v === "object" && v !== null && !Array.isArray(v);
1324
+ }
1325
+ var DISPATCH_TOOLS = /* @__PURE__ */ new Set([
1326
+ "Agent",
1327
+ "Task",
1328
+ "task",
1329
+ "dispatch_agent",
1330
+ "dispatchAgent",
1331
+ "launch_agent",
1332
+ "run_subagent",
1333
+ "Subagent"
1334
+ ]);
1335
+ var EVALUATION_RE = /\b(review|reviewer|audit|auditor|judge|verdict|blind[- ]pass|blind review|adversarial|critique|assess(ment)?|verify (my|the) (work|change|diff|implementation)|find (any )?(bugs|issues|defects|problems) in (my|the)|pr(ü|ue)f(e|ung)?|begutachte|gutachten|bewerte|(gegen)?check(e)?|durchsicht|review(e|iere))\b/i;
1336
+ var PRELOADED_VERDICT_RE = [
1337
+ // German — the operator writes German, and an English-only list meant no
1338
+ // German dispatch was ever screened at all.
1339
+ /\bich erwarte (hier )?(keine|nichts)\b/i,
1340
+ /\b(du )?(wirst|solltest) (hier )?nichts finden\b/i,
1341
+ /\bbest(ä|ae)tige (nur|kurz)\b/i,
1342
+ /\b(ist|sollte) (wahrscheinlich|vermutlich|sicher) (sauber|korrekt|in ordnung|fine)\b/i,
1343
+ /\bkeine findings? (erwartet|zu erwarten)\b/i,
1344
+ // English paraphrases of the audited construct.
1345
+ /\bi am confident there is nothing\b/i,
1346
+ /\ba clean pass is the likely\b/i,
1347
+ /\bno[- ]findings? (is|are) (expected|welcome|fine|acceptable|the likely)/i,
1348
+ /\b(you )?(should|will|probably) find (nothing|no (issues|problems|bugs|findings))/i,
1349
+ /\bconfirm (that )?there (are|is) no\b/i,
1350
+ /\bi (believe|think|expect) (this|it) is (clean|correct|fine)\b/i,
1351
+ /\bexpect(ed)? (to be )?(clean|green|no findings)\b/i,
1352
+ /\bjust confirm\b/i,
1353
+ /\bit('s| is) (probably|likely) fine\b/i
1354
+ ];
1355
+ function isDispatchTool(tool) {
1356
+ return tool !== null && DISPATCH_TOOLS.has(tool);
1357
+ }
1358
+ function isEvaluationPrompt(prompt) {
1359
+ return EVALUATION_RE.test(prompt);
1360
+ }
1361
+ var SELF_SCOPE_RE = /\b(my (work|change|diff|implementation|code|fix|patch|branch|pr)|this (diff|change|branch|pr|delta|implementation|patch)|the delta|the change i|what i (wrote|built|changed|implemented)|i just (wrote|built|changed|implemented))\b/i;
1362
+ function isSelfScoped(prompt) {
1363
+ return SELF_SCOPE_RE.test(prompt);
1364
+ }
1365
+ function preloadedVerdict(prompt) {
1366
+ for (const re of PRELOADED_VERDICT_RE) {
1367
+ const m = re.exec(prompt);
1368
+ if (m) {
1369
+ return m[0];
1370
+ }
1371
+ }
1372
+ return null;
1373
+ }
1374
+ function extractDispatch(envelope) {
1375
+ const payload = _isObject5(envelope["payload"]) ? envelope["payload"] : envelope;
1376
+ const toolRaw = payload["tool_name"] ?? payload["toolName"] ?? payload["tool"];
1377
+ const tool = typeof toolRaw === "string" ? toolRaw : null;
1378
+ const input = _isObject5(payload["tool_input"]) ? payload["tool_input"] : _isObject5(payload["toolInput"]) ? payload["toolInput"] : payload;
1379
+ for (const key of ["prompt", "instructions", "task", "description"]) {
1380
+ const v = input[key];
1381
+ if (typeof v === "string" && v.trim()) {
1382
+ return [tool, v];
1383
+ }
1384
+ }
1385
+ return [tool, ""];
1386
+ }
1387
+ function _ledgerStamp(consumer_root) {
1388
+ try {
1389
+ const raw = fs6.readFileSync(
1390
+ path6.join(consumer_root, "agents", "state", "git-authorization.json"),
1391
+ "utf8"
1392
+ );
1393
+ const d = JSON.parse(raw);
1394
+ return typeof d["detected_at"] === "string" ? d["detected_at"] : "";
1395
+ } catch {
1396
+ return "";
1397
+ }
1398
+ }
1399
+ function _load(target) {
1400
+ try {
1401
+ const decoded = JSON.parse(fs6.readFileSync(target, "utf8"));
1402
+ if (_isObject5(decoded) && Array.isArray(decoded["evaluations"])) {
1403
+ return decoded;
1404
+ }
1405
+ } catch {
1406
+ }
1407
+ return { session_id: "", turn_started_at: "", evaluations: [] };
1408
+ }
1409
+ 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.";
1410
+ function decide2(tool, prompt, priorEvaluations) {
1411
+ if (!isDispatchTool(tool) || !isEvaluationPrompt(prompt)) {
1412
+ return { exit: EXIT_ALLOW4, stdout: "", stderr: "", evaluations: priorEvaluations };
1413
+ }
1414
+ const preloaded = preloadedVerdict(prompt);
1415
+ if (preloaded !== null) {
1416
+ return {
1417
+ exit: EXIT_BLOCK2,
1418
+ stdout: "",
1419
+ 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.
1420
+ `,
1421
+ evaluations: priorEvaluations
1422
+ };
1423
+ }
1424
+ if (!isSelfScoped(prompt)) {
1425
+ return { exit: EXIT_ALLOW4, stdout: "", stderr: "", evaluations: priorEvaluations };
1426
+ }
1427
+ if (priorEvaluations >= 1) {
1428
+ return {
1429
+ exit: EXIT_BLOCK2,
1430
+ stdout: "",
1431
+ 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.
1432
+ `,
1433
+ evaluations: priorEvaluations
1434
+ };
1435
+ }
1436
+ return {
1437
+ exit: EXIT_ALLOW4,
1438
+ stdout: `${JSON.stringify({ decision: "warn", reason: WARN_TEXT })}
1439
+ `,
1440
+ stderr: "",
1441
+ evaluations: priorEvaluations + 1
1442
+ };
1443
+ }
1444
+ function run5(stdin_text, options) {
1445
+ let envelope = {};
1446
+ if (stdin_text.trim()) {
1447
+ try {
1448
+ const decoded = JSON.parse(stdin_text);
1449
+ if (_isObject5(decoded)) {
1450
+ envelope = decoded;
1451
+ }
1452
+ } catch {
1453
+ return EXIT_ALLOW4;
1454
+ }
1455
+ }
1456
+ const target = path6.join(options.consumer_root, STATE_FILE4);
1457
+ const state = _load(target);
1458
+ const session_id = typeof envelope["session_id"] === "string" ? envelope["session_id"] : "";
1459
+ const turnMarker = `${session_id}:${_ledgerStamp(options.consumer_root)}`;
1460
+ if (state.session_id !== turnMarker) {
1461
+ state.session_id = turnMarker;
1462
+ state.turn_started_at = (/* @__PURE__ */ new Date()).toISOString();
1463
+ state.evaluations = [];
1464
+ }
1465
+ const [tool, prompt] = extractDispatch(envelope);
1466
+ const decision = decide2(tool, prompt, state.evaluations.length);
1467
+ if (decision.evaluations > state.evaluations.length) {
1468
+ state.evaluations.push({
1469
+ at: (/* @__PURE__ */ new Date()).toISOString(),
1470
+ digest: crypto.createHash("sha256").update(prompt).digest("hex").slice(0, 16),
1471
+ prompt_chars: prompt.length
1472
+ });
1473
+ try {
1474
+ atomic_write_json(target, state);
1475
+ } catch {
1476
+ }
1477
+ }
1478
+ if (decision.stdout) {
1479
+ process.stdout.write(decision.stdout);
1480
+ }
1481
+ if (decision.stderr) {
1482
+ process.stderr.write(decision.stderr);
1483
+ }
1484
+ return decision.exit;
1485
+ }
1486
+ function main5(argv) {
1487
+ const args = argv ?? process.argv.slice(2);
1488
+ let consumer_root = process.cwd();
1489
+ for (let i = 0; i < args.length; i += 1) {
1490
+ const a = args[i];
1491
+ if (a === "--project-dir" && args[i + 1] !== void 0) {
1492
+ consumer_root = args[i + 1];
1493
+ i += 1;
1494
+ } else if (a !== void 0 && a.startsWith("--project-dir=")) {
1495
+ consumer_root = a.slice("--project-dir=".length);
1496
+ }
1497
+ }
1498
+ return run5(readHookStdin(), { consumer_root });
1499
+ }
1500
+ function _isCliEntry5() {
1501
+ if (true) {
1502
+ return false;
1503
+ }
1504
+ if (process.argv[1] === void 0) {
1505
+ return false;
1506
+ }
1507
+ const argvUrl = pathToFileURL5(path6.resolve(process.argv[1])).href;
1508
+ if (import.meta.url === argvUrl) {
1509
+ return true;
1510
+ }
1511
+ try {
1512
+ const here = fs6.realpathSync(fileURLToPath5(import.meta.url));
1513
+ const argv = fs6.realpathSync(path6.resolve(process.argv[1]));
1514
+ return here === argv;
1515
+ } catch {
1516
+ return false;
1517
+ }
1518
+ }
1519
+ if (_isCliEntry5()) {
1520
+ process.exit(main5());
1521
+ }
1522
+
1523
+ // src/scripts/conformance_scan.ts
1524
+ var REPO_ROOT = path7.resolve(path7.dirname(fileURLToPath6(import.meta.url)), "..", "..");
1525
+ var OBSERVED_BAND = { low: 9.1, high: 39.2 };
1526
+ var BAND_MIN_TURNS = 1978;
1527
+ function bandVerdict(rate_pct, assistant_turns) {
1528
+ if (assistant_turns < BAND_MIN_TURNS) {
1529
+ return "corpus-too-small";
1530
+ }
1531
+ return rate_pct >= OBSERVED_BAND.low && rate_pct <= OBSERVED_BAND.high ? "inside" : "outside";
1532
+ }
1533
+ function _isObject6(v) {
1534
+ return typeof v === "object" && v !== null && !Array.isArray(v);
1535
+ }
1536
+ function userText(entry) {
1537
+ if (entry["type"] !== "user" || isSidechain(entry)) {
1538
+ return null;
1539
+ }
1540
+ const text = entryText(entry);
1541
+ if (!text.trim()) {
1542
+ return null;
1543
+ }
1544
+ if (text.includes("<system-reminder>") || text.startsWith("<local-command-stdout>")) {
1545
+ return null;
1546
+ }
1547
+ return text;
1548
+ }
1549
+ function assistantText(entry) {
1550
+ if (entry["type"] !== "assistant" || isSidechain(entry)) {
1551
+ return null;
1552
+ }
1553
+ return entryText(entry).trim() || null;
1554
+ }
1555
+ function toolUses2(entry) {
1556
+ const msg = entry["message"];
1557
+ const content = _isObject6(msg) ? msg["content"] : void 0;
1558
+ if (!Array.isArray(content)) {
1559
+ return [];
1560
+ }
1561
+ return content.filter((b) => _isObject6(b) && b["type"] === "tool_use").map((b) => {
1562
+ const o = b;
1563
+ return {
1564
+ name: String(o["name"] ?? ""),
1565
+ input: _isObject6(o["input"]) ? o["input"] : {}
1566
+ };
1567
+ });
1568
+ }
1569
+ function isInjectedBody(text) {
1570
+ if (/^(Base directory for this skill|<command-message>|<command-name>|<local-command)/.test(text)) {
1571
+ return true;
1572
+ }
1573
+ return text.length > 2500 && classify(text).language === "en";
1574
+ }
1575
+ var HARNESS_TEXT = /^(API Error:|Request (timed out|was aborted)|\[Request interrupted|Error: |Credit balance is too low|You've hit your|Prompt is too long)/i;
1576
+ var EN_OPENER = /^(let me|i'?ll |i'?m |found it|ok[,. ]|okay|alright|here'?s|now[,. ]|looking|checking|reading|running|the |this |that |there |we |you |good |right[,. ]|perfect|done[.,]|all |confirmed|correct)/i;
1577
+ function scanSession(sessionId, lines) {
1578
+ const report = {
1579
+ session: sessionId,
1580
+ user_turns: 0,
1581
+ assistant_turns: 0,
1582
+ violations: []
1583
+ };
1584
+ let pinned = null;
1585
+ let pendingPoll = null;
1586
+ let authorized = /* @__PURE__ */ new Set();
1587
+ let sawPending = false;
1588
+ let evaluationsThisTurn = 0;
1589
+ let turnsSincePrompt = 0;
1590
+ let compactionSincePrompt = false;
1591
+ for (const line of lines) {
1592
+ if (!line.trim()) {
1593
+ continue;
1594
+ }
1595
+ let entry;
1596
+ try {
1597
+ entry = JSON.parse(line);
1598
+ } catch {
1599
+ continue;
1600
+ }
1601
+ if (entry["type"] === "user" && entry["toolUseResult"] !== void 0 && pendingPoll !== null) {
1602
+ const raw = entry["toolUseResult"];
1603
+ const out = typeof raw === "string" ? raw : JSON.stringify(raw);
1604
+ const pending = pendingCount(out);
1605
+ if (pending !== null && pending > 0) {
1606
+ sawPending = true;
1607
+ } else if (isVacuousOutput(out) || pending === 0 && !sawPending) {
1608
+ report.violations.push({
1609
+ check: "vacuous-evidence",
1610
+ session: sessionId,
1611
+ at: pendingPoll.at,
1612
+ detail: `CI poll read as settled without an in-flight observation: ${out.slice(0, 90)}`
1613
+ });
1614
+ }
1615
+ pendingPoll = null;
1616
+ continue;
1617
+ }
1618
+ if (entry["type"] === "system" && entry["subtype"] === "compact_boundary") {
1619
+ compactionSincePrompt = true;
1620
+ continue;
1621
+ }
1622
+ const ut = userText(entry);
1623
+ if (ut !== null) {
1624
+ if (entry["isCompactSummary"] === true) {
1625
+ continue;
1626
+ }
1627
+ if (isSyntheticPrompt(ut)) {
1628
+ continue;
1629
+ }
1630
+ if (isInjectedBody(ut)) {
1631
+ continue;
1632
+ }
1633
+ report.user_turns += 1;
1634
+ turnsSincePrompt = 0;
1635
+ compactionSincePrompt = false;
1636
+ const c = classify(ut);
1637
+ if (c.language !== "und") {
1638
+ pinned = c.language;
1639
+ }
1640
+ authorized = new Set(classifyAuthorization(ut).authorized);
1641
+ sawPending = false;
1642
+ evaluationsThisTurn = 0;
1643
+ pendingPoll = null;
1644
+ continue;
1645
+ }
1646
+ if (entry["type"] !== "assistant" || entry["isSidechain"] === true) {
1647
+ continue;
1648
+ }
1649
+ const at = String(entry["timestamp"] ?? "");
1650
+ const prose = assistantText(entry);
1651
+ if (prose !== null) {
1652
+ report.assistant_turns += 1;
1653
+ turnsSincePrompt += 1;
1654
+ if (pinned === "de") {
1655
+ const first = prose.split("\n").find((l) => l.trim()) ?? "";
1656
+ if (HARNESS_TEXT.test(first.trim())) {
1657
+ continue;
1658
+ }
1659
+ const c = classify(first);
1660
+ if (first.length > 12 && (c.language === "en" || EN_OPENER.test(first.trim()))) {
1661
+ report.violations.push({
1662
+ check: "language-pin",
1663
+ session: sessionId,
1664
+ at,
1665
+ detail: `German pin, English reply opener: "${first.slice(0, 110)}"`,
1666
+ turns_since_prompt: turnsSincePrompt,
1667
+ compaction_since_prompt: compactionSincePrompt
1668
+ });
1669
+ }
1670
+ }
1671
+ }
1672
+ for (const tu of toolUses2(entry)) {
1673
+ const cmd = typeof tu.input["command"] === "string" ? tu.input["command"] : null;
1674
+ if (cmd !== null) {
1675
+ const op = commandOp(cmd);
1676
+ if (op !== null && !authorized.has(op) && BLOCK_OPS.has(op)) {
1677
+ report.violations.push({
1678
+ check: "git-authorization",
1679
+ session: sessionId,
1680
+ at,
1681
+ detail: `irreversible \`${op}\` with no authorization in the turn's prompt: ${cmd.slice(0, 110)}`
1682
+ });
1683
+ }
1684
+ if (isCiPoll(cmd)) {
1685
+ pendingPoll = { at, cmd };
1686
+ }
1687
+ }
1688
+ const prompt = typeof tu.input["prompt"] === "string" ? tu.input["prompt"] : null;
1689
+ if (prompt !== null && isEvaluationPrompt(prompt)) {
1690
+ const preloaded = preloadedVerdict(prompt);
1691
+ if (preloaded !== null) {
1692
+ report.violations.push({
1693
+ check: "evidence-steering",
1694
+ session: sessionId,
1695
+ at,
1696
+ detail: `evaluation prompt pre-loads its verdict: "${preloaded}"`
1697
+ });
1698
+ } else if (isSelfScoped(prompt)) {
1699
+ if (evaluationsThisTurn >= 1) {
1700
+ report.violations.push({
1701
+ check: "evidence-steering",
1702
+ session: sessionId,
1703
+ at,
1704
+ detail: "second self-review dispatch in one turn (verdict shopping)"
1705
+ });
1706
+ }
1707
+ evaluationsThisTurn += 1;
1708
+ }
1709
+ }
1710
+ }
1711
+ }
1712
+ return report;
1713
+ }
1714
+ var DEFAULT_RATE_SERIES = path7.join(
1715
+ REPO_ROOT,
1716
+ "agents",
1717
+ "runtime",
1718
+ "state",
1719
+ "conformance-rates.jsonl"
1720
+ );
1721
+ function defaultStore(projectDir) {
1722
+ const slug = projectDir.replace(/[/.]/g, "-");
1723
+ return path7.join(process.env["HOME"] ?? "", ".claude", "projects", slug);
1724
+ }
1725
+ function _tokens(chars) {
1726
+ return Math.round(chars / 4);
1727
+ }
1728
+ function measureDelivered(projectRulesDir, globalRulesDir) {
1729
+ const p = censusRuleDir(projectRulesDir);
1730
+ const g = censusRuleDir(globalRulesDir);
1731
+ return {
1732
+ project: {
1733
+ dir: projectRulesDir,
1734
+ present: fs7.existsSync(projectRulesDir),
1735
+ files: p.files,
1736
+ tokens: _tokens(p.chars)
1737
+ },
1738
+ global: {
1739
+ dir: globalRulesDir,
1740
+ present: fs7.existsSync(globalRulesDir),
1741
+ files: g.files,
1742
+ tokens: _tokens(g.chars)
1743
+ },
1744
+ union_tokens: _tokens(p.chars + g.chars)
1745
+ };
1746
+ }
1747
+ function storeKey(store) {
1748
+ return createHash("sha256").update(path7.resolve(store)).digest("hex").slice(0, 12);
1749
+ }
1750
+ function scanStore(store, limit) {
1751
+ const files = fs7.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => ({ f, m: fs7.statSync(path7.join(store, f)).mtimeMs })).sort((a, b) => b.m - a.m).slice(0, limit).map((x) => x.f);
1752
+ const per_session = [];
1753
+ for (const f of files) {
1754
+ const lines = fs7.readFileSync(path7.join(store, f), "utf8").split("\n");
1755
+ const r = scanSession(f.replace(/\.jsonl$/, "").slice(0, 8), lines);
1756
+ if (r.assistant_turns > 0) {
1757
+ per_session.push(r);
1758
+ }
1759
+ }
1760
+ const totals = {
1761
+ "language-pin": 0,
1762
+ "git-authorization": 0,
1763
+ "vacuous-evidence": 0,
1764
+ "evidence-steering": 0
1765
+ };
1766
+ for (const s of per_session) {
1767
+ for (const v of s.violations) {
1768
+ totals[v.check] = (totals[v.check] ?? 0) + 1;
1769
+ }
1770
+ }
1771
+ const assistant_turns = per_session.reduce((n, s) => n + s.assistant_turns, 0);
1772
+ const language_pin = totals["language-pin"] ?? 0;
1773
+ const rate_pct = assistant_turns === 0 ? 0 : Number((100 * language_pin / assistant_turns).toFixed(1));
1774
+ const delivered = measureDelivered(
1775
+ path7.join(REPO_ROOT, ".claude", "rules"),
1776
+ path7.join(process.env["HOME"] ?? os.homedir(), ".claude", "rules")
1777
+ );
1778
+ const ownStore = path7.resolve(store) === path7.resolve(defaultStore(REPO_ROOT));
1779
+ return {
1780
+ scanned_at: (/* @__PURE__ */ new Date()).toISOString(),
1781
+ store,
1782
+ sessions: per_session.length,
1783
+ totals,
1784
+ per_session,
1785
+ delivered,
1786
+ rate: {
1787
+ store_key: storeKey(store),
1788
+ sessions: per_session.length,
1789
+ assistant_turns,
1790
+ language_pin,
1791
+ rate_pct,
1792
+ band: bandVerdict(rate_pct, assistant_turns),
1793
+ // Only attach the carrier figures when the rate and the carriers describe
1794
+ // the same project.
1795
+ delivered_project_tokens: ownStore ? delivered.project.tokens : null,
1796
+ delivered_global_tokens: ownStore ? delivered.global.tokens : null
1797
+ }
1798
+ };
1799
+ }
1800
+ function recordRate(file, scanned_at, rate) {
1801
+ fs7.mkdirSync(path7.dirname(file), { recursive: true });
1802
+ fs7.appendFileSync(file, `${JSON.stringify({ measured_at: scanned_at, ...rate })}
1803
+ `, "utf8");
1804
+ }
1805
+ function render(report) {
1806
+ const lines = [];
1807
+ lines.push(`conformance:behavior \xB7 ${report.sessions} session(s) \xB7 ${report.store}`);
1808
+ for (const [k, v] of Object.entries(report.totals)) {
1809
+ lines.push(` ${v === 0 ? "\u2705" : "\u26A0\uFE0F "} ${k.padEnd(18)} ${v}`);
1810
+ }
1811
+ const worst = [...report.per_session].filter((s) => s.violations.length > 0).sort((a, b) => b.violations.length - a.violations.length).slice(0, 5);
1812
+ if (worst.length > 0) {
1813
+ lines.push("");
1814
+ lines.push(" Highest-violation sessions:");
1815
+ for (const s of worst) {
1816
+ lines.push(` ${s.session} ${s.violations.length} across ${s.assistant_turns} turns`);
1817
+ }
1818
+ }
1819
+ const d = report.delivered;
1820
+ lines.push("");
1821
+ lines.push(" Delivered rule text (chars/4, carriers as they stand NOW \u2014 not per-session):");
1822
+ const carrier = (label, c) => c.present ? ` ${label} ${String(c.files).padStart(4)} rules ${String(c.tokens).padStart(7)} tok` : ` ${label} ABSENT \u2014 not a measured zero, this carrier does not exist here`;
1823
+ lines.push(carrier("project", d.project));
1824
+ lines.push(carrier("global ", d.global));
1825
+ lines.push(` union ${String(d.union_tokens).padStart(7)} tok`);
1826
+ lines.push(" A session's own payload is NOT recoverable: the transcript records no system");
1827
+ lines.push(" or tools field, and the carriers change under the sessions. This is one");
1828
+ lines.push(" reading; the series below is what makes it interpretable over time.");
1829
+ lines.push("");
1830
+ lines.push(
1831
+ ` language-pin rate ${report.rate.rate_pct.toFixed(1)}% (${report.rate.language_pin} of ${report.rate.assistant_turns} assistant turns)`
1832
+ );
1833
+ if (report.rate.band === "corpus-too-small") {
1834
+ lines.push(
1835
+ ` Not compared: ${report.rate.assistant_turns} turns is below the ${BAND_MIN_TURNS} of the`
1836
+ );
1837
+ lines.push(" smallest corpus the band was derived from, so a verdict here would be about");
1838
+ lines.push(" corpus size, not behaviour.");
1839
+ } else if (report.rate.band === "inside") {
1840
+ lines.push(
1841
+ ` Inside the observed ${OBSERVED_BAND.low}-${OBSERVED_BAND.high}% band (M5, n=3).`
1842
+ );
1843
+ } else {
1844
+ lines.push(
1845
+ ` \u26A0\uFE0F OUTSIDE the observed ${OBSERVED_BAND.low}-${OBSERVED_BAND.high}% band (M5, n=3).`
1846
+ );
1847
+ lines.push(" This is the falsifier both council members named when the volume test was");
1848
+ lines.push(" cancelled: a project outside the band is the signal that the cancellation");
1849
+ lines.push(" was wrong. It is a reason to look, not a defect in this run.");
1850
+ lines.push(" CHECK FIRST that this is a different PROJECT: a git worktree gets its own");
1851
+ lines.push(" transcript store under the same project, so a store-keyed series counts it");
1852
+ lines.push(" as new. Store novelty is not project novelty.");
1853
+ }
1854
+ lines.push("");
1855
+ lines.push(" Scanned checks are exactly the mechanised ones. Ask-shape, session-canary,");
1856
+ lines.push(" promissory closings and checkbox batching are left as prose and NOT measured.");
1857
+ return lines.join("\n");
1858
+ }
1859
+ function main6(argv) {
1860
+ const args = argv ?? process.argv.slice(2);
1861
+ let store = null;
1862
+ let limit = 30;
1863
+ let json = false;
1864
+ let out = null;
1865
+ let record = null;
1866
+ for (let i = 0; i < args.length; i += 1) {
1867
+ const a = args[i];
1868
+ if (a === "--store" && args[i + 1] !== void 0) {
1869
+ store = args[++i];
1870
+ } else if (a === "--limit" && args[i + 1] !== void 0) {
1871
+ limit = Number(args[++i]);
1872
+ } else if (a === "--json") {
1873
+ json = true;
1874
+ } else if (a === "--output" && args[i + 1] !== void 0) {
1875
+ out = args[++i];
1876
+ } else if (a === "--record") {
1877
+ record = args[i + 1] !== void 0 && !args[i + 1].startsWith("-") ? args[++i] : DEFAULT_RATE_SERIES;
1878
+ }
1879
+ }
1880
+ const resolved = store ?? defaultStore(process.cwd());
1881
+ if (!fs7.existsSync(resolved)) {
1882
+ process.stderr.write(`conformance:behavior: no transcript store at ${resolved}
1883
+ `);
1884
+ return 1;
1885
+ }
1886
+ const report = scanStore(resolved, Number.isFinite(limit) && limit > 0 ? limit : 30);
1887
+ if (out) {
1888
+ fs7.writeFileSync(out, `${JSON.stringify(report, null, 2)}
1889
+ `, "utf8");
1890
+ }
1891
+ if (record) {
1892
+ recordRate(record, report.scanned_at, report.rate);
1893
+ }
1894
+ fs7.writeSync(1, json ? `${JSON.stringify(report, null, 2)}
1895
+ ` : `${render(report)}
1896
+ `);
1897
+ return 0;
1898
+ }
1899
+ function _isCliEntry6() {
1900
+ if (true) {
1901
+ return false;
1902
+ }
1903
+ if (process.argv[1] === void 0) {
1904
+ return false;
1905
+ }
1906
+ const argvUrl = pathToFileURL6(path7.resolve(process.argv[1])).href;
1907
+ if (import.meta.url === argvUrl) {
1908
+ return true;
1909
+ }
1910
+ try {
1911
+ const here = fs7.realpathSync(fileURLToPath6(import.meta.url));
1912
+ const argv = fs7.realpathSync(path7.resolve(process.argv[1]));
1913
+ return here === argv;
1914
+ } catch {
1915
+ return false;
1916
+ }
1917
+ }
1918
+ if (_isCliEntry6()) {
1919
+ process.exit(main6());
1920
+ }
1921
+
1922
+ // src/scripts/report_carrier_divergence.ts
1923
+ import * as fs8 from "node:fs";
1924
+ import * as os2 from "node:os";
1925
+ import * as path8 from "node:path";
1926
+ import process2 from "node:process";
1927
+ import { fileURLToPath as fileURLToPath7, pathToFileURL as pathToFileURL7 } from "node:url";
1928
+ var _HERE = fileURLToPath7(import.meta.url);
1929
+ var REPO_ROOT2 = path8.resolve(path8.dirname(_HERE), "..", "..");
1930
+ var PROJECT_RULES = path8.join(".claude", "rules");
1931
+ var PROJECTION_SOURCE = path8.join("dist", "agent-src", "rules");
1932
+ var GLOBAL_RULES = path8.join(".claude", "rules");
1933
+ function _listRules(dir) {
1934
+ try {
1935
+ return fs8.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
1936
+ } catch {
56
1937
  return [];
57
1938
  }
58
- const raw = readFileSync(logPath, "utf8");
59
- const lines = raw.split("\n").filter((l) => l.length > 0);
60
- const tail = lines.slice(-RECOVERY_DEPTH_CAP);
61
- const out = [];
62
- for (const line of tail) {
63
- const parsed = tryParseEntry(line);
64
- if (parsed !== null) {
65
- out.push(parsed);
1939
+ }
1940
+ function _ruleType(file) {
1941
+ let text;
1942
+ try {
1943
+ text = fs8.readFileSync(file, "utf-8");
1944
+ } catch {
1945
+ return "";
1946
+ }
1947
+ if (!text.startsWith("---")) return "";
1948
+ const end = text.indexOf("\n---", 3);
1949
+ const fm = end === -1 ? text : text.slice(0, end);
1950
+ const m = /^type:\s*"?([a-z]+)"?/m.exec(fm);
1951
+ return m === null ? "" : m[1];
1952
+ }
1953
+ function compareCarriers(projectDir, globalDir, sourceDir) {
1954
+ const out = {
1955
+ projectDir,
1956
+ globalDir,
1957
+ globalPresent: fs8.existsSync(globalDir),
1958
+ projectPresent: fs8.existsSync(projectDir),
1959
+ shared: 0,
1960
+ identical: [],
1961
+ provenanceOnly: [],
1962
+ bodyDiff: [],
1963
+ projectOnly: [],
1964
+ globalOnly: [],
1965
+ unreadable: [],
1966
+ manualOnlyGlobal: []
1967
+ };
1968
+ const project = new Set(_listRules(projectDir));
1969
+ const global = new Set(_listRules(globalDir));
1970
+ for (const rule of [.../* @__PURE__ */ new Set([...project, ...global])].sort()) {
1971
+ const inP = project.has(rule);
1972
+ const inG = global.has(rule);
1973
+ if (inP && !inG) {
1974
+ out.projectOnly.push(rule);
1975
+ continue;
1976
+ }
1977
+ if (!inP && inG) {
1978
+ out.globalOnly.push(rule);
1979
+ if (sourceDir !== void 0 && _ruleType(path8.join(sourceDir, rule)) === "manual") {
1980
+ out.manualOnlyGlobal.push(rule);
1981
+ }
1982
+ continue;
1983
+ }
1984
+ out.shared += 1;
1985
+ let verdict;
1986
+ try {
1987
+ verdict = comparePair(
1988
+ fs8.readFileSync(path8.join(projectDir, rule)),
1989
+ fs8.readFileSync(path8.join(globalDir, rule))
1990
+ );
1991
+ } catch {
1992
+ out.shared -= 1;
1993
+ out.unreadable.push(rule);
1994
+ continue;
66
1995
  }
1996
+ if (verdict === "identical") out.identical.push(rule);
1997
+ else if (verdict === "provenance-only") out.provenanceOnly.push(rule);
1998
+ else out.bodyDiff.push(rule);
67
1999
  }
68
2000
  return out;
69
2001
  }
70
- function tryParseEntry(line) {
2002
+ function render2(d) {
2003
+ const lines = [];
2004
+ lines.push("cross-carrier rule divergence \u2014 advisory, gates on nothing");
2005
+ lines.push(` project carrier: ${d.projectDir}`);
2006
+ lines.push(` global carrier: ${d.globalDir}`);
2007
+ lines.push("");
2008
+ if (!d.globalPresent) {
2009
+ lines.push(" The global carrier is absent \u2014 this machine loads the project projection");
2010
+ lines.push(" only, so there is no second copy to diverge from. Nothing to report, and");
2011
+ lines.push(" that is a real answer rather than a skipped check.");
2012
+ return lines.join("\n");
2013
+ }
2014
+ if (!d.projectPresent) {
2015
+ lines.push(" The project rule tree does not exist in this checkout \u2014 it is generated and");
2016
+ lines.push(" gitignored, so a fresh clone has none, and `agents/.agent-tools.yml` may");
2017
+ lines.push(" select zero tools deliberately. Run `task generate-tools` and re-run this");
2018
+ lines.push(" report. Reported rather than silently substituted with the projection");
2019
+ lines.push(" source: dist/ holds rules the project tree omits by design, so reading it");
2020
+ lines.push(" as the project carrier would answer a different question than the one asked.");
2021
+ return lines.join("\n");
2022
+ }
2023
+ lines.push(` shared rule names ${d.shared}`);
2024
+ lines.push(` byte-identical ${d.identical.length}`);
2025
+ lines.push(` differ ONLY in the install stamp ${d.provenanceOnly.length}`);
2026
+ lines.push(` differ in BODY ${d.bodyDiff.length}`);
2027
+ if (d.unreadable.length > 0) {
2028
+ lines.push(` unreadable on one side ${d.unreadable.length} (${d.unreadable.join(", ")})`);
2029
+ lines.push(" Not counted as shared and NOT a body difference \u2014 a dangling entry is");
2030
+ lines.push(" a broken install, not a disagreement about content.");
2031
+ }
2032
+ lines.push(` present at project scope only ${d.projectOnly.length}`);
2033
+ lines.push(` present at global scope only ${d.globalOnly.length}`);
2034
+ lines.push("");
2035
+ if (d.bodyDiff.length > 0) {
2036
+ lines.push(" BODY DIVERGENCE \u2014 the two carriers deliver different text for these rules.");
2037
+ lines.push(" Both copies reach the model, with no precedence marker between them, so a");
2038
+ lines.push(" claim one copy retracts can be re-asserted by the other. PRECEDENCE: the");
2039
+ lines.push(" project projection is generated from src/ at this commit and wins; the");
2040
+ lines.push(" global install is a release snapshot. Close it by reinstalling the global");
2041
+ lines.push(" copy, or by reading the project copy as authoritative.");
2042
+ for (const r of d.bodyDiff) lines.push(` - ${r}`);
2043
+ lines.push("");
2044
+ }
2045
+ if (d.provenanceOnly.length > 0) {
2046
+ lines.push(` ${String(d.provenanceOnly.length)} pair(s) differ only by the installer's package:/source_path: stamp.`);
2047
+ lines.push(" Not a content difference and NOT a defect: install.ts stamps every installed");
2048
+ lines.push(" rule, the in-repo projection stamps nothing. Closing it was decided against");
2049
+ lines.push(" on 2026-07-31 \u2014 agents/settings/contexts/dedup-reachability-refusal.md holds");
2050
+ lines.push(" the analysis and the five reopen conditions. Reported as a count on purpose:");
2051
+ lines.push(" naming every expected pair would bury the class above.");
2052
+ lines.push("");
2053
+ }
2054
+ if (d.projectOnly.length > 0) {
2055
+ lines.push(` project-scope only (${String(d.projectOnly.length)}) \u2014 newer than the installed release:`);
2056
+ lines.push(` ${d.projectOnly.join(", ")}`);
2057
+ }
2058
+ const driftGlobalOnly = d.globalOnly.filter((r) => !d.manualOnlyGlobal.includes(r));
2059
+ if (driftGlobalOnly.length > 0) {
2060
+ lines.push(` global-scope only (${String(driftGlobalOnly.length)}) \u2014 the release carries these, this checkout does not project them:`);
2061
+ lines.push(` ${driftGlobalOnly.join(", ")}`);
2062
+ }
2063
+ if (d.manualOnlyGlobal.length > 0) {
2064
+ lines.push(
2065
+ ` delivered by the global carrier ONLY, by design (${String(d.manualOnlyGlobal.length)}, ADR-004 \`type: manual\`):`
2066
+ );
2067
+ lines.push(` ${d.manualOnlyGlobal.join(", ")}`);
2068
+ lines.push(" The project projection filters these out and the global installer does not.");
2069
+ lines.push(" Two writers, different filters \u2014 so on a machine with both carriers these");
2070
+ lines.push(" rules reach the model, and on a project-only machine they do not. Expected,");
2071
+ lines.push(" and worth knowing before citing any project-scope reach figure.");
2072
+ }
2073
+ if (d.projectOnly.length > 0 || d.globalOnly.length > 0) {
2074
+ lines.push("");
2075
+ lines.push(" Asymmetric reach is why a project-scope conformance figure does not transfer");
2076
+ lines.push(" to another machine: the rule set the model was told differs by carrier.");
2077
+ }
2078
+ if (d.bodyDiff.length === 0) {
2079
+ lines.push("");
2080
+ lines.push(" No body divergence. The condition is transient \u2014 it returns whenever the");
2081
+ lines.push(" checkout moves ahead of the installed release \u2014 so this is a reading of");
2082
+ lines.push(" right now, not a property of the repo. Re-run it, do not cite it.");
2083
+ }
2084
+ return lines.join("\n");
2085
+ }
2086
+ function main7(argv) {
2087
+ const args = argv ?? process2.argv.slice(2);
2088
+ let projectDir = path8.join(REPO_ROOT2, PROJECT_RULES);
2089
+ let globalDir = path8.join(os2.homedir(), GLOBAL_RULES);
2090
+ let sourceDir = path8.join(REPO_ROOT2, PROJECTION_SOURCE);
2091
+ const value = (i) => {
2092
+ const v = args[i + 1];
2093
+ return v === void 0 || v.startsWith("-") ? null : v;
2094
+ };
2095
+ for (let i = 0; i < args.length; i += 1) {
2096
+ const a = args[i];
2097
+ if (a === "--project" || a === "--global" || a === "--source") {
2098
+ const v = value(i);
2099
+ if (v === null) {
2100
+ process2.stderr.write(`report_carrier_divergence: ${a} needs a directory
2101
+ `);
2102
+ return 1;
2103
+ }
2104
+ const resolved = path8.resolve(v);
2105
+ if (a === "--project") projectDir = resolved;
2106
+ else if (a === "--global") globalDir = resolved;
2107
+ else sourceDir = resolved;
2108
+ i += 1;
2109
+ } else if (a === "--help" || a === "-h") {
2110
+ process2.stdout.write(
2111
+ "usage: report_carrier_divergence [--project DIR] [--global DIR] [--source DIR]\n"
2112
+ );
2113
+ return 0;
2114
+ } else if (a !== void 0 && a.startsWith("--") && a !== "--quiet") {
2115
+ process2.stderr.write(`report_carrier_divergence: unknown flag ${a}
2116
+ `);
2117
+ return 1;
2118
+ }
2119
+ }
2120
+ process2.stdout.write(`${render2(compareCarriers(projectDir, globalDir, sourceDir))}
2121
+ `);
2122
+ return 0;
2123
+ }
2124
+ function _isCliEntry7() {
2125
+ if (process2.argv[1] === void 0) return false;
2126
+ if (import.meta.url === pathToFileURL7(path8.resolve(process2.argv[1])).href) return true;
71
2127
  try {
72
- const obj = JSON.parse(line);
73
- if (typeof obj.ts !== "string" || typeof obj.kind !== "string" || typeof obj.path !== "string") {
2128
+ return fs8.realpathSync(_HERE) === fs8.realpathSync(path8.resolve(process2.argv[1]));
2129
+ } catch {
2130
+ return false;
2131
+ }
2132
+ }
2133
+ if (_isCliEntry7()) {
2134
+ process2.exit(main7());
2135
+ }
2136
+
2137
+ // src/scripts/report_skill_activation.ts
2138
+ import * as fs9 from "node:fs";
2139
+ import * as os3 from "node:os";
2140
+ import * as path9 from "node:path";
2141
+ import { fileURLToPath as fileURLToPath8, pathToFileURL as pathToFileURL8 } from "node:url";
2142
+ var _HERE2 = fileURLToPath8(import.meta.url);
2143
+ var REPO_ROOT3 = path9.resolve(path9.dirname(_HERE2), "..", "..");
2144
+ var SKILLS_ROOT = "src/skills";
2145
+ var TRIGGER_KEYS = ["triggers:", "trigger_description:", "file_pattern:", "path_prefix:"];
2146
+ var DETERMINISTIC_RE = /^\s*(?:[-*]\s*)?(?:\*\*)?(MUST|NEVER|ALWAYS)\b/m;
2147
+ function censusSkills(root) {
2148
+ const out = { total: 0, withTriggerKey: [], withDeterministicObligation: [] };
2149
+ if (!fs9.existsSync(root)) {
2150
+ return out;
2151
+ }
2152
+ for (const entry of fs9.readdirSync(root, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
2153
+ if (!entry.isDirectory()) {
2154
+ continue;
2155
+ }
2156
+ const file = path9.join(root, entry.name, "SKILL.md");
2157
+ if (!fs9.existsSync(file)) {
2158
+ continue;
2159
+ }
2160
+ out.total += 1;
2161
+ const text = fs9.readFileSync(file, "utf8");
2162
+ const fmEnd = text.startsWith("---") ? text.indexOf("\n---", 3) : -1;
2163
+ const fm = fmEnd === -1 ? "" : text.slice(0, fmEnd);
2164
+ if (TRIGGER_KEYS.some((k) => new RegExp(`^${k}`, "m").test(fm))) {
2165
+ out.withTriggerKey.push(entry.name);
2166
+ }
2167
+ const body = fmEnd === -1 ? text : text.slice(fmEnd + 4);
2168
+ if (DETERMINISTIC_RE.test(body)) {
2169
+ out.withDeterministicObligation.push(entry.name);
2170
+ }
2171
+ }
2172
+ return out;
2173
+ }
2174
+ function defaultStore2(cwd) {
2175
+ return path9.join(os3.homedir(), ".claude", "projects", cwd.replace(/\//g, "-"));
2176
+ }
2177
+ function measureUsage(store, limit) {
2178
+ const rep = { store, sessions: 0, assistantTurns: 0, invocations: 0, bySkill: {} };
2179
+ if (!fs9.existsSync(store)) {
2180
+ return rep;
2181
+ }
2182
+ const files = fs9.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => path9.join(store, f)).sort((a, b) => fs9.statSync(b).mtimeMs - fs9.statSync(a).mtimeMs).slice(0, limit);
2183
+ rep.sessions = files.length;
2184
+ for (const file of files) {
2185
+ for (const line of fs9.readFileSync(file, "utf8").split("\n")) {
2186
+ if (!line.trim()) {
2187
+ continue;
2188
+ }
2189
+ let entry;
2190
+ try {
2191
+ entry = JSON.parse(line);
2192
+ } catch {
2193
+ continue;
2194
+ }
2195
+ if (entry["type"] !== "assistant") {
2196
+ continue;
2197
+ }
2198
+ rep.assistantTurns += 1;
2199
+ const msg = entry["message"];
2200
+ const content = msg !== null && typeof msg === "object" ? msg["content"] : null;
2201
+ if (!Array.isArray(content)) {
2202
+ continue;
2203
+ }
2204
+ for (const part of content) {
2205
+ if (part === null || typeof part !== "object") {
2206
+ continue;
2207
+ }
2208
+ const p = part;
2209
+ if (p["type"] !== "tool_use" || p["name"] !== "Skill") {
2210
+ continue;
2211
+ }
2212
+ rep.invocations += 1;
2213
+ const input = p["input"];
2214
+ const name = input !== null && typeof input === "object" ? input["skill"] : null;
2215
+ const key = typeof name === "string" && name !== "" ? name : "<unnamed>";
2216
+ rep.bySkill[key] = (rep.bySkill[key] ?? 0) + 1;
2217
+ }
2218
+ }
2219
+ }
2220
+ return rep;
2221
+ }
2222
+ function _pct(n, d) {
2223
+ return d === 0 ? "n/a" : `${(100 * n / d).toFixed(1)}%`;
2224
+ }
2225
+ function render3(census, usage) {
2226
+ const lines = [];
2227
+ lines.push("skill activation census \u2014 advisory, gates on nothing");
2228
+ lines.push("");
2229
+ lines.push(` skills shipped ${census.total}`);
2230
+ lines.push(
2231
+ ` with a machine-matchable trigger key ${census.withTriggerKey.length} (${_pct(census.withTriggerKey.length, census.total)})`
2232
+ );
2233
+ lines.push(
2234
+ ` with a deterministic obligation ${census.withDeterministicObligation.length} (${_pct(census.withDeterministicObligation.length, census.total)})`
2235
+ );
2236
+ if (census.withDeterministicObligation.length > 0) {
2237
+ lines.push(` ${census.withDeterministicObligation.join(", ")}`);
2238
+ }
2239
+ lines.push("");
2240
+ let totalInv = 0;
2241
+ const distinct = /* @__PURE__ */ new Set();
2242
+ for (const u of usage) {
2243
+ for (const k of Object.keys(u.bySkill)) {
2244
+ distinct.add(k);
2245
+ }
2246
+ totalInv += u.invocations;
2247
+ lines.push(
2248
+ ` ${path9.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)}`
2249
+ );
2250
+ }
2251
+ lines.push("");
2252
+ lines.push(` invocations total ${totalInv}`);
2253
+ lines.push(
2254
+ ` distinct skills invoked ${distinct.size} of ${census.total} (${_pct(distinct.size, census.total)})`
2255
+ );
2256
+ if (distinct.size > 0) {
2257
+ lines.push(` ${[...distinct].sort().join(", ")}`);
2258
+ }
2259
+ lines.push("");
2260
+ if (census.withTriggerKey.length === 0) {
2261
+ lines.push(" VERDICT: activation is not measurable as a rate. No skill declares a");
2262
+ lines.push(" machine-matchable trigger, so a missed-activation detector has nothing to");
2263
+ lines.push(" match against, and matching the description prose is the class this suite");
2264
+ lines.push(" excludes. The counts above are the finding, not an input to a percentage.");
2265
+ }
2266
+ lines.push("");
2267
+ lines.push(" NOT MEASURED: whether each skill reached the model WITH its description.");
2268
+ lines.push(" The host's injected catalogue is not persisted in the transcript, so the");
2269
+ lines.push(" bare-name hypothesis is a single-session observation. Falsifier: log the");
2270
+ lines.push(" catalogue once per session, then count descriptions against bare names.");
2271
+ return lines.join("\n");
2272
+ }
2273
+ function main8(argv) {
2274
+ let limit = 30;
2275
+ const stores = [];
2276
+ for (let i = 0; i < argv.length; i += 1) {
2277
+ const a = argv[i];
2278
+ if (a === "--limit" && argv[i + 1] !== void 0) {
2279
+ limit = Number.parseInt(argv[i + 1], 10);
2280
+ i += 1;
2281
+ } else if (a === "--store" && argv[i + 1] !== void 0) {
2282
+ stores.push(argv[i + 1]);
2283
+ i += 1;
2284
+ } else if (a === "--help" || a === "-h") {
2285
+ process.stdout.write("usage: report_skill_activation [--limit N] [--store PATH]...\n");
2286
+ return 0;
2287
+ }
2288
+ }
2289
+ if (!Number.isFinite(limit) || limit <= 0) {
2290
+ process.stderr.write("report_skill_activation: --limit must be a positive integer\n");
2291
+ return 1;
2292
+ }
2293
+ if (stores.length === 0) {
2294
+ stores.push(defaultStore2(REPO_ROOT3));
2295
+ }
2296
+ const census = censusSkills(path9.join(REPO_ROOT3, SKILLS_ROOT));
2297
+ const usage = stores.map((s) => measureUsage(s, limit));
2298
+ process.stdout.write(`${render3(census, usage)}
2299
+ `);
2300
+ return 0;
2301
+ }
2302
+ if (process.argv[1] !== void 0 && import.meta.url === pathToFileURL8(path9.resolve(process.argv[1])).href) {
2303
+ process.exit(main8(process.argv.slice(2)));
2304
+ }
2305
+
2306
+ // src/scripts/report_skill_obligation_violations.ts
2307
+ import * as fs10 from "node:fs";
2308
+ import * as os4 from "node:os";
2309
+ import * as path10 from "node:path";
2310
+ import process3 from "node:process";
2311
+ import { fileURLToPath as fileURLToPath9, pathToFileURL as pathToFileURL9 } from "node:url";
2312
+ var _HERE3 = fileURLToPath9(import.meta.url);
2313
+ var REPO_ROOT4 = path10.resolve(path10.dirname(_HERE3), "..", "..");
2314
+ var OBLIGATION_LINE_RE = new RegExp(`${DETERMINISTIC_RE.source}.*`, "gm");
2315
+ var PATH_RE = /`([A-Za-z0-9_./-]*\/[A-Za-z0-9_.-]+\.[a-z]{2,6})`/g;
2316
+ var CMD_RE = /`([a-z][a-z0-9-]*(?: [a-z][a-z0-9:._-]*){1,3})`/g;
2317
+ var CMD_HEADS = /* @__PURE__ */ new Set([
2318
+ "cargo",
2319
+ "npm",
2320
+ "pnpm",
2321
+ "yarn",
2322
+ "git",
2323
+ "php",
2324
+ "composer",
2325
+ "task",
2326
+ "make",
2327
+ "npx",
2328
+ "gh",
2329
+ "python3",
2330
+ "docker"
2331
+ ]);
2332
+ var PIVOT_RE = /\s—\s|\s–\s|→|;\s|\s—use\b|\buse instead\b/;
2333
+ function _verb(line) {
2334
+ const m = /\b(NEVER|MUST|ALWAYS)\b/.exec(line);
2335
+ return m?.[1] ?? "NEVER";
2336
+ }
2337
+ function _artefacts(line) {
2338
+ const out = [];
2339
+ for (const m of line.matchAll(PATH_RE)) {
2340
+ out.push({ artefact: m[1], kind: "path" });
2341
+ }
2342
+ for (const m of line.matchAll(CMD_RE)) {
2343
+ const cmd = m[1];
2344
+ if (CMD_HEADS.has(cmd.split(" ")[0])) {
2345
+ out.push({ artefact: cmd, kind: "command" });
2346
+ }
2347
+ }
2348
+ return out;
2349
+ }
2350
+ function extractObligations(repoRoot) {
2351
+ const skillsRoot = path10.join(repoRoot, SKILLS_ROOT);
2352
+ const census = censusSkills(skillsRoot);
2353
+ const out = {
2354
+ skills: census.withDeterministicObligation,
2355
+ totalLines: 0,
2356
+ withArtefact: [],
2357
+ linesWithArtefact: 0,
2358
+ forbidden: [],
2359
+ prescribed: [],
2360
+ required: []
2361
+ };
2362
+ for (const skill of census.withDeterministicObligation) {
2363
+ let text;
2364
+ try {
2365
+ text = fs10.readFileSync(path10.join(skillsRoot, skill, "SKILL.md"), "utf-8");
2366
+ } catch {
2367
+ continue;
2368
+ }
2369
+ const fmEnd = text.startsWith("---") ? text.indexOf("\n---", 3) : -1;
2370
+ const body = fmEnd === -1 ? text : text.slice(fmEnd + 4);
2371
+ for (const raw of body.match(OBLIGATION_LINE_RE) ?? []) {
2372
+ const line = raw.trim();
2373
+ out.totalLines += 1;
2374
+ const verb = _verb(line);
2375
+ const pivot = PIVOT_RE.exec(line);
2376
+ const head = pivot === null ? line : line.slice(0, pivot.index);
2377
+ const found = _artefacts(line);
2378
+ if (found.length > 0) {
2379
+ out.linesWithArtefact += 1;
2380
+ }
2381
+ for (const a of found) {
2382
+ const polarity = verb === "NEVER" ? head.includes(`\`${a.artefact}\``) ? "forbidden" : "prescribed" : "required";
2383
+ const ob = { skill, line, verb, artefact: a.artefact, kind: a.kind, polarity };
2384
+ out.withArtefact.push(ob);
2385
+ if (polarity === "forbidden") out.forbidden.push(ob);
2386
+ else if (polarity === "prescribed") out.prescribed.push(ob);
2387
+ else out.required.push(ob);
2388
+ }
2389
+ }
2390
+ }
2391
+ return out;
2392
+ }
2393
+ function _parseSession(lines) {
2394
+ return lines.map((line) => {
2395
+ if (!line.trim()) return null;
2396
+ try {
2397
+ return JSON.parse(line);
2398
+ } catch {
74
2399
  return null;
75
2400
  }
76
- return obj;
2401
+ });
2402
+ }
2403
+ var WRITE_TOOLS = /* @__PURE__ */ new Set([
2404
+ "Edit",
2405
+ "Write",
2406
+ "NotebookEdit",
2407
+ "MultiEdit",
2408
+ "str-replace-editor",
2409
+ "str_replace_editor",
2410
+ "save-file",
2411
+ "save_file"
2412
+ ]);
2413
+ var SHELL_TOOLS = /* @__PURE__ */ new Set(["Bash", "BashTool", "launch-process", "launch_process"]);
2414
+ var SKILL_BODY_RE = /Base directory for this skill:\s*\S*?\/skills\/([A-Za-z0-9_-]+)/;
2415
+ function loadedSkillsFrom(entries) {
2416
+ const first = /* @__PURE__ */ new Map();
2417
+ const note = (name, at) => {
2418
+ const key = name.replace(/:/g, "-");
2419
+ if (!first.has(key)) first.set(key, at);
2420
+ };
2421
+ entries.forEach((entry, i) => {
2422
+ if (entry === null || isSidechain(entry)) return;
2423
+ if (entry["type"] === "user") {
2424
+ const m = SKILL_BODY_RE.exec(entryText(entry));
2425
+ if (m !== null) note(m[1], i);
2426
+ return;
2427
+ }
2428
+ if (entry["type"] !== "assistant") return;
2429
+ for (const t of toolUses(entry)) {
2430
+ if (t.name !== "Skill") continue;
2431
+ const s = t.input["skill"];
2432
+ if (typeof s === "string" && s !== "") note(s, i);
2433
+ }
2434
+ });
2435
+ return first;
2436
+ }
2437
+ var PATH_FIELDS = ["file_path", "path", "filePath", "notebook_path", "target_file"];
2438
+ var COMMAND_FIELDS = ["command", "cmd"];
2439
+ function _targetText(input, kind) {
2440
+ const fields = kind === "path" ? PATH_FIELDS : COMMAND_FIELDS;
2441
+ return fields.map((f) => input[f]).filter((v) => typeof v === "string").join("\n");
2442
+ }
2443
+ function scanSessionForViolations(session, lines, forbidden, parsed) {
2444
+ const entries = parsed ?? _parseSession(lines);
2445
+ const loaded = loadedSkillsFrom(entries);
2446
+ if (loaded.size === 0) return [];
2447
+ const relevant = forbidden.filter((o) => loaded.has(o.skill));
2448
+ if (relevant.length === 0) return [];
2449
+ const flags = [];
2450
+ entries.forEach((entry, i) => {
2451
+ if (entry === null || isSidechain(entry)) return;
2452
+ if (entry["type"] !== "assistant") return;
2453
+ for (const t of toolUses(entry)) {
2454
+ const isWrite = WRITE_TOOLS.has(t.name);
2455
+ const isShell = SHELL_TOOLS.has(t.name);
2456
+ if (!isWrite && !isShell) continue;
2457
+ for (const o of relevant) {
2458
+ const loadedAt = loaded.get(o.skill);
2459
+ if (i <= loadedAt) continue;
2460
+ if (o.kind === "path" && !isWrite) continue;
2461
+ if (o.kind === "command" && !isShell) continue;
2462
+ const text = _targetText(t.input, o.kind);
2463
+ if (!text.includes(o.artefact)) continue;
2464
+ const at = text.indexOf(o.artefact);
2465
+ flags.push({
2466
+ session,
2467
+ skill: o.skill,
2468
+ artefact: o.artefact,
2469
+ kind: o.kind,
2470
+ at: i,
2471
+ loadedAt,
2472
+ tool: t.name,
2473
+ evidence: text.slice(Math.max(0, at - 40), at + o.artefact.length + 40).replace(/\n/g, " ")
2474
+ });
2475
+ }
2476
+ }
2477
+ });
2478
+ return flags;
2479
+ }
2480
+ function defaultStore3(cwd) {
2481
+ return path10.join(os4.homedir(), ".claude", "projects", cwd.replace(/[/.]/g, "-"));
2482
+ }
2483
+ function scanStore2(repoRoot, store, limit) {
2484
+ const census = extractObligations(repoRoot);
2485
+ const report = {
2486
+ store,
2487
+ sessions: 0,
2488
+ sessionsWithASkill: 0,
2489
+ census,
2490
+ flags: []
2491
+ };
2492
+ let files;
2493
+ try {
2494
+ files = fs10.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => ({ p: path10.join(store, f), m: fs10.statSync(path10.join(store, f)).mtimeMs })).sort((a, b) => b.m - a.m).slice(0, limit).map((x) => x.p);
77
2495
  } catch {
78
- return null;
2496
+ return report;
2497
+ }
2498
+ report.sessions = files.length;
2499
+ for (const file of files) {
2500
+ const lines = fs10.readFileSync(file, "utf-8").split("\n");
2501
+ const id = path10.basename(file).replace(/\.jsonl$/, "").slice(0, 8);
2502
+ const entries = _parseSession(lines);
2503
+ if (loadedSkillsFrom(entries).size > 0) report.sessionsWithASkill += 1;
2504
+ report.flags.push(...scanSessionForViolations(id, lines, census.forbidden, entries));
2505
+ }
2506
+ return report;
2507
+ }
2508
+ function _pct2(n, d) {
2509
+ return d === 0 ? "n/a" : `${(100 * n / d).toFixed(1)}%`;
2510
+ }
2511
+ function render4(r) {
2512
+ const c = r.census;
2513
+ const lines = [];
2514
+ lines.push("SK-2 loaded-but-violated \u2014 advisory, gates on nothing");
2515
+ lines.push("");
2516
+ lines.push(" COVERAGE (the headline \u2014 read this before any violation count)");
2517
+ lines.push(` skills with a deterministic obligation ${c.skills.length}`);
2518
+ lines.push(` obligation lines in them ${c.totalLines}`);
2519
+ lines.push(
2520
+ ` \u2026LINES naming a concrete artefact ${c.linesWithArtefact} (${_pct2(c.linesWithArtefact, c.totalLines)})`
2521
+ );
2522
+ lines.push(` \u2026artefacts named across those lines ${c.withArtefact.length}`);
2523
+ lines.push(` \u2026of those, the FORBIDDEN artefact ${c.forbidden.length}`);
2524
+ lines.push(` \u2026the PRESCRIBED alternative (excluded) ${c.prescribed.length}`);
2525
+ lines.push(` \u2026REQUIRED by a MUST/ALWAYS (excluded) ${c.required.length}`);
2526
+ lines.push(
2527
+ ` testable without judgement ${c.forbidden.length} artefact(s) over ${c.totalLines} obligation line(s)`
2528
+ );
2529
+ lines.push("");
2530
+ lines.push(" The other lines are deterministic in wording and need a reading to test");
2531
+ lines.push(' ("NEVER return `clean` out of politeness"). That is the FC-8 class this suite');
2532
+ lines.push(" excludes, so it is reported as uncovered rather than approximated.");
2533
+ lines.push("");
2534
+ if (c.forbidden.length > 0) {
2535
+ lines.push(" Mechanised obligations:");
2536
+ for (const o of c.forbidden) {
2537
+ lines.push(` [${o.skill}] ${o.kind}: ${o.artefact}`);
2538
+ }
2539
+ lines.push("");
2540
+ }
2541
+ if (c.prescribed.length > 0) {
2542
+ lines.push(" Excluded as prescriptive (the artefact is the remedy, not the offence):");
2543
+ for (const o of c.prescribed) {
2544
+ lines.push(` [${o.skill}] ${o.artefact}`);
2545
+ }
2546
+ lines.push("");
2547
+ }
2548
+ if (c.required.length > 0) {
2549
+ lines.push(" Excluded as REQUIRED \u2014 a MUST/ALWAYS line, so its violation is the");
2550
+ lines.push(" artefact being ABSENT, and a transcript cannot show that something never");
2551
+ lines.push(" happened without also knowing it was due:");
2552
+ for (const o of c.required) {
2553
+ lines.push(` [${o.skill}] ${o.verb} ${o.artefact}`);
2554
+ }
2555
+ lines.push("");
2556
+ }
2557
+ lines.push(
2558
+ ` Scanned ${r.sessions} session(s); ${r.sessionsWithASkill} had a skill in context.`
2559
+ );
2560
+ lines.push(` Flags: ${r.flags.length}`);
2561
+ for (const f of r.flags) {
2562
+ lines.push(` ${f.session} [${f.skill}] ${f.artefact} via ${f.tool} (entry ${f.at}, loaded ${f.loadedAt})`);
2563
+ lines.push(` \u2026${f.evidence}\u2026`);
2564
+ }
2565
+ lines.push("");
2566
+ lines.push(" PRECISION: every flag above is hand-read before any number from this script");
2567
+ lines.push(" is cited (Phase 3.3). A detector that cannot state its false-positive rate");
2568
+ lines.push(" ships as detection-only, and this one says so.");
2569
+ return lines.join("\n");
2570
+ }
2571
+ function main9(argv) {
2572
+ const args = argv ?? process3.argv.slice(2);
2573
+ let limit = 30;
2574
+ let json = false;
2575
+ const stores = [];
2576
+ for (let i = 0; i < args.length; i += 1) {
2577
+ const a = args[i];
2578
+ const value = (at) => {
2579
+ const v = args[at + 1];
2580
+ return v === void 0 || v.startsWith("-") ? null : v;
2581
+ };
2582
+ if (a === "--limit") {
2583
+ const v = value(i);
2584
+ if (v === null) {
2585
+ process3.stderr.write("report_skill_obligation_violations: --limit needs a number\n");
2586
+ return 1;
2587
+ }
2588
+ limit = Number.parseInt(v, 10);
2589
+ i += 1;
2590
+ } else if (a === "--store") {
2591
+ const v = value(i);
2592
+ if (v === null) {
2593
+ process3.stderr.write("report_skill_obligation_violations: --store needs a path\n");
2594
+ return 1;
2595
+ }
2596
+ stores.push(v);
2597
+ i += 1;
2598
+ } else if (a === "--json") {
2599
+ json = true;
2600
+ } else if (a === "--help" || a === "-h") {
2601
+ process3.stdout.write(
2602
+ "usage: report_skill_obligation_violations [--limit N] [--store PATH]... [--json]\n"
2603
+ );
2604
+ return 0;
2605
+ } else if (a !== void 0 && a.startsWith("--") && a !== "--quiet") {
2606
+ process3.stderr.write(`report_skill_obligation_violations: unknown flag ${a}
2607
+ `);
2608
+ return 1;
2609
+ }
2610
+ }
2611
+ if (!Number.isFinite(limit) || limit <= 0) {
2612
+ process3.stderr.write("report_skill_obligation_violations: --limit must be a positive integer\n");
2613
+ return 1;
2614
+ }
2615
+ if (stores.length === 0) stores.push(defaultStore3(REPO_ROOT4));
2616
+ for (const store of stores) {
2617
+ const r = scanStore2(REPO_ROOT4, store, limit);
2618
+ process3.stdout.write(json ? `${JSON.stringify(r, null, 2)}
2619
+ ` : `${render4(r)}
2620
+ `);
2621
+ }
2622
+ return 0;
2623
+ }
2624
+ function _isCliEntry8() {
2625
+ if (process3.argv[1] === void 0) return false;
2626
+ if (import.meta.url === pathToFileURL9(path10.resolve(process3.argv[1])).href) return true;
2627
+ try {
2628
+ return fs10.realpathSync(_HERE3) === fs10.realpathSync(path10.resolve(process3.argv[1]));
2629
+ } catch {
2630
+ return false;
79
2631
  }
80
2632
  }
2633
+ if (_isCliEntry8()) {
2634
+ process3.exit(main9());
2635
+ }
81
2636
 
82
- // src/scripts/router_target_paths.mjs
83
- var ROUTE_TARGET_TEMPLATES = Object.freeze({
84
- skill: Object.freeze(["dist/agent-src/skills/{id}/SKILL.md"]),
85
- command: Object.freeze(["dist/agent-src/commands/{id}.md"]),
86
- guideline: Object.freeze(["docs/guidelines/{id}.md"]),
87
- contract: Object.freeze([
88
- "docs/contracts/{id}.md",
89
- "dist/agent-src/contexts/contracts/{id}.md"
90
- ])
91
- });
92
- function parseRouteTarget(target) {
93
- const sep = target.indexOf(":");
94
- if (sep <= 0) return null;
95
- const kind = target.slice(0, sep);
96
- const id = target.slice(sep + 1);
97
- if (!id) return null;
98
- if (!Object.prototype.hasOwnProperty.call(ROUTE_TARGET_TEMPLATES, kind)) return null;
99
- return { kind, id };
2637
+ // src/scripts/report_conformance_funnel.ts
2638
+ var _HERE4 = fileURLToPath10(import.meta.url);
2639
+ var REPO_ROOT5 = path11.resolve(path11.dirname(_HERE4), "..", "..");
2640
+ function buildFunnel(opts = {}) {
2641
+ const repoRoot = opts.repoRoot ?? REPO_ROOT5;
2642
+ const projectRulesDir = opts.projectRulesDir ?? path11.join(repoRoot, PROJECT_RULES);
2643
+ const globalRulesDir = opts.globalRulesDir ?? path11.join(os5.homedir(), GLOBAL_RULES);
2644
+ const sourceDir = opts.sourceDir ?? path11.join(repoRoot, PROJECTION_SOURCE);
2645
+ const store = opts.store ?? defaultStore(repoRoot);
2646
+ const limit = opts.limit ?? 30;
2647
+ return {
2648
+ delivered: measureDelivered(projectRulesDir, globalRulesDir),
2649
+ divergence: compareCarriers(projectRulesDir, globalRulesDir, sourceDir),
2650
+ skills: censusSkills(path11.join(repoRoot, SKILLS_ROOT)),
2651
+ usage: measureUsage(store, limit),
2652
+ storePresent: fs11.existsSync(store),
2653
+ sk2: scanStore2(repoRoot, store, limit)
2654
+ };
100
2655
  }
101
- function routeTargetPathsPosix(target) {
102
- const parsed = parseRouteTarget(target);
103
- if (parsed === null) return [];
104
- return ROUTE_TARGET_TEMPLATES[parsed.kind].map((tpl) => tpl.replace("{id}", parsed.id));
2656
+ function _pct3(n, d) {
2657
+ return d === 0 ? "n/a" : `${(100 * n / d).toFixed(1)}%`;
2658
+ }
2659
+ function _carrierLine(label, c) {
2660
+ return c.present ? ` ${label} ${String(c.files).padStart(4)} rules ${String(c.tokens).padStart(7)} tok` : ` ${label} ABSENT \u2014 not a measured zero, this carrier does not exist here`;
2661
+ }
2662
+ function render5(r) {
2663
+ const lines = [];
2664
+ lines.push("conformance funnel \u2014 delivery \u2192 activation \u2192 compliance (report, not a gate)");
2665
+ lines.push("");
2666
+ lines.push("DELIVERY \u2014 rule text the carriers put in front of the model");
2667
+ lines.push(_carrierLine("project", r.delivered.project));
2668
+ lines.push(_carrierLine("global ", r.delivered.global));
2669
+ lines.push(` union ${String(r.delivered.union_tokens).padStart(7)} tok`);
2670
+ lines.push(" Carriers as they stand NOW \u2014 a session's own payload is NOT recoverable:");
2671
+ lines.push(" the transcript records no system or tools field (conformance_scan).");
2672
+ const d = r.divergence;
2673
+ if (!d.globalPresent) {
2674
+ lines.push(" Divergence: the global carrier is absent \u2014 this machine loads the project");
2675
+ lines.push(" projection only, so there is no second copy to diverge from. A real");
2676
+ lines.push(" answer, not a skipped check.");
2677
+ } else if (!d.projectPresent) {
2678
+ lines.push(" Divergence: the project rule tree does not exist in this checkout \u2014 it is");
2679
+ lines.push(" generated and gitignored. Run `task generate-tools`, then re-run. Not");
2680
+ lines.push(" substituted with dist/, which would answer a different question.");
2681
+ } else {
2682
+ lines.push(
2683
+ ` Divergence: ${d.shared} shared \xB7 ${d.bodyDiff.length} body-diff \xB7 ${d.provenanceOnly.length} provenance-only \xB7 ${d.projectOnly.length} project-only \xB7 ${d.globalOnly.length} global-only` + (d.manualOnlyGlobal.length > 0 ? ` (${d.manualOnlyGlobal.length} by design, ADR-004 manual)` : "")
2684
+ );
2685
+ if (d.bodyDiff.length > 0) {
2686
+ lines.push(` BODY DIVERGENCE \u2014 both copies reach the model: ${d.bodyDiff.join(", ")}`);
2687
+ lines.push(" Precedence: the project projection is generated from src/ at this commit");
2688
+ lines.push(" and wins; the global install is a release snapshot.");
2689
+ }
2690
+ if (d.unreadable.length > 0) {
2691
+ lines.push(` Unreadable on one side (${d.unreadable.length}) \u2014 a broken install, not a disagreement.`);
2692
+ }
2693
+ }
2694
+ lines.push("");
2695
+ lines.push("ACTIVATION \u2014 are the delivered skills invoked?");
2696
+ lines.push(` skills shipped ${r.skills.total}`);
2697
+ lines.push(
2698
+ ` with a machine-matchable trigger key ${r.skills.withTriggerKey.length} (${_pct3(r.skills.withTriggerKey.length, r.skills.total)})`
2699
+ );
2700
+ if (!r.storePresent) {
2701
+ lines.push(` NO DATA: no transcript store at ${r.usage.store} \u2014 usage is not`);
2702
+ lines.push(" measurable here. An absent store is an answer, not a zero.");
2703
+ } else {
2704
+ const distinct = Object.keys(r.usage.bySkill).length;
2705
+ lines.push(
2706
+ ` sessions=${r.usage.sessions} assistant turns=${r.usage.assistantTurns} Skill invocations=${r.usage.invocations} distinct skills=${distinct} of ${r.skills.total} (${_pct3(distinct, r.skills.total)})`
2707
+ );
2708
+ }
2709
+ if (r.skills.withTriggerKey.length === 0) {
2710
+ lines.push(" Honest scope: activation is NOT measurable as a rate \u2014 no skill declares");
2711
+ lines.push(" a machine-matchable trigger, and matching description prose is the class");
2712
+ lines.push(" this suite excludes (report_skill_activation).");
2713
+ }
2714
+ lines.push(" NOT MEASURED: whether each skill reached the model WITH its description \u2014");
2715
+ lines.push(" the host's injected catalogue is not persisted in the transcript, so the");
2716
+ lines.push(" bare-name (truncation) hypothesis stays a single-session observation.");
2717
+ lines.push("");
2718
+ const c = r.sk2.census;
2719
+ lines.push("COMPLIANCE \u2014 SK-2 loaded-but-violated (coverage is the headline)");
2720
+ lines.push(` skills with a deterministic obligation ${c.skills.length}`);
2721
+ lines.push(
2722
+ ` testable without judgement ${c.forbidden.length} artefact(s) over ${c.totalLines} obligation line(s)`
2723
+ );
2724
+ lines.push(" The rest are deterministic in WORDING and need a reading to test \u2014 the");
2725
+ lines.push(" FC-8 class this suite excludes, reported as uncovered, not approximated");
2726
+ lines.push(" (report_skill_obligation_violations).");
2727
+ if (!r.storePresent) {
2728
+ lines.push(" NO DATA: no transcript store \u2014 loaded-but-violated cannot be observed");
2729
+ lines.push(" here. The coverage census above is static and still holds.");
2730
+ } else {
2731
+ lines.push(
2732
+ ` sessions scanned=${r.sk2.sessions} with a skill in context=${r.sk2.sessionsWithASkill} flags=${r.sk2.flags.length}`
2733
+ );
2734
+ for (const f of r.sk2.flags) {
2735
+ lines.push(` ${f.session} [${f.skill}] ${f.artefact} via ${f.tool}`);
2736
+ }
2737
+ }
2738
+ lines.push(" PRECISION: every flag is hand-read before any number here is cited \u2014 the");
2739
+ lines.push(" detector cannot state its false-positive rate, so it ships detection-only.");
2740
+ lines.push("");
2741
+ lines.push("This is a REPORT. It gates on nothing and must never be wired into a CI");
2742
+ lines.push("workflow without a measured false-positive rate first (locked policy).");
2743
+ return lines.join("\n");
2744
+ }
2745
+ function main10(argv) {
2746
+ const args = argv ?? process4.argv.slice(2);
2747
+ const opts = {};
2748
+ const value = (i) => {
2749
+ const v = args[i + 1];
2750
+ return v === void 0 || v.startsWith("-") ? null : v;
2751
+ };
2752
+ for (let i = 0; i < args.length; i += 1) {
2753
+ const a = args[i];
2754
+ if (a === "--store" || a === "--project" || a === "--global" || a === "--source") {
2755
+ const v = value(i);
2756
+ if (v === null) {
2757
+ process4.stderr.write(`report_conformance_funnel: ${a} needs a value
2758
+ `);
2759
+ return 1;
2760
+ }
2761
+ const resolved = path11.resolve(v);
2762
+ if (a === "--store") opts.store = resolved;
2763
+ else if (a === "--project") opts.projectRulesDir = resolved;
2764
+ else if (a === "--global") opts.globalRulesDir = resolved;
2765
+ else opts.sourceDir = resolved;
2766
+ i += 1;
2767
+ } else if (a === "--limit") {
2768
+ const v = value(i);
2769
+ const n = v === null ? Number.NaN : Number.parseInt(v, 10);
2770
+ if (!Number.isFinite(n) || n <= 0) {
2771
+ process4.stderr.write("report_conformance_funnel: --limit must be a positive integer\n");
2772
+ return 1;
2773
+ }
2774
+ opts.limit = n;
2775
+ i += 1;
2776
+ } else if (a === "--help" || a === "-h") {
2777
+ process4.stdout.write(
2778
+ "usage: report_conformance_funnel [--store PATH] [--limit N]\n [--project DIR] [--global DIR] [--source DIR]\n"
2779
+ );
2780
+ return 0;
2781
+ } else if (a !== void 0 && a.startsWith("--") && a !== "--quiet") {
2782
+ process4.stderr.write(`report_conformance_funnel: unknown flag ${a}
2783
+ `);
2784
+ return 1;
2785
+ }
2786
+ }
2787
+ process4.stdout.write(`${render5(buildFunnel(opts))}
2788
+ `);
2789
+ return 0;
2790
+ }
2791
+ function _isCliEntry9() {
2792
+ if (process4.argv[1] === void 0) return false;
2793
+ if (import.meta.url === pathToFileURL10(path11.resolve(process4.argv[1])).href) return true;
2794
+ try {
2795
+ return fs11.realpathSync(_HERE4) === fs11.realpathSync(path11.resolve(process4.argv[1]));
2796
+ } catch {
2797
+ return false;
2798
+ }
2799
+ }
2800
+ if (_isCliEntry9()) {
2801
+ process4.exit(main10());
105
2802
  }
106
2803
 
107
2804
  // src/scripts/_cli/cmd_conformance.ts
108
- var _HERE = fileURLToPath(import.meta.url);
2805
+ var _HERE5 = fileURLToPath11(import.meta.url);
109
2806
  var PACKAGE_ROOT = resolvePackageRoot(import.meta.url);
110
2807
  var CONFORMANCE_CHECK_IDS = [
111
2808
  "txlog-clean",
@@ -116,29 +2813,29 @@ var CONFORMANCE_CHECK_IDS = [
116
2813
  ];
117
2814
  var THIN_STUB_MARKER = "Routed rule \u2014 load the body on trigger-match";
118
2815
  function print(s = "") {
119
- process.stdout.write(`${s}
2816
+ process5.stdout.write(`${s}
120
2817
  `);
121
2818
  }
122
2819
  function eprint(s = "") {
123
- process.stderr.write(`${s}
2820
+ process5.stderr.write(`${s}
124
2821
  `);
125
2822
  }
126
2823
  function conformanceLogPath() {
127
- const override = process.env["AGENT_CONFIG_CONFORMANCE_LOG"];
2824
+ const override = process5.env["AGENT_CONFIG_CONFORMANCE_LOG"];
128
2825
  if (override && override.trim().length > 0) {
129
2826
  return override;
130
2827
  }
131
- return path.join(os.homedir(), ".event4u", "agent-config", "conformance-log.jsonl");
2828
+ return path12.join(os6.homedir(), ".event4u", "agent-config", "conformance-log.jsonl");
132
2829
  }
133
2830
  function installLogPath() {
134
- const override = process.env["AGENT_CONFIG_INSTALL_LOG"];
2831
+ const override = process5.env["AGENT_CONFIG_INSTALL_LOG"];
135
2832
  if (override && override.trim().length > 0) {
136
2833
  return override;
137
2834
  }
138
- return path.join(os.homedir(), ".event4u", "agent-config", "install-log.jsonl");
2835
+ return path12.join(os6.homedir(), ".event4u", "agent-config", "install-log.jsonl");
139
2836
  }
140
2837
  function _check_txlog_clean(logPath = installLogPath()) {
141
- if (!fs.existsSync(logPath)) {
2838
+ if (!fs12.existsSync(logPath)) {
142
2839
  return {
143
2840
  id: "txlog-clean",
144
2841
  status: "ok",
@@ -172,11 +2869,11 @@ function _check_txlog_clean(logPath = installLogPath()) {
172
2869
  };
173
2870
  }
174
2871
  function routeTargetPaths(target) {
175
- return routeTargetPathsPosix(target).map((rel) => path.join(...rel.split("/")));
2872
+ return routeTargetPathsPosix(target).map((rel) => path12.join(...rel.split("/")));
176
2873
  }
177
2874
  function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
178
- const routerPath = path.join(packageRoot, "dist", "router.json");
179
- if (!fs.existsSync(routerPath)) {
2875
+ const routerPath = path12.join(packageRoot, "dist", "router.json");
2876
+ if (!fs12.existsSync(routerPath)) {
180
2877
  return {
181
2878
  id: "router-pointers",
182
2879
  status: "fail",
@@ -186,7 +2883,7 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
186
2883
  }
187
2884
  let router;
188
2885
  try {
189
- router = JSON.parse(fs.readFileSync(routerPath, "utf8"));
2886
+ router = JSON.parse(fs12.readFileSync(routerPath, "utf8"));
190
2887
  } catch (exc) {
191
2888
  return {
192
2889
  id: "router-pointers",
@@ -195,13 +2892,13 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
195
2892
  remedy: "reinstall the package \u2014 dist/router.json is corrupt"
196
2893
  };
197
2894
  }
198
- const rulesDir = path.join(packageRoot, "dist", "agent-src", "rules");
2895
+ const rulesDir = path12.join(packageRoot, "dist", "agent-src", "rules");
199
2896
  const broken = [];
200
2897
  let ids = 0;
201
2898
  let targets = 0;
202
2899
  for (const kernelId of router["kernel"] ?? []) {
203
2900
  ids += 1;
204
- if (!fs.existsSync(path.join(rulesDir, `${kernelId}.md`))) {
2901
+ if (!fs12.existsSync(path12.join(rulesDir, `${kernelId}.md`))) {
205
2902
  broken.push(`kernel:${kernelId} \u2192 rules/${kernelId}.md missing`);
206
2903
  }
207
2904
  }
@@ -209,7 +2906,7 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
209
2906
  for (const entry of router[tier] ?? []) {
210
2907
  const ruleId = String(entry["id"] ?? "");
211
2908
  ids += 1;
212
- if (!fs.existsSync(path.join(rulesDir, `${ruleId}.md`))) {
2909
+ if (!fs12.existsSync(path12.join(rulesDir, `${ruleId}.md`))) {
213
2910
  broken.push(`${tier}:${ruleId} \u2192 rules/${ruleId}.md missing`);
214
2911
  }
215
2912
  for (const target of entry["routes_to"] ?? []) {
@@ -219,7 +2916,7 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
219
2916
  broken.push(`${ruleId} \u2192 unparseable routes_to ${JSON.stringify(target)}`);
220
2917
  continue;
221
2918
  }
222
- if (!candidates.some((rel) => fs.existsSync(path.join(packageRoot, rel)))) {
2919
+ if (!candidates.some((rel) => fs12.existsSync(path12.join(packageRoot, rel)))) {
223
2920
  broken.push(`${ruleId} \u2192 ${target} (${candidates[0]} missing)`);
224
2921
  }
225
2922
  }
@@ -251,9 +2948,9 @@ function detectSmokePlatform(projectRoot) {
251
2948
  return "claude";
252
2949
  }
253
2950
  function dispatcherRunner(packageRoot) {
254
- const localTsx = path.join(packageRoot, "node_modules", ".bin", "tsx");
255
- const script = path.join(packageRoot, "src", "scripts", "hooks", "dispatch_hook.ts");
256
- if (fs.existsSync(localTsx)) {
2951
+ const localTsx = path12.join(packageRoot, "node_modules", ".bin", "tsx");
2952
+ const script = path12.join(packageRoot, "src", "scripts", "hooks", "dispatch_hook.ts");
2953
+ if (fs12.existsSync(localTsx)) {
257
2954
  return [localTsx, script];
258
2955
  }
259
2956
  return ["npx", "tsx", script];
@@ -303,12 +3000,12 @@ function _check_hook_dispatcher(projectRoot, packageRoot = PACKAGE_ROOT, opts =
303
3000
  };
304
3001
  }
305
3002
  function readSettingsMode(projectRoot) {
306
- const settingsPath = path.join(projectRoot, ".agent-settings.yml");
307
- if (!fs.existsSync(settingsPath)) {
3003
+ const settingsPath = path12.join(projectRoot, ".agent-settings.yml");
3004
+ if (!fs12.existsSync(settingsPath)) {
308
3005
  return "eager-all";
309
3006
  }
310
3007
  try {
311
- const parsed = YAML.parse(fs.readFileSync(settingsPath, "utf8"));
3008
+ const parsed = YAML.parse(fs12.readFileSync(settingsPath, "utf8"));
312
3009
  const lean = parsed?.["lean_projection"] ?? null;
313
3010
  const mode = lean?.["mode"];
314
3011
  return typeof mode === "string" && mode.length > 0 ? mode : "eager-all";
@@ -319,7 +3016,7 @@ function readSettingsMode(projectRoot) {
319
3016
  function kernelIdsFromRouter(packageRoot) {
320
3017
  try {
321
3018
  const router = JSON.parse(
322
- fs.readFileSync(path.join(packageRoot, "dist", "router.json"), "utf8")
3019
+ fs12.readFileSync(path12.join(packageRoot, "dist", "router.json"), "utf8")
323
3020
  );
324
3021
  return new Set((router["kernel"] ?? []).map(String));
325
3022
  } catch {
@@ -336,8 +3033,8 @@ function _check_lean_projection(projectRoot, packageRoot = PACKAGE_ROOT, project
336
3033
  remedy: "set lean_projection.mode to 'eager-all' or 'thin' in .agent-settings.yml"
337
3034
  };
338
3035
  }
339
- const rulesDir = projectedRulesDir ?? path.join(projectRoot, ".augment", "rules");
340
- if (!fs.existsSync(rulesDir)) {
3036
+ const rulesDir = projectedRulesDir ?? path12.join(projectRoot, ".augment", "rules");
3037
+ if (!fs12.existsSync(rulesDir)) {
341
3038
  return {
342
3039
  id: "lean-projection",
343
3040
  status: "skipped",
@@ -346,7 +3043,7 @@ function _check_lean_projection(projectRoot, packageRoot = PACKAGE_ROOT, project
346
3043
  };
347
3044
  }
348
3045
  const kernel = kernelIdsFromRouter(packageRoot);
349
- const files = fs.readdirSync(rulesDir).filter((f) => f.endsWith(".md") && f !== "INDEX.md").sort();
3046
+ const files = fs12.readdirSync(rulesDir).filter((f) => f.endsWith(".md") && f !== "INDEX.md").sort();
350
3047
  const nonKernel = files.filter((f) => !kernel.has(f.replace(/\.md$/, "")));
351
3048
  if (nonKernel.length === 0) {
352
3049
  return {
@@ -360,7 +3057,7 @@ function _check_lean_projection(projectRoot, packageRoot = PACKAGE_ROOT, project
360
3057
  for (const f of nonKernel) {
361
3058
  let text = "";
362
3059
  try {
363
- text = fs.readFileSync(path.join(rulesDir, f), "utf8");
3060
+ text = fs12.readFileSync(path12.join(rulesDir, f), "utf8");
364
3061
  } catch {
365
3062
  continue;
366
3063
  }
@@ -391,8 +3088,8 @@ function _check_lean_projection(projectRoot, packageRoot = PACKAGE_ROOT, project
391
3088
  function _check_host_manifest(projectRoot) {
392
3089
  const detected = Object.entries(detectToolPresence(projectRoot)).filter(([, v]) => v === true).map(([k]) => k);
393
3090
  const detectedNote = detected.length > 0 ? `detected surfaces: ${detected.join(", ")}` : "no surfaces detected";
394
- const settingsPath = path.join(projectRoot, ".agent-settings.yml");
395
- if (!fs.existsSync(settingsPath)) {
3091
+ const settingsPath = path12.join(projectRoot, ".agent-settings.yml");
3092
+ if (!fs12.existsSync(settingsPath)) {
396
3093
  return {
397
3094
  id: "host-manifest",
398
3095
  status: "ok",
@@ -402,7 +3099,7 @@ function _check_host_manifest(projectRoot) {
402
3099
  }
403
3100
  let parsed = null;
404
3101
  try {
405
- parsed = YAML.parse(fs.readFileSync(settingsPath, "utf8"));
3102
+ parsed = YAML.parse(fs12.readFileSync(settingsPath, "utf8"));
406
3103
  } catch (exc) {
407
3104
  return {
408
3105
  id: "host-manifest",
@@ -429,7 +3126,7 @@ function _check_host_manifest(projectRoot) {
429
3126
  };
430
3127
  }
431
3128
  function runConformanceChecks(opts = {}) {
432
- const projectRoot = opts.projectRoot ?? process.cwd();
3129
+ const projectRoot = opts.projectRoot ?? process5.cwd();
433
3130
  const packageRoot = opts.packageRoot ?? PACKAGE_ROOT;
434
3131
  const checks = [];
435
3132
  checks.push(_check_txlog_clean());
@@ -463,18 +3160,20 @@ function appendConformanceReport(checks, projectRoot, logPath = conformanceLogPa
463
3160
  } catch {
464
3161
  }
465
3162
  }
466
- var USAGE = "usage: agent-config conformance [-h] [--project PROJECT] [--json]\n [--skip-dispatcher]\n";
3163
+ var USAGE = "usage: agent-config conformance [-h] [--project PROJECT] [--json]\n [--skip-dispatcher] [--funnel]\n";
467
3164
  function _parse(argv) {
468
- const opts = { project: null, json: false, skip_dispatcher: false };
3165
+ const opts = { project: null, json: false, skip_dispatcher: false, funnel: false };
469
3166
  let i = 0;
470
3167
  while (i < argv.length) {
471
3168
  const a = argv[i];
472
3169
  if (a === "-h" || a === "--help") {
473
- process.stdout.write(USAGE);
3170
+ process5.stdout.write(USAGE);
474
3171
  throw new ArgparseExit(0);
475
3172
  }
476
3173
  if (a === "--json") {
477
3174
  opts.json = true;
3175
+ } else if (a === "--funnel") {
3176
+ opts.funnel = true;
478
3177
  } else if (a === "--skip-dispatcher") {
479
3178
  opts.skip_dispatcher = true;
480
3179
  } else if (a === "--project" || a.startsWith("--project=")) {
@@ -482,16 +3181,16 @@ function _parse(argv) {
482
3181
  opts.project = a.slice(a.indexOf("=") + 1);
483
3182
  } else {
484
3183
  if (i + 1 >= argv.length) {
485
- process.stderr.write(USAGE);
486
- process.stderr.write("agent-config conformance: error: --project expects a value\n");
3184
+ process5.stderr.write(USAGE);
3185
+ process5.stderr.write("agent-config conformance: error: --project expects a value\n");
487
3186
  throw new ArgparseExit(2);
488
3187
  }
489
3188
  opts.project = argv[i + 1];
490
3189
  i += 1;
491
3190
  }
492
3191
  } else {
493
- process.stderr.write(USAGE);
494
- process.stderr.write(`agent-config conformance: error: unrecognized argument ${a}
3192
+ process5.stderr.write(USAGE);
3193
+ process5.stderr.write(`agent-config conformance: error: unrecognized argument ${a}
495
3194
  `);
496
3195
  throw new ArgparseExit(2);
497
3196
  }
@@ -499,8 +3198,12 @@ function _parse(argv) {
499
3198
  }
500
3199
  return opts;
501
3200
  }
502
- function main(argv = null) {
503
- const opts = _parse(argv !== null ? Array.from(argv) : process.argv.slice(2));
3201
+ function main11(argv = null) {
3202
+ const opts = _parse(argv !== null ? Array.from(argv) : process5.argv.slice(2));
3203
+ if (opts.funnel) {
3204
+ print(render5(buildFunnel()));
3205
+ return 0;
3206
+ }
504
3207
  let project_root;
505
3208
  try {
506
3209
  [project_root] = _resolve_project_root(opts.project);
@@ -566,28 +3269,28 @@ function main(argv = null) {
566
3269
  }
567
3270
  return failed ? 1 : 0;
568
3271
  }
569
- function _isCliEntry() {
570
- if (process.argv[1] === void 0) {
3272
+ function _isCliEntry10() {
3273
+ if (process5.argv[1] === void 0) {
571
3274
  return false;
572
3275
  }
573
- const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
3276
+ const argvUrl = pathToFileURL11(path12.resolve(process5.argv[1])).href;
574
3277
  if (import.meta.url === argvUrl) {
575
3278
  return true;
576
3279
  }
577
3280
  try {
578
- const here = fs.realpathSync(fileURLToPath(import.meta.url));
579
- const argv = fs.realpathSync(path.resolve(process.argv[1]));
3281
+ const here = fs12.realpathSync(fileURLToPath11(import.meta.url));
3282
+ const argv = fs12.realpathSync(path12.resolve(process5.argv[1]));
580
3283
  return here === argv;
581
3284
  } catch {
582
3285
  return false;
583
3286
  }
584
3287
  }
585
- if (_isCliEntry() || process.argv[1] === _HERE) {
3288
+ if (_isCliEntry10() || process5.argv[1] === _HERE5) {
586
3289
  try {
587
- process.exitCode = main(process.argv.slice(2));
3290
+ process5.exitCode = main11(process5.argv.slice(2));
588
3291
  } catch (e) {
589
3292
  if (e instanceof ArgparseExit) {
590
- process.exitCode = e.code;
3293
+ process5.exitCode = e.code;
591
3294
  } else {
592
3295
  throw e;
593
3296
  }
@@ -607,7 +3310,7 @@ export {
607
3310
  conformanceLogPath,
608
3311
  detectSmokePlatform,
609
3312
  installLogPath,
610
- main,
3313
+ main11 as main,
611
3314
  routeTargetPaths,
612
3315
  runConformanceChecks
613
3316
  };