@event4u/agent-config 16.1.0 → 16.2.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 (217) hide show
  1. package/.augment-plugin/marketplace.json +3 -3
  2. package/.augment-plugin/plugin.json +1 -1
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/CHANGELOG.md +237 -248
  5. package/dist/agent-src/commands/roadmap/process-full.md +1 -1
  6. package/dist/agent-src/commands/work.md +4 -0
  7. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +4 -8
  8. package/dist/agent-src/rules/ui-audit-gate.md +1 -2
  9. package/dist/agent-src/skills/accessibility-auditor/data/aria-patterns.csv +1 -1
  10. package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +2 -2
  11. package/dist/agent-src/skills/design-review/SKILL.md +30 -4
  12. package/dist/agent-src/skills/design-review/references/verification-automation.md +32 -0
  13. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +16 -1
  14. package/dist/agent-src/skills/existing-ui-audit/references/component-taxonomy-detection.md +56 -0
  15. package/dist/agent-src/skills/markitdown/SKILL.md +4 -0
  16. package/dist/agent-src/skills/playwright-testing/SKILL.md +30 -6
  17. package/dist/agent-src/skills/ui-component-architect/SKILL.md +23 -0
  18. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  19. package/dist/agent-src/templates/roadmaps.md +30 -0
  20. package/dist/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
  21. package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +260 -21
  22. package/dist/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
  23. package/dist/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
  24. package/dist/agent-src/templates/scripts/work_engine/state.ts +57 -7
  25. package/dist/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
  26. package/dist/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
  27. package/dist/cli/python/knowledge_ingest.js +1 -1
  28. package/dist/cli/python/workspace_drive.js +1 -1
  29. package/dist/cli-delegate/{chunk-S3VFXMTM.js → chunk-4XFP2Y3E.js} +1 -1
  30. package/dist/cli-delegate/{chunk-HDVVGPQW.js → chunk-7XGBX6ZH.js} +471 -387
  31. package/dist/cli-delegate/{chunk-GJCBB2UG.js → chunk-FP36SWJD.js} +432 -144
  32. package/dist/cli-delegate/{chunk-UNCRJBT3.js → chunk-IGD6ERX6.js} +31 -0
  33. package/dist/cli-delegate/{chunk-FXYUYU4N.js → chunk-JNMVLWLR.js} +42 -1
  34. package/dist/cli-delegate/{chunk-QJWAUMJW.js → chunk-YJ72UIAK.js} +730 -15
  35. package/dist/cli-delegate/cmd_conformance.js +902 -782
  36. package/dist/cli-delegate/cmd_doctor.js +2 -2
  37. package/dist/cli-delegate/cmd_export.js +2 -2
  38. package/dist/cli-delegate/cmd_handoff.js +4 -4
  39. package/dist/cli-delegate/cmd_memory_get.js +1 -1
  40. package/dist/cli-delegate/cmd_prune.js +2 -2
  41. package/dist/cli-delegate/cmd_route_audit.js +1 -1
  42. package/dist/cli-delegate/cmd_session_recycle.js +2 -2
  43. package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
  44. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  45. package/dist/cli-delegate/cmd_sync.js +2 -2
  46. package/dist/cli-delegate/cmd_uninstall.js +2 -2
  47. package/dist/cli-delegate/cmd_validate.js +2 -2
  48. package/dist/discovery/deprecation-report.md +1 -1
  49. package/dist/discovery/discovery-manifest.json +10 -10
  50. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  51. package/dist/discovery/discovery-manifest.summary.md +1 -1
  52. package/dist/discovery/dormancy-report.md +1 -1
  53. package/dist/discovery/orphan-report.md +1 -1
  54. package/dist/discovery/packs.json +2 -2
  55. package/dist/discovery/trust-report.md +1 -1
  56. package/dist/discovery/workspaces.json +2 -2
  57. package/dist/hooks/dispatch.js +6666 -2581
  58. package/dist/hooks/dispatch.sha256 +1 -0
  59. package/dist/install/install.mjs +848 -733
  60. package/dist/mcp/registry-manifest.json +2 -2
  61. package/dist/mcp/server.json +2 -2
  62. package/dist/mcp/server.mjs +2747 -1510
  63. package/dist/server/schemas/settings.js +23 -0
  64. package/dist/ui/assets/index-Cwq4d0hX.js +23 -0
  65. package/dist/ui/assets/index-Cyjy8bWO.css +1 -0
  66. package/dist/ui/index.html +2 -2
  67. package/docs/contracts/settings-classes.md +6 -4
  68. package/package.json +4 -4
  69. package/src/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
  70. package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +260 -21
  71. package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
  72. package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
  73. package/src/agent-src/templates/scripts/work_engine/state.ts +57 -7
  74. package/src/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
  75. package/src/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
  76. package/src/config/agent-settings.template.yml +29 -0
  77. package/src/config/ci-local-parity.yml +18 -0
  78. package/src/config/continuity-surface.json +2 -2
  79. package/src/config/gate-coverage.yml +234 -3
  80. package/src/config/gate-violation-baselines.json +7 -3
  81. package/src/config/hook-latency-budget.json +21 -0
  82. package/src/config/hook-token-budget.json +2 -2
  83. package/src/config/metric-registry.yml +114 -0
  84. package/src/config/placeholder-drift-budget.json +10 -0
  85. package/src/config/routing-coverage-seed.json +18 -4
  86. package/src/scripts/_cli/cmd_doctor.ts +5 -19
  87. package/src/scripts/_cli/doctor_execution.ts +100 -17
  88. package/src/scripts/_cli/doctor_network_posture.ts +226 -0
  89. package/src/scripts/_lib/body_portable.ts +102 -0
  90. package/src/scripts/_lib/cc_transcript.ts +132 -1
  91. package/src/scripts/_lib/concern_sla_window.ts +200 -0
  92. package/src/scripts/_lib/forge_protection.ts +114 -0
  93. package/src/scripts/_lib/forge_reader.ts +558 -0
  94. package/src/scripts/_lib/host_env_write.ts +111 -0
  95. package/src/scripts/_lib/llm_proposer_transport.ts +14 -4
  96. package/src/scripts/_lib/obligations.ts +119 -8
  97. package/src/scripts/_lib/probe_inputs.ts +271 -0
  98. package/src/scripts/_lib/read_surface_scan.ts +322 -0
  99. package/src/scripts/_lib/release_highlights.ts +2 -1
  100. package/src/scripts/_lib/retrieval_sanitize.ts +77 -16
  101. package/src/scripts/_lib/shell_write_shapes.ts +108 -0
  102. package/src/scripts/_lib/structural_hiding.ts +622 -0
  103. package/src/scripts/_lib/turn_end_refusals.ts +525 -4
  104. package/src/scripts/_lib/turn_end_transcript.ts +216 -0
  105. package/src/scripts/_lib/verification_command.ts +324 -0
  106. package/src/scripts/_lib/verification_evidence.ts +647 -0
  107. package/src/scripts/_lib/verify_clause.ts +280 -0
  108. package/src/scripts/ai_team/team_dispatch.ts +4 -0
  109. package/src/scripts/before_complete_hook.ts +340 -23
  110. package/src/scripts/bench_hook_latency.ts +412 -5
  111. package/src/scripts/cache_realization_report.ts +61 -1
  112. package/src/scripts/check_argument_hint.ts +287 -0
  113. package/src/scripts/check_claims.ts +125 -1
  114. package/src/scripts/check_enforcement_coverage.ts +38 -9
  115. package/src/scripts/check_enforcement_matrix.ts +245 -3
  116. package/src/scripts/check_finding_dispositions.ts +22 -3
  117. package/src/scripts/check_generator_sync.ts +71 -0
  118. package/src/scripts/check_host_docs_digest.ts +786 -0
  119. package/src/scripts/check_host_format_column.ts +559 -0
  120. package/src/scripts/check_input_references.ts +228 -0
  121. package/src/scripts/check_inputs_parity.ts +235 -0
  122. package/src/scripts/check_kernel_edit_ratified.ts +47 -5
  123. package/src/scripts/check_kill_switch_table.ts +294 -0
  124. package/src/scripts/check_placeholder_drift.ts +221 -0
  125. package/src/scripts/check_read_surface_coverage.ts +515 -0
  126. package/src/scripts/check_release_holds.ts +2 -1
  127. package/src/scripts/check_routing_coverage.ts +378 -11
  128. package/src/scripts/check_static_parity.ts +17 -0
  129. package/src/scripts/ci_settle.ts +145 -19
  130. package/src/scripts/closure_scan.ts +115 -1
  131. package/src/scripts/compile_hook_manifest.ts +60 -16
  132. package/src/scripts/external_sources_denylist.json +29 -4
  133. package/src/scripts/hook_manifest.json +1 -1
  134. package/src/scripts/hook_manifest.yaml +42 -5
  135. package/src/scripts/hooks/block_config_weakening.ts +229 -4
  136. package/src/scripts/hooks/block_kernel_rule_writes.ts +9 -78
  137. package/src/scripts/hooks/block_plumbing_writes.ts +314 -0
  138. package/src/scripts/hooks/bundle_integrity.ts +291 -0
  139. package/src/scripts/hooks/chain_nudge_hook.ts +1 -2
  140. package/src/scripts/hooks/code_graph_context_hook.ts +1 -2
  141. package/src/scripts/hooks/comment_discipline_hook.ts +1 -3
  142. package/src/scripts/hooks/concern_registry.ts +2 -0
  143. package/src/scripts/hooks/concern_timings.ts +94 -0
  144. package/src/scripts/hooks/delegation_nudge_hook.ts +1 -2
  145. package/src/scripts/hooks/design_pass_hook.ts +100 -9
  146. package/src/scripts/hooks/design_slop_hook.ts +1 -2
  147. package/src/scripts/hooks/dispatch_hook.ts +25 -19
  148. package/src/scripts/hooks/edit_shape_hook.ts +1 -2
  149. package/src/scripts/hooks/end_review_nudge_hook.ts +1 -1
  150. package/src/scripts/hooks/evidence_independence.ts +1 -2
  151. package/src/scripts/hooks/exit_codes.ts +128 -0
  152. package/src/scripts/hooks/host_lowering.json +1 -0
  153. package/src/scripts/hooks/host_lowering.ts +128 -3
  154. package/src/scripts/hooks/host_lowering.yaml +262 -46
  155. package/src/scripts/hooks/host_semantics.ts +30 -2
  156. package/src/scripts/hooks/injection_budget.ts +9 -2
  157. package/src/scripts/hooks/interruption_ledger_hook.ts +1 -2
  158. package/src/scripts/hooks/journal_record_hook.ts +1 -2
  159. package/src/scripts/hooks/memory_recall_hook.ts +1 -2
  160. package/src/scripts/hooks/obligation_settle_hook.ts +151 -5
  161. package/src/scripts/hooks/one_question_per_ask_hook.ts +25 -4
  162. package/src/scripts/hooks/orchestration_record_hook.ts +1 -2
  163. package/src/scripts/hooks/permission_gate.ts +1 -2
  164. package/src/scripts/hooks/push_settle_hook.ts +1 -3
  165. package/src/scripts/hooks/replay_hook.ts +2 -1
  166. package/src/scripts/hooks/reread_guard_hook.ts +1 -2
  167. package/src/scripts/hooks/review_baseline_hook.ts +1 -2
  168. package/src/scripts/hooks/rtk_wrap_hook.ts +1 -2
  169. package/src/scripts/hooks/rule_inject_hook.ts +8 -5
  170. package/src/scripts/hooks/run_continuation_hook.ts +13 -15
  171. package/src/scripts/hooks/skill_route_hook.ts +1 -2
  172. package/src/scripts/hooks/source_first_gate_hook.ts +1 -2
  173. package/src/scripts/hooks/spawn_guard_shadow_hook.ts +1 -2
  174. package/src/scripts/hooks/subagent_ledger_hook.ts +1 -2
  175. package/src/scripts/hooks/suggestion_capture_hook.ts +1 -1
  176. package/src/scripts/hooks/table_fingerprint.ts +49 -0
  177. package/src/scripts/hooks/telemetry_flush_hook.ts +1 -2
  178. package/src/scripts/hooks/telemetry_usage_hook.ts +1 -2
  179. package/src/scripts/hooks/tool_result_bytes_hook.ts +93 -9
  180. package/src/scripts/hooks/turn_end_gate_hook.ts +337 -324
  181. package/src/scripts/hooks/ui_route_nudge_hook.ts +1 -2
  182. package/src/scripts/install.ts +3 -4
  183. package/src/scripts/lint_deny_text.ts +419 -0
  184. package/src/scripts/lint_exit_codes.ts +262 -0
  185. package/src/scripts/lint_hook_manifest.ts +55 -0
  186. package/src/scripts/lint_roadmap_blockers.ts +78 -12
  187. package/src/scripts/lint_skill_descriptions.ts +1 -1
  188. package/src/scripts/lint_skill_scripts_readonly.ts +1 -1
  189. package/src/scripts/mcp_server/prompts.ts +142 -1
  190. package/src/scripts/measure_skill_ranker_baseline.ts +305 -51
  191. package/src/scripts/measure_turn_end_gate.ts +193 -0
  192. package/src/scripts/memory_lookup.ts +1 -1
  193. package/src/scripts/prepack-check.mjs +53 -0
  194. package/src/scripts/release_publication.ts +13 -1
  195. package/src/scripts/report_invocation_surface.ts +383 -0
  196. package/src/scripts/report_obligation_carriers.ts +699 -0
  197. package/src/scripts/report_obligation_writer_split.ts +236 -0
  198. package/src/scripts/report_skill_menu_census.ts +208 -0
  199. package/src/scripts/report_standing_payload_by_host.ts +1 -1
  200. package/src/scripts/roadmap_verify_share.ts +213 -0
  201. package/src/scripts/schemas/command.schema.json +44 -0
  202. package/src/scripts/schemas/review-findings.schema.json +4 -0
  203. package/src/scripts/schemas/skill.schema.json +44 -0
  204. package/src/scripts/self_review_gate.ts +359 -10
  205. package/src/scripts/semantic_parity_census.ts +314 -0
  206. package/src/scripts/session_eol_report.ts +54 -1
  207. package/src/scripts/sweep_dead_scan_roots.ts +1 -1
  208. package/src/scripts/trigger_eval_grandfather.json +0 -2
  209. package/src/scripts/trigger_eval_rotation.ts +122 -27
  210. package/src/scripts/ui_conformance_probe.ts +156 -4
  211. package/src/scripts/update_prices.ts +85 -3
  212. package/src/scripts/write_bundle_digest.mjs +74 -0
  213. package/src/server/schemas/settings.ts +31 -0
  214. package/src/templates/consumer-settings/ONBOARDING.md +1 -0
  215. package/dist/ui/assets/index-DH1xc62v.js +0 -29
  216. package/dist/ui/assets/index-jSfMW8_t.css +0 -1
  217. /package/dist/cli-delegate/{chunk-YUM5KURG.js → chunk-7RQP4UDH.js} +0 -0
@@ -1,16 +1,25 @@
1
1
  import { createRequire as __acCreateRequire } from 'node:module'; const require = globalThis.require ?? __acCreateRequire(import.meta.url);
2
2
  import {
3
+ NO_RED_EVIDENCE_REASON,
3
4
  deriveSessionKey,
5
+ describeRecordFinding,
4
6
  foldRefusal,
7
+ foldShadow,
8
+ hasRedThenGreen,
5
9
  isCiPoll,
6
10
  isVacuousOutput,
11
+ isVerificationCommand,
7
12
  parseRecord,
13
+ parseShadowRecord,
8
14
  pendingCount,
9
15
  readInstallBoundary,
16
+ readRunEvidence,
17
+ readTurnRunState,
10
18
  sessionRefusalFile,
19
+ sessionShadowFile,
11
20
  statePathFor,
12
21
  validateAssumption
13
- } from "./chunk-QJWAUMJW.js";
22
+ } from "./chunk-YJ72UIAK.js";
14
23
  import {
15
24
  readHookStdin
16
25
  } from "./chunk-F7DQWYR5.js";
@@ -36,7 +45,7 @@ import {
36
45
  frontmatterPaths,
37
46
  proseEqual,
38
47
  splitFrontmatter
39
- } from "./chunk-GJCBB2UG.js";
48
+ } from "./chunk-FP36SWJD.js";
40
49
  import "./chunk-FTAISGDQ.js";
41
50
  import "./chunk-M2DPGFZ4.js";
42
51
  import "./chunk-ROYYTEZU.js";
@@ -44,7 +53,7 @@ import "./chunk-BTWUYDGN.js";
44
53
  import {
45
54
  projectStoreSlug,
46
55
  resolveSessionRole
47
- } from "./chunk-FXYUYU4N.js";
56
+ } from "./chunk-JNMVLWLR.js";
48
57
  import {
49
58
  atomic_write_json,
50
59
  atomic_write_text,
@@ -91,7 +100,7 @@ import {
91
100
 
92
101
  // src/scripts/_cli/cmd_conformance.ts
93
102
  var YAML = __toESM(require_dist(), 1);
94
- import * as fs15 from "node:fs";
103
+ import * as fs16 from "node:fs";
95
104
  import * as os7 from "node:os";
96
105
  import * as path15 from "node:path";
97
106
  import process5 from "node:process";
@@ -124,7 +133,7 @@ function routeTargetPathsPosix(target) {
124
133
  }
125
134
 
126
135
  // src/scripts/report_conformance_funnel.ts
127
- import * as fs14 from "node:fs";
136
+ import * as fs15 from "node:fs";
128
137
  import * as os6 from "node:os";
129
138
  import * as path14 from "node:path";
130
139
  import process4 from "node:process";
@@ -132,7 +141,7 @@ import { fileURLToPath as fileURLToPath13, pathToFileURL as pathToFileURL13 } fr
132
141
 
133
142
  // src/scripts/conformance_scan.ts
134
143
  import { createHash as createHash3 } from "node:crypto";
135
- import fs10 from "node:fs";
144
+ import fs11 from "node:fs";
136
145
  import os2 from "node:os";
137
146
  import path10 from "node:path";
138
147
  import { fileURLToPath as fileURLToPath9, pathToFileURL as pathToFileURL9 } from "node:url";
@@ -1918,7 +1927,7 @@ var COMMAND_OBJECTS = {
1918
1927
  };
1919
1928
 
1920
1929
  // src/scripts/hooks/turn_end_gate_hook.ts
1921
- import * as fs7 from "node:fs";
1930
+ import * as fs8 from "node:fs";
1922
1931
  import * as path7 from "node:path";
1923
1932
  import { fileURLToPath as fileURLToPath6, pathToFileURL as pathToFileURL6 } from "node:url";
1924
1933
 
@@ -2298,162 +2307,6 @@ function detectDroppedDecision(assistantTurnTexts, closingFromPayload = false) {
2298
2307
  return null;
2299
2308
  }
2300
2309
 
2301
- // src/scripts/hooks/end_review_nudge_hook.ts
2302
- import { spawnSync as spawnSync2 } from "node:child_process";
2303
- import * as crypto from "node:crypto";
2304
- import * as fs5 from "node:fs";
2305
- import * as os from "node:os";
2306
- import * as path5 from "node:path";
2307
- import { fileURLToPath as fileURLToPath4, pathToFileURL as pathToFileURL4 } from "node:url";
2308
-
2309
- // src/scripts/_lib/review_baseline.ts
2310
- import { spawnSync } from "node:child_process";
2311
- import * as fs4 from "node:fs";
2312
- import * as path4 from "node:path";
2313
- function currentHeadSha(cwd) {
2314
- try {
2315
- const r = spawnSync("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8" });
2316
- if (r.status !== 0) return null;
2317
- const out = (r.stdout ?? "").trim();
2318
- return out === "" ? null : out;
2319
- } catch {
2320
- return null;
2321
- }
2322
- }
2323
- var BASELINE_STATE_REL = path4.join("agents", "runtime", "state", "review-baseline");
2324
- function baselineStateDir(workspaceRoot) {
2325
- return path4.join(workspaceRoot, BASELINE_STATE_REL);
2326
- }
2327
- function baselineStateFile(workspaceRoot, sessionKey) {
2328
- return path4.join(baselineStateDir(workspaceRoot), `${sessionKey}.json`);
2329
- }
2330
- function parseBaseline(raw) {
2331
- let decoded;
2332
- try {
2333
- decoded = JSON.parse(raw);
2334
- } catch {
2335
- return null;
2336
- }
2337
- if (typeof decoded !== "object" || decoded === null || Array.isArray(decoded)) return null;
2338
- const o = decoded;
2339
- const lines = o["baseline_lines"];
2340
- if (typeof lines !== "number" || !Number.isFinite(lines) || lines < 0) return null;
2341
- const written = o["written_at"];
2342
- if (typeof written !== "string") return null;
2343
- const head = o["head_sha"];
2344
- const measure = o["measure"];
2345
- return {
2346
- head_sha: typeof head === "string" && head !== "" ? head : null,
2347
- baseline_lines: lines,
2348
- ...measure === "exact" || measure === "capped_approximation" ? { measure } : {},
2349
- written_at: written
2350
- };
2351
- }
2352
- function readBaseline(workspaceRoot, sessionKey) {
2353
- const file = baselineStateFile(workspaceRoot, sessionKey);
2354
- let raw;
2355
- try {
2356
- raw = fs4.readFileSync(file, "utf-8");
2357
- } catch (exc) {
2358
- const code = exc?.code;
2359
- return code === "ENOENT" ? "absent" : "unreadable";
2360
- }
2361
- return parseBaseline(raw) ?? "unreadable";
2362
- }
2363
- function applyBaseline(measuredLines, baseline, headSha, measuredMeasure = "exact") {
2364
- if (baseline === "absent" || baseline === "unreadable") {
2365
- return { applied: false, fallback: baseline, lines: measuredLines };
2366
- }
2367
- if (baseline.measure !== "exact" || measuredMeasure !== "exact") {
2368
- return { applied: false, fallback: "mixed-measure", lines: measuredLines };
2369
- }
2370
- if (baseline.head_sha !== headSha()) {
2371
- return { applied: false, fallback: "head-moved", lines: measuredLines };
2372
- }
2373
- return {
2374
- applied: true,
2375
- baseline,
2376
- lines: Math.max(0, measuredLines - baseline.baseline_lines)
2377
- };
2378
- }
2379
-
2380
- // src/scripts/_lib/audit_field_provenance.ts
2381
- var MAX_APPLIED_IDS = 32;
2382
- function appliedIds(values) {
2383
- const out = [];
2384
- for (const raw of values ?? []) {
2385
- const v = String(raw).trim();
2386
- if (v === "" || out.includes(v)) continue;
2387
- out.push(v);
2388
- if (out.length >= MAX_APPLIED_IDS) break;
2389
- }
2390
- return out;
2391
- }
2392
-
2393
- // src/scripts/_lib/review_skipped_record.ts
2394
- var BASELINE_APPLICATIONS = [
2395
- "applied",
2396
- "absent",
2397
- "unreadable",
2398
- "head_moved",
2399
- "mixed_measure"
2400
- ];
2401
- var PRODUCER_PRIVACY_CLASS = "ids-only";
2402
- function isNonNegInt(n) {
2403
- return typeof n === "number" && Number.isFinite(n) && Number.isInteger(n) && n >= 0;
2404
- }
2405
- var HIGH_RISK_DIFF_LINES = 200;
2406
- function buildReviewSkippedLine(input) {
2407
- const errors = [];
2408
- if (!isNonNegInt(input.diff_lines)) {
2409
- errors.push("diff_lines must be a non-negative integer");
2410
- }
2411
- if (input.mutation_measure !== "exact" && input.mutation_measure !== "capped_approximation") {
2412
- errors.push('mutation_measure must be "exact" or "capped_approximation"');
2413
- }
2414
- if (!input.ts) errors.push("ts (ISO-8601 UTC) is required");
2415
- if (!input.id) errors.push("id (ULID, UUID, or content hash) is required");
2416
- if (input.baseline !== void 0 && !BASELINE_APPLICATIONS.includes(input.baseline)) {
2417
- errors.push(`baseline must be one of ${BASELINE_APPLICATIONS.join(", ")}`);
2418
- }
2419
- if (errors.length) return { line: null, errors };
2420
- const line = {
2421
- schema_version: 1,
2422
- id: input.id,
2423
- ts: input.ts,
2424
- work_id: input.work_id ?? `review-skipped-${input.ts}`,
2425
- phase: "report",
2426
- outcome: "skipped",
2427
- confidence_band: "medium",
2428
- risk_class: input.diff_lines >= HIGH_RISK_DIFF_LINES ? "high" : "medium",
2429
- memory: { asks: 0, hits: 0 },
2430
- verify: { claims: 0, first_try_passes: 0 },
2431
- // COMPUTED, never a producer constant.
2432
- rules_applied: appliedIds(input.rules_applied),
2433
- // MANDATORY on every audit line, same as the orchestration producer.
2434
- privacy_class: PRODUCER_PRIVACY_CLASS,
2435
- // `skills_applied` is OMITTED here, deliberately and not by oversight.
2436
- // audit-log-v1 distinguishes an absent key ("not recorded") from `[]`
2437
- // ("recorded, none applied"), and this writer observes a review that
2438
- // did not happen -- it has no skill observation to offer in either
2439
- // direction. Emitting `[]` would assert "no skills applied" on evidence
2440
- // this producer never gathered, which is the exact fabrication the
2441
- // absent/empty split exists to make impossible.
2442
- persona: null,
2443
- input_kind: "prompt",
2444
- type: "note",
2445
- review_skipped: {
2446
- diff_lines: input.diff_lines,
2447
- mutation_measure: input.mutation_measure,
2448
- // Omitted, not defaulted, when the producer recorded nothing —
2449
- // audit-log-v1's absent-versus-present split is the same one
2450
- // `skills_applied` uses above, for the same reason.
2451
- ...input.baseline === void 0 ? {} : { baseline: input.baseline }
2452
- }
2453
- };
2454
- return { line, errors: [] };
2455
- }
2456
-
2457
2310
  // src/scripts/hooks/envelope.ts
2458
2311
  var ENVELOPE_KEYS = [
2459
2312
  "schema_version",
@@ -2508,355 +2361,50 @@ function unwrap(stdin_text, default_platform = "generic") {
2508
2361
  ];
2509
2362
  }
2510
2363
 
2511
- // src/scripts/hooks/end_review_nudge_hook.ts
2512
- var MUTATION_LINE_THRESHOLD = 50;
2513
- var UNTRACKED_FILE_CAP = 20;
2514
- var TRANSCRIPT_SCAN_MAX_BYTES = 50 * 1024 * 1024;
2515
- function isSafeTranscriptPath(rawPath, opts = {}) {
2516
- if (!rawPath || !/\.jsonl$/i.test(rawPath)) return false;
2517
- const homeDir = opts.homeDir ?? os.homedir();
2518
- const maxBytes = opts.maxBytes ?? TRANSCRIPT_SCAN_MAX_BYTES;
2519
- try {
2520
- const realHome = fs5.realpathSync(homeDir);
2521
- const realPath = fs5.realpathSync(rawPath);
2522
- const relative3 = path5.relative(realHome, realPath);
2523
- if (relative3 === "" || relative3.startsWith("..") || path5.isAbsolute(relative3)) {
2524
- return false;
2525
- }
2526
- const { size } = fs5.statSync(realPath);
2527
- return size <= maxBytes;
2528
- } catch {
2529
- return false;
2530
- }
2364
+ // src/scripts/hooks/subagent_ledger_hook.ts
2365
+ import * as crypto from "node:crypto";
2366
+ import * as fs4 from "node:fs";
2367
+ import * as path4 from "node:path";
2368
+ import { fileURLToPath as fileURLToPath4, pathToFileURL as pathToFileURL4 } from "node:url";
2369
+
2370
+ // src/scripts/_lib/subagent_response.ts
2371
+ var MAX_SUMMARY_CHARS = 2e3;
2372
+ var MAX_RESPONSE_LINE_CHARS = 240;
2373
+ var MAX_RESPONSE_ENTRIES = 40;
2374
+ var MAX_ARTIFACT_REF_CHARS = 200;
2375
+ var MAX_ENVELOPE_CHARS = 12e3;
2376
+ var CONFIDENCE = /* @__PURE__ */ new Set(["low", "medium", "high"]);
2377
+ var RESPONSE_REQUIRED_FIELDS = [
2378
+ "summary",
2379
+ "handoff",
2380
+ "confidence",
2381
+ "findings",
2382
+ "risks"
2383
+ ];
2384
+ function countContractFields(input) {
2385
+ if (input === null || typeof input !== "object") return 0;
2386
+ const r = input;
2387
+ return RESPONSE_REQUIRED_FIELDS.filter((k) => Object.hasOwn(r, k)).length;
2531
2388
  }
2532
- var EXIT_WARN2 = 2;
2533
- function parseNumstat(raw) {
2534
- const rows = [];
2535
- for (const line of raw.split("\n")) {
2536
- const m = /^(\d+|-)\t(\d+|-)\t(.+)$/.exec(line);
2537
- if (!m) continue;
2538
- const addedStr = m[1];
2539
- const deletedStr = m[2];
2540
- rows.push({
2541
- added: addedStr === "-" ? 0 : Number(addedStr),
2542
- deleted: deletedStr === "-" ? 0 : Number(deletedStr),
2543
- path: m[3]
2544
- });
2389
+ function validateResponse(input) {
2390
+ const errors = [];
2391
+ if (input === null || typeof input !== "object") {
2392
+ return { valid: false, errors: ["response is not an object"] };
2545
2393
  }
2546
- return rows;
2547
- }
2548
- var DOC_PATH_RE = /\.md$/i;
2549
- function isDocPath(p) {
2550
- return DOC_PATH_RE.test(p);
2551
- }
2552
- function nonDocMutatedLines(rows) {
2553
- return rows.filter((r) => !isDocPath(r.path)).reduce((sum, r) => sum + r.added + r.deleted, 0);
2554
- }
2555
- function gitNumstatRows(cwd) {
2556
- try {
2557
- const r = spawnSync2("git", ["diff", "--numstat", "HEAD"], {
2558
- cwd,
2559
- encoding: "utf8",
2560
- maxBuffer: 16 * 1024 * 1024
2561
- });
2562
- if (r.status !== 0) return [];
2563
- return parseNumstat(r.stdout ?? "");
2564
- } catch {
2565
- return [];
2566
- }
2567
- }
2568
- function untrackedNonDocFiles(cwd) {
2569
- try {
2570
- const r = spawnSync2("git", ["ls-files", "--others", "--exclude-standard"], {
2571
- cwd,
2572
- encoding: "utf8",
2573
- maxBuffer: 16 * 1024 * 1024
2574
- });
2575
- if (r.status !== 0) return [];
2576
- return (r.stdout ?? "").split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !isDocPath(l));
2577
- } catch {
2578
- return [];
2579
- }
2580
- }
2581
- var BINARY_PROBE_BYTES = 8e3;
2582
- var UNTRACKED_FILE_READ_CAP_BYTES = 8 * 1024 * 1024;
2583
- function untrackedFileLineCount(cwd, relPath) {
2584
- try {
2585
- const abs = path5.resolve(cwd, relPath);
2586
- const size = fs5.statSync(abs).size;
2587
- if (size === 0) return 0;
2588
- if (size > UNTRACKED_FILE_READ_CAP_BYTES) {
2589
- return MUTATION_LINE_THRESHOLD + 1;
2590
- }
2591
- const buf = fs5.readFileSync(abs);
2592
- if (buf.length === 0) return 0;
2593
- const probe = buf.subarray(0, Math.min(buf.length, BINARY_PROBE_BYTES));
2594
- if (probe.includes(0)) return 0;
2595
- let lines = 0;
2596
- for (const byte of buf) {
2597
- if (byte === 10) lines += 1;
2598
- }
2599
- if (buf[buf.length - 1] !== 10) lines += 1;
2600
- return lines;
2601
- } catch {
2602
- return 0;
2603
- }
2604
- }
2605
- function totalNonDocMutatedLinesWithMeasure(cwd) {
2606
- const trackedLines = nonDocMutatedLines(gitNumstatRows(cwd));
2607
- const untracked = untrackedNonDocFiles(cwd);
2608
- if (untracked.length > UNTRACKED_FILE_CAP) {
2609
- return { lines: MUTATION_LINE_THRESHOLD + 1 + trackedLines, measure: "capped_approximation" };
2610
- }
2611
- const untrackedLines = untracked.reduce(
2612
- (sum, relPath) => sum + untrackedFileLineCount(cwd, relPath),
2613
- 0
2614
- );
2615
- return { lines: trackedLines + untrackedLines, measure: "exact" };
2616
- }
2617
- function isObject(v) {
2618
- return typeof v === "object" && v !== null && !Array.isArray(v);
2619
- }
2620
- function str(v) {
2621
- return typeof v === "string" ? v : "";
2622
- }
2623
- var DISPATCH_TOOL_RE = /^(agent|task)$/i;
2624
- var REVIEW_SHAPED_LABEL_RE = /\b(review|judge|adversarial|critique|second[- ]opinion|blind\s+(pass|review)|cross-model)\b/i;
2625
- var REVIEW_SHAPED_PROMPT_RE = /\b(code\s+review|neutral\s+review|adversarial|judge|critique|second[- ]opinion|blind\s+(pass|review)|cross-model)\b/i;
2626
- var REVIEW_SKILL_NAME_RE = /^(review[-:]changes|judge[-:][a-z0-9-]+|adversarial-review|code-review)$/i;
2627
- function scanTranscriptForReviewer(transcriptText) {
2628
- for (const raw of transcriptText.split("\n")) {
2629
- const line = raw.trim();
2630
- if (!line) continue;
2631
- let obj;
2632
- try {
2633
- obj = JSON.parse(line);
2634
- } catch {
2635
- continue;
2636
- }
2637
- if (!isObject(obj) || obj["type"] !== "assistant") continue;
2638
- const msg = obj["message"];
2639
- if (!isObject(msg)) continue;
2640
- const content = msg["content"];
2641
- if (!Array.isArray(content)) continue;
2642
- for (const blk of content) {
2643
- if (!isObject(blk) || blk["type"] !== "tool_use") continue;
2644
- const name = str(blk["name"]);
2645
- const input = blk["input"];
2646
- const inputObj = isObject(input) ? input : {};
2647
- if (DISPATCH_TOOL_RE.test(name)) {
2648
- const label = [
2649
- str(inputObj["description"]),
2650
- str(inputObj["subagent_type"])
2651
- ].filter(Boolean).join(" ");
2652
- if (REVIEW_SHAPED_LABEL_RE.test(label)) return true;
2653
- const prompt = str(inputObj["prompt"]);
2654
- if (REVIEW_SHAPED_PROMPT_RE.test(prompt)) return true;
2655
- continue;
2656
- }
2657
- if (/^skill$/i.test(name)) {
2658
- const skillName = str(inputObj["skill"]) || str(inputObj["name"]) || str(inputObj["command"]);
2659
- if (REVIEW_SKILL_NAME_RE.test(skillName)) return true;
2660
- continue;
2661
- }
2662
- if (REVIEW_SKILL_NAME_RE.test(name)) return true;
2663
- }
2664
- }
2665
- return false;
2666
- }
2667
- function buildAdvisoryLine(diffLines) {
2668
- return `this session mutated ${diffLines} lines without a neutral review; spawn a cross-model reviewer before claiming done (delegation-policy / verify-budget)`;
2669
- }
2670
- function appendReviewSkippedTelemetry(workspaceRoot, diffLines, mutationMeasure, baseline) {
2671
- if (is_replay_mode()) return;
2672
- const ts = (/* @__PURE__ */ new Date()).toISOString();
2673
- const { line, errors } = buildReviewSkippedLine({
2674
- diff_lines: diffLines,
2675
- mutation_measure: mutationMeasure,
2676
- ts,
2677
- id: crypto.randomUUID(),
2678
- baseline
2679
- });
2680
- if (errors.length || line === null) return;
2681
- try {
2682
- const dir = path5.join(workspaceRoot, "agents", "runtime", "state", "audit");
2683
- fs5.mkdirSync(dir, { recursive: true });
2684
- const file = path5.join(dir, `${ts.slice(0, 7)}.jsonl`);
2685
- fs5.appendFileSync(file, `${JSON.stringify(line)}
2686
- `, "utf8");
2687
- } catch {
2688
- }
2689
- }
2690
- function sessionStateDir(workspaceRoot) {
2691
- return path5.join(workspaceRoot, "agents", "runtime", "state", "end-review-nudge");
2692
- }
2693
- function sessionStateFile(workspaceRoot, sessionKey) {
2694
- return path5.join(sessionStateDir(workspaceRoot), `${sessionKey}.json`);
2695
- }
2696
- function deriveSessionKey2(envelope, payload) {
2697
- const raw = str(envelope["session_id"]) || str(payload["transcript_path"] ?? payload["transcriptPath"]);
2698
- const material = raw || "unknown-session";
2699
- return crypto.createHash("sha256").update(material).digest("hex");
2700
- }
2701
- function hasFiredThisSession(workspaceRoot, sessionKey) {
2702
- try {
2703
- return fs5.existsSync(sessionStateFile(workspaceRoot, sessionKey));
2704
- } catch {
2705
- return false;
2706
- }
2707
- }
2708
- function markFiredThisSession(workspaceRoot, sessionKey, ts) {
2709
- try {
2710
- atomic_write_json(sessionStateFile(workspaceRoot, sessionKey), { fired_at: ts });
2711
- } catch {
2712
- }
2713
- }
2714
- function _read_stdin() {
2715
- return readHookStdin();
2716
- }
2717
- var FALLBACK_TO_TELEMETRY = {
2718
- absent: "absent",
2719
- unreadable: "unreadable",
2720
- "head-moved": "head_moved",
2721
- "mixed-measure": "mixed_measure"
2722
- };
2723
- function main4() {
2724
- const [envelope, payload] = unwrap(_read_stdin(), "claude");
2725
- const event = String(envelope["event"] ?? "");
2726
- if (event !== "" && event !== "stop") {
2727
- return 0;
2728
- }
2729
- const workspace_root = String(envelope["workspace_root"] ?? "").trim() || process.cwd();
2730
- let measuredLines;
2731
- let mutationMeasure;
2732
- try {
2733
- const measured = totalNonDocMutatedLinesWithMeasure(workspace_root);
2734
- measuredLines = measured.lines;
2735
- mutationMeasure = measured.measure;
2736
- } catch {
2737
- return 0;
2738
- }
2739
- const sessionKey = deriveSessionKey2(envelope, payload);
2740
- const baselineOutcome = applyBaseline(
2741
- measuredLines,
2742
- readBaseline(workspace_root, sessionKey),
2743
- // A THUNK, not a value: on the `absent` and `unreadable` branches the
2744
- // result is discarded, and those two are the steady state on every host
2745
- // without this nudge and on every session before its first baseline
2746
- // write. Passing the value eagerly spent an 11-14 ms `git rev-parse` on
2747
- // each of those stops for nothing.
2748
- () => currentHeadSha(workspace_root),
2749
- mutationMeasure
2750
- );
2751
- const diffLines = baselineOutcome.lines;
2752
- const baselineApplication = baselineOutcome.applied ? "applied" : FALLBACK_TO_TELEMETRY[baselineOutcome.fallback];
2753
- if (diffLines <= MUTATION_LINE_THRESHOLD) {
2754
- return 0;
2755
- }
2756
- if (hasFiredThisSession(workspace_root, sessionKey)) {
2757
- return 0;
2758
- }
2759
- const transcriptPath = str(
2760
- payload["transcript_path"] ?? payload["transcriptPath"]
2761
- ).trim();
2762
- let reviewerRan = false;
2763
- if (transcriptPath && isSafeTranscriptPath(transcriptPath)) {
2764
- try {
2765
- reviewerRan = scanTranscriptForReviewer(fs5.readFileSync(transcriptPath, "utf-8"));
2766
- } catch {
2767
- reviewerRan = false;
2768
- }
2769
- }
2770
- if (reviewerRan) {
2771
- return 0;
2772
- }
2773
- markFiredThisSession(workspace_root, sessionKey, (/* @__PURE__ */ new Date()).toISOString());
2774
- try {
2775
- appendReviewSkippedTelemetry(
2776
- workspace_root,
2777
- diffLines,
2778
- mutationMeasure,
2779
- baselineApplication
2780
- );
2781
- } catch {
2782
- }
2783
- process.stdout.write(
2784
- `${JSON.stringify({
2785
- decision: "warn",
2786
- reason: `end-review-nudge: session mutated ${diffLines} non-doc lines without a neutral review`,
2787
- additional_context: buildAdvisoryLine(diffLines)
2788
- })}
2789
- `
2790
- );
2791
- return EXIT_WARN2;
2792
- }
2793
- function _isCliEntry4() {
2794
- if (true) {
2795
- return false;
2796
- }
2797
- if (process.argv[1] === void 0) {
2798
- return false;
2799
- }
2800
- const argvUrl = pathToFileURL4(path5.resolve(process.argv[1])).href;
2801
- if (import.meta.url === argvUrl) {
2802
- return true;
2803
- }
2804
- try {
2805
- const here = fs5.realpathSync(fileURLToPath4(import.meta.url));
2806
- const argv = fs5.realpathSync(path5.resolve(process.argv[1]));
2807
- return here === argv;
2808
- } catch {
2809
- return false;
2810
- }
2811
- }
2812
- if (_isCliEntry4()) {
2813
- process.exit(main4());
2814
- }
2815
-
2816
- // src/scripts/hooks/subagent_ledger_hook.ts
2817
- import * as crypto2 from "node:crypto";
2818
- import * as fs6 from "node:fs";
2819
- import * as path6 from "node:path";
2820
- import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL5 } from "node:url";
2821
-
2822
- // src/scripts/_lib/subagent_response.ts
2823
- var MAX_SUMMARY_CHARS = 2e3;
2824
- var MAX_RESPONSE_LINE_CHARS = 240;
2825
- var MAX_RESPONSE_ENTRIES = 40;
2826
- var MAX_ARTIFACT_REF_CHARS = 200;
2827
- var MAX_ENVELOPE_CHARS = 12e3;
2828
- var CONFIDENCE = /* @__PURE__ */ new Set(["low", "medium", "high"]);
2829
- var RESPONSE_REQUIRED_FIELDS = [
2830
- "summary",
2831
- "handoff",
2832
- "confidence",
2833
- "findings",
2834
- "risks"
2835
- ];
2836
- function countContractFields(input) {
2837
- if (input === null || typeof input !== "object") return 0;
2838
- const r = input;
2839
- return RESPONSE_REQUIRED_FIELDS.filter((k) => Object.hasOwn(r, k)).length;
2840
- }
2841
- function validateResponse(input) {
2842
- const errors = [];
2843
- if (input === null || typeof input !== "object") {
2844
- return { valid: false, errors: ["response is not an object"] };
2845
- }
2846
- const r = input;
2847
- if (typeof r.summary !== "string") errors.push("summary missing or not a string");
2848
- if (typeof r.handoff !== "string") errors.push("handoff missing or not a string");
2849
- if (!CONFIDENCE.has(r.confidence)) errors.push(`confidence must be low|medium|high (got ${String(r.confidence)})`);
2850
- if (!Array.isArray(r.findings)) errors.push("findings missing or not an array");
2851
- if (!Array.isArray(r.risks)) errors.push("risks missing or not an array");
2852
- if (Array.isArray(r.findings)) {
2853
- for (const [i, f] of r.findings.entries()) {
2854
- if (!f || typeof f.title !== "string") errors.push(`finding[${i}] missing title`);
2855
- const refs = f?.evidence_refs;
2856
- if (refs && refs.some((x) => typeof x !== "string" || x.includes("\n"))) {
2857
- errors.push(`finding[${i}] evidence_refs must be ref tokens, not bodies`);
2858
- }
2859
- }
2394
+ const r = input;
2395
+ if (typeof r.summary !== "string") errors.push("summary missing or not a string");
2396
+ if (typeof r.handoff !== "string") errors.push("handoff missing or not a string");
2397
+ if (!CONFIDENCE.has(r.confidence)) errors.push(`confidence must be low|medium|high (got ${String(r.confidence)})`);
2398
+ if (!Array.isArray(r.findings)) errors.push("findings missing or not an array");
2399
+ if (!Array.isArray(r.risks)) errors.push("risks missing or not an array");
2400
+ if (Array.isArray(r.findings)) {
2401
+ for (const [i, f] of r.findings.entries()) {
2402
+ if (!f || typeof f.title !== "string") errors.push(`finding[${i}] missing title`);
2403
+ const refs = f?.evidence_refs;
2404
+ if (refs && refs.some((x) => typeof x !== "string" || x.includes("\n"))) {
2405
+ errors.push(`finding[${i}] evidence_refs must be ref tokens, not bodies`);
2406
+ }
2407
+ }
2860
2408
  }
2861
2409
  if (r.assumptions !== void 0) {
2862
2410
  if (!Array.isArray(r.assumptions)) {
@@ -2918,22 +2466,57 @@ function validateResponse(input) {
2918
2466
  return { valid: errors.length === 0, errors };
2919
2467
  }
2920
2468
 
2921
- // src/scripts/hooks/subagent_ledger_hook.ts
2469
+ // src/scripts/hooks/exit_codes.ts
2922
2470
  var EXIT_ALLOW3 = 0;
2923
- function isObject2(v) {
2471
+ var EXIT_BLOCK = 1;
2472
+ var EXIT_WARN2 = 2;
2473
+ var EXIT_ERROR = 3;
2474
+ var EXIT_CODES = Object.freeze([
2475
+ Object.freeze({
2476
+ code: EXIT_ALLOW3,
2477
+ name: "allow",
2478
+ meaning: "no objection; the dispatcher passes the call through",
2479
+ owner: "every concern \u2014 the default verdict",
2480
+ authorizedBy: "nothing; allowing needs no authority"
2481
+ }),
2482
+ Object.freeze({
2483
+ code: EXIT_BLOCK,
2484
+ name: "block",
2485
+ meaning: "refuse the call; the dispatcher lowers this onto the host deny channel",
2486
+ owner: "a concern declared `severity: blocking` in hook_manifest.yaml",
2487
+ authorizedBy: "the manifest entry plus its row in the BLOCKING_ALLOWLIST of tests/hooks/concern_severity.test.ts \u2014 an advisory concern emitting this is downgraded to warn by dispatch_hook._is_advisory"
2488
+ }),
2489
+ Object.freeze({
2490
+ code: EXIT_WARN2,
2491
+ name: "warn",
2492
+ meaning: "advisory; the dispatcher exits 0 and surfaces the reason",
2493
+ owner: "any concern",
2494
+ authorizedBy: "nothing; warning is the ceiling for an advisory concern and is always available to a blocking one"
2495
+ }),
2496
+ Object.freeze({
2497
+ code: EXIT_ERROR,
2498
+ name: "error",
2499
+ meaning: "the concern crashed, timed out, or could not decide \u2014 any rc >= 3. The dispatcher resolves it by the concern`s declared severity, never by the number itself",
2500
+ owner: "no concern emits this on purpose; the runtime does",
2501
+ authorizedBy: "hook_manifest.yaml `fail_closed` and `severity` together \u2014 see docs/contracts/hook-architecture-v1.md \xA7 Exit-code semantics"
2502
+ })
2503
+ ]);
2504
+
2505
+ // src/scripts/hooks/subagent_ledger_hook.ts
2506
+ function isObject(v) {
2924
2507
  return typeof v === "object" && v !== null && !Array.isArray(v);
2925
2508
  }
2926
- var LEDGER_DIR = path6.join("agents", "runtime", "state", "subagent-ledger");
2509
+ var LEDGER_DIR = path4.join("agents", "runtime", "state", "subagent-ledger");
2927
2510
  var OPEN_SUBDIR = "open";
2928
2511
  var OPEN_RECORD_TTL_MS = 24 * 60 * 60 * 1e3;
2929
2512
  function refFor(agentId) {
2930
- return crypto2.createHash("sha256").update(agentId, "utf8").digest("hex").slice(0, 12);
2513
+ return crypto.createHash("sha256").update(agentId, "utf8").digest("hex").slice(0, 12);
2931
2514
  }
2932
2515
  function unwrapPayload(envelope) {
2933
2516
  const inner = envelope["payload"];
2934
- return isObject2(inner) ? inner : envelope;
2517
+ return isObject(inner) ? inner : envelope;
2935
2518
  }
2936
- function str2(payload, ...keys) {
2519
+ function str(payload, ...keys) {
2937
2520
  for (const k of keys) {
2938
2521
  const v = payload[k];
2939
2522
  if (typeof v === "string" && v) return v;
@@ -2941,11 +2524,11 @@ function str2(payload, ...keys) {
2941
2524
  return null;
2942
2525
  }
2943
2526
  function extractParentId(payload, ownId) {
2944
- const explicit = str2(payload, "parent_agent_id", "parentAgentId");
2527
+ const explicit = str(payload, "parent_agent_id", "parentAgentId");
2945
2528
  if (explicit) return explicit;
2946
2529
  const session = payload["session"];
2947
- if (isObject2(session)) {
2948
- const sessionAgent = str2(session, "agent_id", "agentId");
2530
+ if (isObject(session)) {
2531
+ const sessionAgent = str(session, "agent_id", "agentId");
2949
2532
  if (sessionAgent && sessionAgent !== ownId) return sessionAgent;
2950
2533
  }
2951
2534
  return null;
@@ -3013,14 +2596,14 @@ function _jsonObjectCandidates(message) {
3013
2596
  return out;
3014
2597
  }
3015
2598
  function openDir(root) {
3016
- return path6.join(root, LEDGER_DIR, OPEN_SUBDIR);
2599
+ return path4.join(root, LEDGER_DIR, OPEN_SUBDIR);
3017
2600
  }
3018
2601
  function openFile(root, ref) {
3019
- return path6.join(openDir(root), `${ref}.json`);
2602
+ return path4.join(openDir(root), `${ref}.json`);
3020
2603
  }
3021
2604
  var DEPTH_BASES = /* @__PURE__ */ new Set(["observed", "asserted-parent", "assumed-root"]);
3022
2605
  function _asOpenRecord(value, expectedRef) {
3023
- if (!isObject2(value)) return null;
2606
+ if (!isObject(value)) return null;
3024
2607
  const ref = value["ref"];
3025
2608
  if (typeof ref !== "string" || ref !== expectedRef) return null;
3026
2609
  if (typeof value["started_at"] !== "string") return null;
@@ -3043,7 +2626,7 @@ function readOpenRecords(root) {
3043
2626
  const out = /* @__PURE__ */ new Map();
3044
2627
  let names;
3045
2628
  try {
3046
- names = fs6.readdirSync(openDir(root));
2629
+ names = fs4.readdirSync(openDir(root));
3047
2630
  } catch {
3048
2631
  return out;
3049
2632
  }
@@ -3051,7 +2634,7 @@ function readOpenRecords(root) {
3051
2634
  if (!name.endsWith(".json")) continue;
3052
2635
  const expectedRef = name.slice(0, -".json".length);
3053
2636
  try {
3054
- const raw = fs6.readFileSync(path6.join(openDir(root), name), "utf8");
2637
+ const raw = fs4.readFileSync(path4.join(openDir(root), name), "utf8");
3055
2638
  const rec = _asOpenRecord(JSON.parse(raw), expectedRef);
3056
2639
  if (rec) out.set(rec.ref, rec);
3057
2640
  } catch {
@@ -3066,13 +2649,13 @@ function resolveDepth(parentRef, open) {
3066
2649
  return { depth: parent.depth + 1, depth_basis: "observed" };
3067
2650
  }
3068
2651
  function ledgerFileFor2(root, ts) {
3069
- return path6.join(root, LEDGER_DIR, `${ts.slice(0, 7)}.jsonl`);
2652
+ return path4.join(root, LEDGER_DIR, `${ts.slice(0, 7)}.jsonl`);
3070
2653
  }
3071
2654
  function appendLedgerLine(root, ts, line) {
3072
2655
  if (is_replay_mode()) return;
3073
2656
  const file = ledgerFileFor2(root, ts);
3074
- fs6.mkdirSync(path6.dirname(file), { recursive: true });
3075
- fs6.appendFileSync(file, `${JSON.stringify(line)}
2657
+ fs4.mkdirSync(path4.dirname(file), { recursive: true });
2658
+ fs4.appendFileSync(file, `${JSON.stringify(line)}
3076
2659
  `, "utf8");
3077
2660
  }
3078
2661
  var WALL_CLOCK_ARMS_MS = [
@@ -3086,7 +2669,7 @@ function armsExceeded(elapsedMs) {
3086
2669
  return WALL_CLOCK_ARMS_MS.filter((a) => elapsedMs >= a.ms).map((a) => a.label);
3087
2670
  }
3088
2671
  function openRecordStats(root, nowMs = Date.now()) {
3089
- const ledgerPresent = fs6.existsSync(path6.join(root, LEDGER_DIR));
2672
+ const ledgerPresent = fs4.existsSync(path4.join(root, LEDGER_DIR));
3090
2673
  const open = readOpenRecords(root);
3091
2674
  let maxDepth = 0;
3092
2675
  let counted = 0;
@@ -3114,13 +2697,13 @@ function writeOpenRecord(root, rec) {
3114
2697
  function removeOpenRecord(root, ref) {
3115
2698
  if (is_replay_mode()) return;
3116
2699
  try {
3117
- fs6.unlinkSync(openFile(root, ref));
2700
+ fs4.unlinkSync(openFile(root, ref));
3118
2701
  } catch {
3119
2702
  }
3120
2703
  }
3121
2704
  function countOpen(root) {
3122
2705
  try {
3123
- return fs6.readdirSync(openDir(root)).filter((n) => n.endsWith(".json")).length;
2706
+ return fs4.readdirSync(openDir(root)).filter((n) => n.endsWith(".json")).length;
3124
2707
  } catch {
3125
2708
  return 0;
3126
2709
  }
@@ -3161,12 +2744,12 @@ function appendUnidentified(root, event, nowIso, payload) {
3161
2744
  unidentified: true,
3162
2745
  // The agent TYPE is an id-shaped enum and may still be present; it is
3163
2746
  // the only thing salvageable from a payload with no id.
3164
- agent_type: str2(payload, "agent_type", "agentType", "subagent_type", "subagentType"),
2747
+ agent_type: str(payload, "agent_type", "agentType", "subagent_type", "subagentType"),
3165
2748
  reason: "payload carried no agent id \u2014 nothing to correlate start with stop"
3166
2749
  });
3167
2750
  }
3168
2751
  function handleStart(root, payload, nowIso, sessionId) {
3169
- const agentId = str2(payload, "agent_id", "agentId");
2752
+ const agentId = str(payload, "agent_id", "agentId");
3170
2753
  if (agentId === null) {
3171
2754
  appendUnidentified(root, "subagent_start", nowIso, payload);
3172
2755
  return;
@@ -3177,7 +2760,7 @@ function handleStart(root, payload, nowIso, sessionId) {
3177
2760
  const { depth, depth_basis } = resolveDepth(parentRef, readOpenRecords(root));
3178
2761
  const rec = {
3179
2762
  ref,
3180
- agent_type: str2(payload, "agent_type", "agentType", "subagent_type", "subagentType"),
2763
+ agent_type: str(payload, "agent_type", "agentType", "subagent_type", "subagentType"),
3181
2764
  started_at: nowIso,
3182
2765
  parent_ref: parentRef,
3183
2766
  depth,
@@ -3204,7 +2787,7 @@ function handleStart(root, payload, nowIso, sessionId) {
3204
2787
  });
3205
2788
  }
3206
2789
  function handleStop(root, payload, nowIso, sessionId) {
3207
- const agentId = str2(payload, "agent_id", "agentId");
2790
+ const agentId = str(payload, "agent_id", "agentId");
3208
2791
  if (agentId === null) {
3209
2792
  appendUnidentified(root, "subagent_stop", nowIso, payload);
3210
2793
  return;
@@ -3216,7 +2799,7 @@ function handleStop(root, payload, nowIso, sessionId) {
3216
2799
  const started = Date.parse(rec.started_at);
3217
2800
  if (Number.isFinite(started)) durationMs = Math.max(0, Date.parse(nowIso) - started);
3218
2801
  }
3219
- const parse2 = classifyEnvelope(str2(payload, "last_assistant_message", "lastAssistantMessage"));
2802
+ const parse2 = classifyEnvelope(str(payload, "last_assistant_message", "lastAssistantMessage"));
3220
2803
  removeOpenRecord(root, ref);
3221
2804
  appendLedgerLine(root, nowIso, {
3222
2805
  event: "subagent_stop",
@@ -3227,7 +2810,7 @@ function handleStop(root, payload, nowIso, sessionId) {
3227
2810
  // relabel a cross-session stop as belonging to the dispatching session
3228
2811
  // and hide exactly the mismatch this field exists to expose.
3229
2812
  session_id: sessionId,
3230
- agent_type: rec?.agent_type ?? str2(payload, "agent_type", "agentType", "subagent_type", "subagentType"),
2813
+ agent_type: rec?.agent_type ?? str(payload, "agent_type", "agentType", "subagent_type", "subagentType"),
3231
2814
  depth: rec?.depth ?? null,
3232
2815
  depth_basis: rec?.depth_basis ?? null,
3233
2816
  // `null` = the matching start was never seen. That is a finding about
@@ -3247,12 +2830,12 @@ function handleStop(root, payload, nowIso, sessionId) {
3247
2830
  }
3248
2831
  function processEnvelope(envelope, consumerRoot) {
3249
2832
  try {
3250
- if (!isObject2(envelope)) return EXIT_ALLOW3;
2833
+ if (!isObject(envelope)) return EXIT_ALLOW3;
3251
2834
  const event = envelope["event"];
3252
2835
  if (event !== "subagent_start" && event !== "subagent_stop") return EXIT_ALLOW3;
3253
2836
  const payload = unwrapPayload(envelope);
3254
2837
  const nowIso = (/* @__PURE__ */ new Date()).toISOString();
3255
- const sessionId = str2(payload, "session_id", "sessionId") ?? str2(envelope, "session_id", "sessionId");
2838
+ const sessionId = str(payload, "session_id", "sessionId") ?? str(envelope, "session_id", "sessionId");
3256
2839
  reapStaleOpenRecords(consumerRoot, nowIso);
3257
2840
  if (event === "subagent_start") handleStart(consumerRoot, payload, nowIso, sessionId);
3258
2841
  else handleStop(consumerRoot, payload, nowIso, sessionId);
@@ -3262,43 +2845,600 @@ function processEnvelope(envelope, consumerRoot) {
3262
2845
  return EXIT_ALLOW3;
3263
2846
  }
3264
2847
  function resolveConsumerRoot(envelope) {
3265
- if (isObject2(envelope)) {
3266
- const pr = str2(envelope, "workspace_root", "project_root");
2848
+ if (isObject(envelope)) {
2849
+ const pr = str(envelope, "workspace_root", "project_root");
3267
2850
  if (pr) return pr;
3268
2851
  const payload = unwrapPayload(envelope);
3269
- const cwd = str2(payload, "cwd");
2852
+ const cwd = str(payload, "cwd");
3270
2853
  if (cwd) return cwd;
3271
2854
  }
3272
2855
  return process.cwd();
3273
2856
  }
3274
- function main5() {
2857
+ function main4() {
3275
2858
  const raw = readHookStdin();
3276
2859
  let envelope = {};
3277
2860
  try {
3278
2861
  envelope = raw.trim() ? JSON.parse(raw) : {};
3279
2862
  } catch {
3280
- return EXIT_ALLOW3;
2863
+ return EXIT_ALLOW3;
2864
+ }
2865
+ return processEnvelope(envelope, resolveConsumerRoot(envelope));
2866
+ }
2867
+ function _isCliEntry4() {
2868
+ if (true) {
2869
+ return false;
2870
+ }
2871
+ if (process.argv[1] === void 0) return false;
2872
+ const argvUrl = pathToFileURL4(path4.resolve(process.argv[1])).href;
2873
+ if (import.meta.url === argvUrl) return true;
2874
+ try {
2875
+ return fs4.realpathSync(fileURLToPath4(import.meta.url)) === fs4.realpathSync(path4.resolve(process.argv[1]));
2876
+ } catch {
2877
+ return false;
2878
+ }
2879
+ }
2880
+ if (_isCliEntry4()) process.exit(main4());
2881
+
2882
+ // src/scripts/_lib/turn_end_transcript.ts
2883
+ import * as fs7 from "node:fs";
2884
+
2885
+ // src/scripts/hooks/end_review_nudge_hook.ts
2886
+ import { spawnSync as spawnSync2 } from "node:child_process";
2887
+ import * as crypto2 from "node:crypto";
2888
+ import * as fs6 from "node:fs";
2889
+ import * as os from "node:os";
2890
+ import * as path6 from "node:path";
2891
+ import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL5 } from "node:url";
2892
+
2893
+ // src/scripts/_lib/review_baseline.ts
2894
+ import { spawnSync } from "node:child_process";
2895
+ import * as fs5 from "node:fs";
2896
+ import * as path5 from "node:path";
2897
+ function currentHeadSha(cwd) {
2898
+ try {
2899
+ const r = spawnSync("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8" });
2900
+ if (r.status !== 0) return null;
2901
+ const out = (r.stdout ?? "").trim();
2902
+ return out === "" ? null : out;
2903
+ } catch {
2904
+ return null;
2905
+ }
2906
+ }
2907
+ var BASELINE_STATE_REL = path5.join("agents", "runtime", "state", "review-baseline");
2908
+ function baselineStateDir(workspaceRoot) {
2909
+ return path5.join(workspaceRoot, BASELINE_STATE_REL);
2910
+ }
2911
+ function baselineStateFile(workspaceRoot, sessionKey) {
2912
+ return path5.join(baselineStateDir(workspaceRoot), `${sessionKey}.json`);
2913
+ }
2914
+ function parseBaseline(raw) {
2915
+ let decoded;
2916
+ try {
2917
+ decoded = JSON.parse(raw);
2918
+ } catch {
2919
+ return null;
2920
+ }
2921
+ if (typeof decoded !== "object" || decoded === null || Array.isArray(decoded)) return null;
2922
+ const o = decoded;
2923
+ const lines = o["baseline_lines"];
2924
+ if (typeof lines !== "number" || !Number.isFinite(lines) || lines < 0) return null;
2925
+ const written = o["written_at"];
2926
+ if (typeof written !== "string") return null;
2927
+ const head = o["head_sha"];
2928
+ const measure = o["measure"];
2929
+ return {
2930
+ head_sha: typeof head === "string" && head !== "" ? head : null,
2931
+ baseline_lines: lines,
2932
+ ...measure === "exact" || measure === "capped_approximation" ? { measure } : {},
2933
+ written_at: written
2934
+ };
2935
+ }
2936
+ function readBaseline(workspaceRoot, sessionKey) {
2937
+ const file = baselineStateFile(workspaceRoot, sessionKey);
2938
+ let raw;
2939
+ try {
2940
+ raw = fs5.readFileSync(file, "utf-8");
2941
+ } catch (exc) {
2942
+ const code = exc?.code;
2943
+ return code === "ENOENT" ? "absent" : "unreadable";
2944
+ }
2945
+ return parseBaseline(raw) ?? "unreadable";
2946
+ }
2947
+ function applyBaseline(measuredLines, baseline, headSha, measuredMeasure = "exact") {
2948
+ if (baseline === "absent" || baseline === "unreadable") {
2949
+ return { applied: false, fallback: baseline, lines: measuredLines };
2950
+ }
2951
+ if (baseline.measure !== "exact" || measuredMeasure !== "exact") {
2952
+ return { applied: false, fallback: "mixed-measure", lines: measuredLines };
2953
+ }
2954
+ if (baseline.head_sha !== headSha()) {
2955
+ return { applied: false, fallback: "head-moved", lines: measuredLines };
2956
+ }
2957
+ return {
2958
+ applied: true,
2959
+ baseline,
2960
+ lines: Math.max(0, measuredLines - baseline.baseline_lines)
2961
+ };
2962
+ }
2963
+
2964
+ // src/scripts/_lib/audit_field_provenance.ts
2965
+ var MAX_APPLIED_IDS = 32;
2966
+ function appliedIds(values) {
2967
+ const out = [];
2968
+ for (const raw of values ?? []) {
2969
+ const v = String(raw).trim();
2970
+ if (v === "" || out.includes(v)) continue;
2971
+ out.push(v);
2972
+ if (out.length >= MAX_APPLIED_IDS) break;
2973
+ }
2974
+ return out;
2975
+ }
2976
+
2977
+ // src/scripts/_lib/review_skipped_record.ts
2978
+ var BASELINE_APPLICATIONS = [
2979
+ "applied",
2980
+ "absent",
2981
+ "unreadable",
2982
+ "head_moved",
2983
+ "mixed_measure"
2984
+ ];
2985
+ var PRODUCER_PRIVACY_CLASS = "ids-only";
2986
+ function isNonNegInt(n) {
2987
+ return typeof n === "number" && Number.isFinite(n) && Number.isInteger(n) && n >= 0;
2988
+ }
2989
+ var HIGH_RISK_DIFF_LINES = 200;
2990
+ function buildReviewSkippedLine(input) {
2991
+ const errors = [];
2992
+ if (!isNonNegInt(input.diff_lines)) {
2993
+ errors.push("diff_lines must be a non-negative integer");
2994
+ }
2995
+ if (input.mutation_measure !== "exact" && input.mutation_measure !== "capped_approximation") {
2996
+ errors.push('mutation_measure must be "exact" or "capped_approximation"');
2997
+ }
2998
+ if (!input.ts) errors.push("ts (ISO-8601 UTC) is required");
2999
+ if (!input.id) errors.push("id (ULID, UUID, or content hash) is required");
3000
+ if (input.baseline !== void 0 && !BASELINE_APPLICATIONS.includes(input.baseline)) {
3001
+ errors.push(`baseline must be one of ${BASELINE_APPLICATIONS.join(", ")}`);
3002
+ }
3003
+ if (errors.length) return { line: null, errors };
3004
+ const line = {
3005
+ schema_version: 1,
3006
+ id: input.id,
3007
+ ts: input.ts,
3008
+ work_id: input.work_id ?? `review-skipped-${input.ts}`,
3009
+ phase: "report",
3010
+ outcome: "skipped",
3011
+ confidence_band: "medium",
3012
+ risk_class: input.diff_lines >= HIGH_RISK_DIFF_LINES ? "high" : "medium",
3013
+ memory: { asks: 0, hits: 0 },
3014
+ verify: { claims: 0, first_try_passes: 0 },
3015
+ // COMPUTED, never a producer constant.
3016
+ rules_applied: appliedIds(input.rules_applied),
3017
+ // MANDATORY on every audit line, same as the orchestration producer.
3018
+ privacy_class: PRODUCER_PRIVACY_CLASS,
3019
+ // `skills_applied` is OMITTED here, deliberately and not by oversight.
3020
+ // audit-log-v1 distinguishes an absent key ("not recorded") from `[]`
3021
+ // ("recorded, none applied"), and this writer observes a review that
3022
+ // did not happen -- it has no skill observation to offer in either
3023
+ // direction. Emitting `[]` would assert "no skills applied" on evidence
3024
+ // this producer never gathered, which is the exact fabrication the
3025
+ // absent/empty split exists to make impossible.
3026
+ persona: null,
3027
+ input_kind: "prompt",
3028
+ type: "note",
3029
+ review_skipped: {
3030
+ diff_lines: input.diff_lines,
3031
+ mutation_measure: input.mutation_measure,
3032
+ // Omitted, not defaulted, when the producer recorded nothing —
3033
+ // audit-log-v1's absent-versus-present split is the same one
3034
+ // `skills_applied` uses above, for the same reason.
3035
+ ...input.baseline === void 0 ? {} : { baseline: input.baseline }
3036
+ }
3037
+ };
3038
+ return { line, errors: [] };
3039
+ }
3040
+
3041
+ // src/scripts/hooks/end_review_nudge_hook.ts
3042
+ var MUTATION_LINE_THRESHOLD = 50;
3043
+ var UNTRACKED_FILE_CAP = 20;
3044
+ var TRANSCRIPT_SCAN_MAX_BYTES = 50 * 1024 * 1024;
3045
+ function isSafeTranscriptPath(rawPath, opts = {}) {
3046
+ if (!rawPath || !/\.jsonl$/i.test(rawPath)) return false;
3047
+ const homeDir = opts.homeDir ?? os.homedir();
3048
+ const maxBytes = opts.maxBytes ?? TRANSCRIPT_SCAN_MAX_BYTES;
3049
+ try {
3050
+ const realHome = fs6.realpathSync(homeDir);
3051
+ const realPath = fs6.realpathSync(rawPath);
3052
+ const relative3 = path6.relative(realHome, realPath);
3053
+ if (relative3 === "" || relative3.startsWith("..") || path6.isAbsolute(relative3)) {
3054
+ return false;
3055
+ }
3056
+ const { size } = fs6.statSync(realPath);
3057
+ return size <= maxBytes;
3058
+ } catch {
3059
+ return false;
3060
+ }
3061
+ }
3062
+ function parseNumstat(raw) {
3063
+ const rows = [];
3064
+ for (const line of raw.split("\n")) {
3065
+ const m = /^(\d+|-)\t(\d+|-)\t(.+)$/.exec(line);
3066
+ if (!m) continue;
3067
+ const addedStr = m[1];
3068
+ const deletedStr = m[2];
3069
+ rows.push({
3070
+ added: addedStr === "-" ? 0 : Number(addedStr),
3071
+ deleted: deletedStr === "-" ? 0 : Number(deletedStr),
3072
+ path: m[3]
3073
+ });
3074
+ }
3075
+ return rows;
3076
+ }
3077
+ var DOC_PATH_RE = /\.md$/i;
3078
+ function isDocPath(p) {
3079
+ return DOC_PATH_RE.test(p);
3080
+ }
3081
+ function nonDocMutatedLines(rows) {
3082
+ return rows.filter((r) => !isDocPath(r.path)).reduce((sum, r) => sum + r.added + r.deleted, 0);
3083
+ }
3084
+ function gitNumstatRows(cwd) {
3085
+ try {
3086
+ const r = spawnSync2("git", ["diff", "--numstat", "HEAD"], {
3087
+ cwd,
3088
+ encoding: "utf8",
3089
+ maxBuffer: 16 * 1024 * 1024
3090
+ });
3091
+ if (r.status !== 0) return [];
3092
+ return parseNumstat(r.stdout ?? "");
3093
+ } catch {
3094
+ return [];
3095
+ }
3096
+ }
3097
+ function untrackedNonDocFiles(cwd) {
3098
+ try {
3099
+ const r = spawnSync2("git", ["ls-files", "--others", "--exclude-standard"], {
3100
+ cwd,
3101
+ encoding: "utf8",
3102
+ maxBuffer: 16 * 1024 * 1024
3103
+ });
3104
+ if (r.status !== 0) return [];
3105
+ return (r.stdout ?? "").split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !isDocPath(l));
3106
+ } catch {
3107
+ return [];
3108
+ }
3109
+ }
3110
+ var BINARY_PROBE_BYTES = 8e3;
3111
+ var UNTRACKED_FILE_READ_CAP_BYTES = 8 * 1024 * 1024;
3112
+ function untrackedFileLineCount(cwd, relPath) {
3113
+ try {
3114
+ const abs = path6.resolve(cwd, relPath);
3115
+ const size = fs6.statSync(abs).size;
3116
+ if (size === 0) return 0;
3117
+ if (size > UNTRACKED_FILE_READ_CAP_BYTES) {
3118
+ return MUTATION_LINE_THRESHOLD + 1;
3119
+ }
3120
+ const buf = fs6.readFileSync(abs);
3121
+ if (buf.length === 0) return 0;
3122
+ const probe = buf.subarray(0, Math.min(buf.length, BINARY_PROBE_BYTES));
3123
+ if (probe.includes(0)) return 0;
3124
+ let lines = 0;
3125
+ for (const byte of buf) {
3126
+ if (byte === 10) lines += 1;
3127
+ }
3128
+ if (buf[buf.length - 1] !== 10) lines += 1;
3129
+ return lines;
3130
+ } catch {
3131
+ return 0;
3132
+ }
3133
+ }
3134
+ function totalNonDocMutatedLinesWithMeasure(cwd) {
3135
+ const trackedLines = nonDocMutatedLines(gitNumstatRows(cwd));
3136
+ const untracked = untrackedNonDocFiles(cwd);
3137
+ if (untracked.length > UNTRACKED_FILE_CAP) {
3138
+ return { lines: MUTATION_LINE_THRESHOLD + 1 + trackedLines, measure: "capped_approximation" };
3139
+ }
3140
+ const untrackedLines = untracked.reduce(
3141
+ (sum, relPath) => sum + untrackedFileLineCount(cwd, relPath),
3142
+ 0
3143
+ );
3144
+ return { lines: trackedLines + untrackedLines, measure: "exact" };
3145
+ }
3146
+ function isObject2(v) {
3147
+ return typeof v === "object" && v !== null && !Array.isArray(v);
3148
+ }
3149
+ function str2(v) {
3150
+ return typeof v === "string" ? v : "";
3151
+ }
3152
+ var DISPATCH_TOOL_RE = /^(agent|task)$/i;
3153
+ var REVIEW_SHAPED_LABEL_RE = /\b(review|judge|adversarial|critique|second[- ]opinion|blind\s+(pass|review)|cross-model)\b/i;
3154
+ var REVIEW_SHAPED_PROMPT_RE = /\b(code\s+review|neutral\s+review|adversarial|judge|critique|second[- ]opinion|blind\s+(pass|review)|cross-model)\b/i;
3155
+ var REVIEW_SKILL_NAME_RE = /^(review[-:]changes|judge[-:][a-z0-9-]+|adversarial-review|code-review)$/i;
3156
+ function scanTranscriptForReviewer(transcriptText) {
3157
+ for (const raw of transcriptText.split("\n")) {
3158
+ const line = raw.trim();
3159
+ if (!line) continue;
3160
+ let obj;
3161
+ try {
3162
+ obj = JSON.parse(line);
3163
+ } catch {
3164
+ continue;
3165
+ }
3166
+ if (!isObject2(obj) || obj["type"] !== "assistant") continue;
3167
+ const msg = obj["message"];
3168
+ if (!isObject2(msg)) continue;
3169
+ const content = msg["content"];
3170
+ if (!Array.isArray(content)) continue;
3171
+ for (const blk of content) {
3172
+ if (!isObject2(blk) || blk["type"] !== "tool_use") continue;
3173
+ const name = str2(blk["name"]);
3174
+ const input = blk["input"];
3175
+ const inputObj = isObject2(input) ? input : {};
3176
+ if (DISPATCH_TOOL_RE.test(name)) {
3177
+ const label = [
3178
+ str2(inputObj["description"]),
3179
+ str2(inputObj["subagent_type"])
3180
+ ].filter(Boolean).join(" ");
3181
+ if (REVIEW_SHAPED_LABEL_RE.test(label)) return true;
3182
+ const prompt = str2(inputObj["prompt"]);
3183
+ if (REVIEW_SHAPED_PROMPT_RE.test(prompt)) return true;
3184
+ continue;
3185
+ }
3186
+ if (/^skill$/i.test(name)) {
3187
+ const skillName = str2(inputObj["skill"]) || str2(inputObj["name"]) || str2(inputObj["command"]);
3188
+ if (REVIEW_SKILL_NAME_RE.test(skillName)) return true;
3189
+ continue;
3190
+ }
3191
+ if (REVIEW_SKILL_NAME_RE.test(name)) return true;
3192
+ }
3193
+ }
3194
+ return false;
3195
+ }
3196
+ function buildAdvisoryLine(diffLines) {
3197
+ return `this session mutated ${diffLines} lines without a neutral review; spawn a cross-model reviewer before claiming done (delegation-policy / verify-budget)`;
3198
+ }
3199
+ function appendReviewSkippedTelemetry(workspaceRoot, diffLines, mutationMeasure, baseline) {
3200
+ if (is_replay_mode()) return;
3201
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
3202
+ const { line, errors } = buildReviewSkippedLine({
3203
+ diff_lines: diffLines,
3204
+ mutation_measure: mutationMeasure,
3205
+ ts,
3206
+ id: crypto2.randomUUID(),
3207
+ baseline
3208
+ });
3209
+ if (errors.length || line === null) return;
3210
+ try {
3211
+ const dir = path6.join(workspaceRoot, "agents", "runtime", "state", "audit");
3212
+ fs6.mkdirSync(dir, { recursive: true });
3213
+ const file = path6.join(dir, `${ts.slice(0, 7)}.jsonl`);
3214
+ fs6.appendFileSync(file, `${JSON.stringify(line)}
3215
+ `, "utf8");
3216
+ } catch {
3217
+ }
3218
+ }
3219
+ function sessionStateDir(workspaceRoot) {
3220
+ return path6.join(workspaceRoot, "agents", "runtime", "state", "end-review-nudge");
3221
+ }
3222
+ function sessionStateFile(workspaceRoot, sessionKey) {
3223
+ return path6.join(sessionStateDir(workspaceRoot), `${sessionKey}.json`);
3224
+ }
3225
+ function deriveSessionKey2(envelope, payload) {
3226
+ const raw = str2(envelope["session_id"]) || str2(payload["transcript_path"] ?? payload["transcriptPath"]);
3227
+ const material = raw || "unknown-session";
3228
+ return crypto2.createHash("sha256").update(material).digest("hex");
3229
+ }
3230
+ function hasFiredThisSession(workspaceRoot, sessionKey) {
3231
+ try {
3232
+ return fs6.existsSync(sessionStateFile(workspaceRoot, sessionKey));
3233
+ } catch {
3234
+ return false;
3235
+ }
3236
+ }
3237
+ function markFiredThisSession(workspaceRoot, sessionKey, ts) {
3238
+ try {
3239
+ atomic_write_json(sessionStateFile(workspaceRoot, sessionKey), { fired_at: ts });
3240
+ } catch {
3241
+ }
3242
+ }
3243
+ function _read_stdin() {
3244
+ return readHookStdin();
3245
+ }
3246
+ var FALLBACK_TO_TELEMETRY = {
3247
+ absent: "absent",
3248
+ unreadable: "unreadable",
3249
+ "head-moved": "head_moved",
3250
+ "mixed-measure": "mixed_measure"
3251
+ };
3252
+ function main5() {
3253
+ const [envelope, payload] = unwrap(_read_stdin(), "claude");
3254
+ const event = String(envelope["event"] ?? "");
3255
+ if (event !== "" && event !== "stop") {
3256
+ return 0;
3257
+ }
3258
+ const workspace_root = String(envelope["workspace_root"] ?? "").trim() || process.cwd();
3259
+ let measuredLines;
3260
+ let mutationMeasure;
3261
+ try {
3262
+ const measured = totalNonDocMutatedLinesWithMeasure(workspace_root);
3263
+ measuredLines = measured.lines;
3264
+ mutationMeasure = measured.measure;
3265
+ } catch {
3266
+ return 0;
3267
+ }
3268
+ const sessionKey = deriveSessionKey2(envelope, payload);
3269
+ const baselineOutcome = applyBaseline(
3270
+ measuredLines,
3271
+ readBaseline(workspace_root, sessionKey),
3272
+ // A THUNK, not a value: on the `absent` and `unreadable` branches the
3273
+ // result is discarded, and those two are the steady state on every host
3274
+ // without this nudge and on every session before its first baseline
3275
+ // write. Passing the value eagerly spent an 11-14 ms `git rev-parse` on
3276
+ // each of those stops for nothing.
3277
+ () => currentHeadSha(workspace_root),
3278
+ mutationMeasure
3279
+ );
3280
+ const diffLines = baselineOutcome.lines;
3281
+ const baselineApplication = baselineOutcome.applied ? "applied" : FALLBACK_TO_TELEMETRY[baselineOutcome.fallback];
3282
+ if (diffLines <= MUTATION_LINE_THRESHOLD) {
3283
+ return 0;
3284
+ }
3285
+ if (hasFiredThisSession(workspace_root, sessionKey)) {
3286
+ return 0;
3287
+ }
3288
+ const transcriptPath = str2(
3289
+ payload["transcript_path"] ?? payload["transcriptPath"]
3290
+ ).trim();
3291
+ let reviewerRan = false;
3292
+ if (transcriptPath && isSafeTranscriptPath(transcriptPath)) {
3293
+ try {
3294
+ reviewerRan = scanTranscriptForReviewer(fs6.readFileSync(transcriptPath, "utf-8"));
3295
+ } catch {
3296
+ reviewerRan = false;
3297
+ }
3298
+ }
3299
+ if (reviewerRan) {
3300
+ return 0;
3301
+ }
3302
+ markFiredThisSession(workspace_root, sessionKey, (/* @__PURE__ */ new Date()).toISOString());
3303
+ try {
3304
+ appendReviewSkippedTelemetry(
3305
+ workspace_root,
3306
+ diffLines,
3307
+ mutationMeasure,
3308
+ baselineApplication
3309
+ );
3310
+ } catch {
3311
+ }
3312
+ process.stdout.write(
3313
+ `${JSON.stringify({
3314
+ decision: "warn",
3315
+ reason: `end-review-nudge: session mutated ${diffLines} non-doc lines without a neutral review`,
3316
+ additional_context: buildAdvisoryLine(diffLines)
3317
+ })}
3318
+ `
3319
+ );
3320
+ return EXIT_WARN2;
3321
+ }
3322
+ function _isCliEntry5() {
3323
+ if (true) {
3324
+ return false;
3325
+ }
3326
+ if (process.argv[1] === void 0) {
3327
+ return false;
3328
+ }
3329
+ const argvUrl = pathToFileURL5(path6.resolve(process.argv[1])).href;
3330
+ if (import.meta.url === argvUrl) {
3331
+ return true;
3332
+ }
3333
+ try {
3334
+ const here = fs6.realpathSync(fileURLToPath5(import.meta.url));
3335
+ const argv = fs6.realpathSync(path6.resolve(process.argv[1]));
3336
+ return here === argv;
3337
+ } catch {
3338
+ return false;
3339
+ }
3340
+ }
3341
+ if (_isCliEntry5()) {
3342
+ process.exit(main5());
3343
+ }
3344
+
3345
+ // src/scripts/_lib/turn_end_transcript.ts
3346
+ function readTranscriptTail(transcriptPath, opts = {}) {
3347
+ const empty = {
3348
+ lastAssistant: "",
3349
+ turnOrdinal: 0,
3350
+ toolCalls: [],
3351
+ assistantTurnTexts: []
3352
+ };
3353
+ if (!transcriptPath || !isSafeTranscriptPath(transcriptPath, opts)) return empty;
3354
+ let lines;
3355
+ try {
3356
+ if (opts.maxBytes !== void 0 && fs7.statSync(transcriptPath).size > opts.maxBytes) {
3357
+ return empty;
3358
+ }
3359
+ lines = fs7.readFileSync(transcriptPath, "utf-8").split("\n");
3360
+ } catch {
3361
+ return empty;
3281
3362
  }
3282
- return processEnvelope(envelope, resolveConsumerRoot(envelope));
3363
+ let lastAssistant = "";
3364
+ let turnOrdinal = 0;
3365
+ let toolCalls = [];
3366
+ let assistantTurnTexts = [];
3367
+ for (const rawLine of lines) {
3368
+ const line = rawLine.trim();
3369
+ if (!line) continue;
3370
+ let obj;
3371
+ try {
3372
+ obj = JSON.parse(line);
3373
+ } catch {
3374
+ continue;
3375
+ }
3376
+ if (typeof obj !== "object" || obj === null || Array.isArray(obj)) continue;
3377
+ const entry = obj;
3378
+ if (entry["isSidechain"] === true) continue;
3379
+ const role = entry["type"];
3380
+ if (role !== "assistant" && role !== "user") continue;
3381
+ const msg = entry["message"];
3382
+ if (typeof msg !== "object" || msg === null || Array.isArray(msg)) continue;
3383
+ const content = msg["content"];
3384
+ if (role === "assistant") {
3385
+ toolCalls.push(...extractToolCalls(content));
3386
+ }
3387
+ const text = _messageText(content);
3388
+ if (text === null) continue;
3389
+ if (role === "assistant") {
3390
+ lastAssistant = text;
3391
+ assistantTurnTexts.push(text);
3392
+ } else if (!isSyntheticPrompt(text)) {
3393
+ turnOrdinal += 1;
3394
+ toolCalls = [];
3395
+ assistantTurnTexts = [];
3396
+ }
3397
+ }
3398
+ return {
3399
+ lastAssistant: lastAssistant.trim(),
3400
+ turnOrdinal,
3401
+ toolCalls,
3402
+ assistantTurnTexts
3403
+ };
3283
3404
  }
3284
- function _isCliEntry5() {
3285
- if (true) {
3286
- return false;
3405
+ function extractToolCalls(content) {
3406
+ if (!Array.isArray(content)) return [];
3407
+ const out = [];
3408
+ for (const blk of content) {
3409
+ if (typeof blk !== "object" || blk === null || Array.isArray(blk)) continue;
3410
+ const b = blk;
3411
+ if (b["type"] !== "tool_use") continue;
3412
+ const name = b["name"];
3413
+ if (typeof name !== "string") continue;
3414
+ const input = b["input"];
3415
+ const call = { name };
3416
+ if (typeof input === "object" && input !== null && !Array.isArray(input)) {
3417
+ const inp = input;
3418
+ const cmd = inp["command"];
3419
+ if (typeof cmd === "string") call.command = cmd;
3420
+ const p = inp["file_path"] ?? inp["path"] ?? inp["notebook_path"];
3421
+ if (typeof p === "string") call.path = p;
3422
+ }
3423
+ out.push(call);
3287
3424
  }
3288
- if (process.argv[1] === void 0) return false;
3289
- const argvUrl = pathToFileURL5(path6.resolve(process.argv[1])).href;
3290
- if (import.meta.url === argvUrl) return true;
3291
- try {
3292
- return fs6.realpathSync(fileURLToPath5(import.meta.url)) === fs6.realpathSync(path6.resolve(process.argv[1]));
3293
- } catch {
3294
- return false;
3425
+ return out;
3426
+ }
3427
+ function _messageText(content) {
3428
+ if (typeof content === "string") return content;
3429
+ if (!Array.isArray(content)) return null;
3430
+ const parts = [];
3431
+ for (const blk of content) {
3432
+ if (typeof blk !== "object" || blk === null || Array.isArray(blk)) continue;
3433
+ const b = blk;
3434
+ if (b["type"] !== "text") continue;
3435
+ const t = b["text"];
3436
+ if (typeof t === "string") parts.push(t);
3295
3437
  }
3438
+ return parts.length > 0 ? parts.join("\n") : null;
3296
3439
  }
3297
- if (_isCliEntry5()) process.exit(main5());
3298
3440
 
3299
3441
  // src/scripts/hooks/turn_end_gate_hook.ts
3300
- var EXIT_BLOCK = 1;
3301
- var EXIT_ALLOW4 = 0;
3302
3442
  var TRANSCRIPT_READ_MAX_BYTES = 8 * 1024 * 1024;
3303
3443
  function stripFencedBlocks(text) {
3304
3444
  const out = [];
@@ -3403,7 +3543,7 @@ function detectPromissory(reply) {
3403
3543
  function readCiSettled(workspaceRoot, session_id) {
3404
3544
  if (!hasStableSessionId(session_id)) return { seen: false, settled: false };
3405
3545
  try {
3406
- const raw = fs7.readFileSync(path7.join(workspaceRoot, statePathFor(session_id)), "utf-8");
3546
+ const raw = fs8.readFileSync(path7.join(workspaceRoot, statePathFor(session_id)), "utf-8");
3407
3547
  const decoded = JSON.parse(raw);
3408
3548
  if (!owns_session_state(decoded, session_id)) return { seen: false, settled: false };
3409
3549
  if (typeof decoded === "object" && decoded !== null && !Array.isArray(decoded)) {
@@ -3439,7 +3579,7 @@ function detectCompletionClaim(reply, ci) {
3439
3579
  function readLanguagePin(workspaceRoot, session_id) {
3440
3580
  if (!hasStableSessionId(session_id)) return "und";
3441
3581
  try {
3442
- const raw = fs7.readFileSync(
3582
+ const raw = fs8.readFileSync(
3443
3583
  path7.join(workspaceRoot, statePathFor2(session_id)),
3444
3584
  "utf-8"
3445
3585
  );
@@ -3466,29 +3606,38 @@ function detectLanguage(reply, pinned) {
3466
3606
  };
3467
3607
  }
3468
3608
  var _EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
3469
- var _VERIFY_RE = /\b(test|tests|vitest|jest|pytest|phpunit|pest|tsc|eslint|ruff|mypy|pyright|clippy|phpstan|typecheck|lint[-_:a-z]*|build|ci|preflight|smoke[-_:a-z]*|check[-_:a-z]*|validate[-_:a-z]*)\b|\b(task|npm|pnpm|yarn|composer|cargo|go|make|php artisan|bun)\s+(run\s+)?\S*(test|check|lint|build|ci|typecheck)/i;
3470
- function isVerificationCommand(command) {
3471
- return _VERIFY_RE.test(command);
3472
- }
3473
- function detectUnverifiedEdit(toolCalls) {
3609
+ function detectUnverifiedEdit(toolCalls, runState = null) {
3474
3610
  let lastEdit = -1;
3475
3611
  for (let i = 0; i < toolCalls.length; i += 1) {
3476
3612
  if (_EDIT_TOOLS.has(toolCalls[i].name)) lastEdit = i;
3477
3613
  }
3478
3614
  if (lastEdit === -1) return null;
3615
+ const edited = toolCalls[lastEdit].path ?? toolCalls[lastEdit].name;
3616
+ if (runState !== null) {
3617
+ const reading = readRunEvidence(runState);
3618
+ if (reading.passed) return null;
3619
+ if (!reading.instrumentGap) {
3620
+ return {
3621
+ detector: "verification",
3622
+ evidence: edited,
3623
+ reason: describeRecordFinding(reading.failed, reading.reasons),
3624
+ mode: "record"
3625
+ };
3626
+ }
3627
+ }
3479
3628
  for (let i = lastEdit + 1; i < toolCalls.length; i += 1) {
3480
3629
  const c = toolCalls[i];
3481
3630
  if (c.name === "Bash" && c.command !== void 0 && isVerificationCommand(c.command)) {
3482
3631
  return null;
3483
3632
  }
3484
3633
  }
3485
- const edited = toolCalls[lastEdit].path;
3486
3634
  return {
3487
3635
  detector: "verification",
3488
3636
  // The path, never the diff: the evidence span is quoted into a refusal,
3489
3637
  // and `ToolCall` is shaped so a file body cannot reach it.
3490
- evidence: edited ?? toolCalls[lastEdit].name,
3491
- reason: "this turn changed a file and then ran no verification command \u2014 no test, type-check, lint or build call follows the last edit (verify-before-complete: a claim without a fresh run is unverified)"
3638
+ evidence: edited,
3639
+ reason: "this turn changed a file and then ran no verification command \u2014 no test, type-check, lint or build call follows the last edit (verify-before-complete: a claim without a fresh run is unverified)",
3640
+ mode: "transcript"
3492
3641
  };
3493
3642
  }
3494
3643
  var _SOURCE_RE = /\.(?:ts|tsx|js|jsx|mjs|cjs|vue|svelte|php|py|rb|go|rs|java|kt|swift|cs|scala|ex|exs)$/i;
@@ -3500,18 +3649,37 @@ function _isTestPath(p) {
3500
3649
  function _isProductionSource(p) {
3501
3650
  return _SOURCE_RE.test(p) && !_isTestPath(p);
3502
3651
  }
3503
- function detectUntestedChange(reply, toolCalls) {
3652
+ function detectUntestedChange(reply, toolCalls, runState = null) {
3504
3653
  const edited = toolCalls.filter((c) => _EDIT_TOOLS.has(c.name) && c.path !== void 0).map((c) => c.path);
3505
3654
  const source = edited.filter(_isProductionSource);
3506
3655
  if (source.length === 0) return null;
3507
- if (edited.some(_isTestPath)) return null;
3656
+ const wroteNewTest = toolCalls.some(
3657
+ (c) => c.name === "Write" && c.path !== void 0 && _isTestPath(c.path)
3658
+ );
3659
+ let noRedEvidence = false;
3660
+ if (edited.some(_isTestPath)) {
3661
+ if (!wroteNewTest) return null;
3662
+ if (runState === null) return null;
3663
+ if (hasRedThenGreen(runState)) return null;
3664
+ if (readRunEvidence(runState).instrumentGap) return null;
3665
+ noRedEvidence = true;
3666
+ }
3508
3667
  const prose = visibleProse(reply);
3509
3668
  const m = _COMPLETION_RE.exec(prose);
3510
3669
  if (!m) return null;
3511
3670
  if (_NEGATED_CLAIM_RE.test(_lineAround(prose, m.index))) return null;
3512
3671
  const shown = source.slice(0, 3).join(", ");
3672
+ if (noRedEvidence) {
3673
+ return {
3674
+ detector: "untested",
3675
+ evidence: shown + (source.length > 3 ? ` (+${String(source.length - 3)} more)` : ""),
3676
+ mode: "record",
3677
+ reason: NO_RED_EVIDENCE_REASON
3678
+ };
3679
+ }
3513
3680
  return {
3514
3681
  detector: "untested",
3682
+ mode: "transcript",
3515
3683
  evidence: shown + (source.length > 3 ? ` (+${String(source.length - 3)} more)` : ""),
3516
3684
  reason: "a completion claim over production code this turn changed, with NO test file touched anywhere in the turn \u2014 running a linter is not evidence the change works. Write the test that would have caught the failure, and enumerate the states the change actually has (every view, every CRUD path, the empty and error states) rather than the happy one. If a test genuinely does not belong here, say which file carries the coverage instead"
3517
3685
  };
@@ -3521,7 +3689,7 @@ function sessionStateFile2(workspaceRoot, sessionKey) {
3521
3689
  }
3522
3690
  function alreadyRefusedTurn(workspaceRoot, sessionKey, turnOrdinal) {
3523
3691
  try {
3524
- const raw = fs7.readFileSync(sessionStateFile2(workspaceRoot, sessionKey), "utf-8");
3692
+ const raw = fs8.readFileSync(sessionStateFile2(workspaceRoot, sessionKey), "utf-8");
3525
3693
  const decoded = JSON.parse(raw);
3526
3694
  if (typeof decoded === "object" && decoded !== null && !Array.isArray(decoded)) {
3527
3695
  return decoded["refused_turn"] === turnOrdinal;
@@ -3535,7 +3703,7 @@ function markRefusedTurn(workspaceRoot, sessionKey, turnOrdinal, detectors, prom
3535
3703
  const file = sessionStateFile2(workspaceRoot, sessionKey);
3536
3704
  let prev = null;
3537
3705
  try {
3538
- prev = parseRecord(fs7.readFileSync(file, "utf-8"));
3706
+ prev = parseRecord(fs8.readFileSync(file, "utf-8"));
3539
3707
  } catch {
3540
3708
  }
3541
3709
  let version;
@@ -3558,112 +3726,10 @@ function markRefusedTurn(workspaceRoot, sessionKey, turnOrdinal, detectors, prom
3558
3726
  } catch {
3559
3727
  }
3560
3728
  }
3561
- function readTranscriptTail(transcriptPath, opts = {}) {
3562
- const empty = {
3563
- lastAssistant: "",
3564
- turnOrdinal: 0,
3565
- toolCalls: [],
3566
- assistantTurnTexts: []
3567
- };
3568
- if (!transcriptPath || !isSafeTranscriptPath(transcriptPath, opts)) return empty;
3569
- let lines;
3570
- try {
3571
- if (opts.maxBytes !== void 0 && fs7.statSync(transcriptPath).size > opts.maxBytes) {
3572
- return empty;
3573
- }
3574
- lines = fs7.readFileSync(transcriptPath, "utf-8").split("\n");
3575
- } catch {
3576
- return empty;
3577
- }
3578
- let lastAssistant = "";
3579
- let turnOrdinal = 0;
3580
- let toolCalls = [];
3581
- let assistantTurnTexts = [];
3582
- for (const rawLine of lines) {
3583
- const line = rawLine.trim();
3584
- if (!line) continue;
3585
- let obj;
3586
- try {
3587
- obj = JSON.parse(line);
3588
- } catch {
3589
- continue;
3590
- }
3591
- if (typeof obj !== "object" || obj === null || Array.isArray(obj)) continue;
3592
- const entry = obj;
3593
- if (entry["isSidechain"] === true) continue;
3594
- const role = entry["type"];
3595
- if (role !== "assistant" && role !== "user") continue;
3596
- const msg = entry["message"];
3597
- if (typeof msg !== "object" || msg === null || Array.isArray(msg)) continue;
3598
- const content = msg["content"];
3599
- if (role === "assistant") {
3600
- toolCalls.push(...extractToolCalls(content));
3601
- }
3602
- const text = _messageText(content);
3603
- if (text === null) continue;
3604
- if (role === "assistant") {
3605
- lastAssistant = text;
3606
- assistantTurnTexts.push(text);
3607
- } else if (!isSyntheticPrompt(text)) {
3608
- turnOrdinal += 1;
3609
- toolCalls = [];
3610
- assistantTurnTexts = [];
3611
- }
3612
- }
3613
- return {
3614
- lastAssistant: lastAssistant.trim(),
3615
- turnOrdinal,
3616
- toolCalls,
3617
- assistantTurnTexts
3618
- };
3619
- }
3620
- function extractToolCalls(content) {
3621
- if (!Array.isArray(content)) return [];
3622
- const out = [];
3623
- for (const blk of content) {
3624
- if (typeof blk !== "object" || blk === null || Array.isArray(blk)) continue;
3625
- const b = blk;
3626
- if (b["type"] !== "tool_use") continue;
3627
- const name = b["name"];
3628
- if (typeof name !== "string") continue;
3629
- const input = b["input"];
3630
- const call = { name };
3631
- if (typeof input === "object" && input !== null && !Array.isArray(input)) {
3632
- const inp = input;
3633
- const cmd = inp["command"];
3634
- if (typeof cmd === "string") call.command = cmd;
3635
- const p = inp["file_path"] ?? inp["path"] ?? inp["notebook_path"];
3636
- if (typeof p === "string") call.path = p;
3637
- }
3638
- out.push(call);
3639
- }
3640
- return out;
3641
- }
3642
- function _messageText(content) {
3643
- if (typeof content === "string") return content;
3644
- if (!Array.isArray(content)) return null;
3645
- const parts = [];
3646
- for (const blk of content) {
3647
- if (typeof blk !== "object" || blk === null || Array.isArray(blk)) continue;
3648
- const b = blk;
3649
- if (b["type"] !== "text") continue;
3650
- const t = b["text"];
3651
- if (typeof t === "string") parts.push(t);
3652
- }
3653
- return parts.length > 0 ? parts.join("\n") : null;
3654
- }
3655
3729
  function str3(v) {
3656
3730
  return typeof v === "string" ? v : "";
3657
3731
  }
3658
- function main6() {
3659
- let envelope;
3660
- let payload;
3661
- try {
3662
- [envelope, payload] = unwrap(readHookStdin(), "claude");
3663
- } catch {
3664
- return EXIT_ALLOW4;
3665
- }
3666
- if (payload["stop_hook_active"] === true) return EXIT_ALLOW4;
3732
+ function assembleDetectorInputs(envelope, payload) {
3667
3733
  const workspaceRoot = str3(envelope["workspace_root"]) || process.cwd();
3668
3734
  let dispatchOpen = false;
3669
3735
  try {
@@ -3677,18 +3743,32 @@ function main6() {
3677
3743
  transcriptPath,
3678
3744
  { maxBytes: TRANSCRIPT_READ_MAX_BYTES }
3679
3745
  );
3680
- if (!lastAssistant) return EXIT_ALLOW4;
3746
+ if (!lastAssistant) return null;
3681
3747
  const payloadClosing = str3(
3682
3748
  payload["last_assistant_message"] ?? payload["lastAssistantMessage"]
3683
3749
  );
3684
3750
  const closingFromPayload = payloadClosing !== "" && !assistantTurnTexts.includes(payloadClosing);
3685
3751
  const turnTexts = closingFromPayload ? [...assistantTurnTexts, payloadClosing] : assistantTurnTexts;
3686
- const promptId = str3(
3687
- payload["prompt_id"] ?? payload["promptId"]
3688
- );
3752
+ const promptId = str3(payload["prompt_id"] ?? payload["promptId"]);
3689
3753
  const rawSessionId = str3(envelope["session_id"]) || "";
3690
3754
  const sessionKey = deriveSessionKey(rawSessionId || "unknown-session");
3691
- if (alreadyRefusedTurn(workspaceRoot, sessionKey, turnOrdinal)) return EXIT_ALLOW4;
3755
+ const runState = readTurnRunState(workspaceRoot, rawSessionId);
3756
+ return {
3757
+ workspaceRoot,
3758
+ rawSessionId,
3759
+ sessionKey,
3760
+ turnOrdinal,
3761
+ promptId,
3762
+ lastAssistant,
3763
+ toolCalls,
3764
+ turnTexts,
3765
+ closingFromPayload,
3766
+ dispatchOpen,
3767
+ runState
3768
+ };
3769
+ }
3770
+ function runDetectors(inputs) {
3771
+ const { dispatchOpen, lastAssistant, workspaceRoot, rawSessionId, toolCalls, runState } = inputs;
3692
3772
  const findings = [];
3693
3773
  for (const f of [
3694
3774
  // A and D are the completion-adjacent pair a pending dispatch excuses
@@ -3696,17 +3776,14 @@ function main6() {
3696
3776
  // excused by anything about a dispatch and run unchanged.
3697
3777
  dispatchOpen ? null : detectPromissory(lastAssistant),
3698
3778
  detectLanguage(lastAssistant, readLanguagePin(workspaceRoot, rawSessionId)),
3699
- detectUnverifiedEdit(toolCalls),
3700
- // Round 7 § Phase 1 — detector D. It is NOT unconditional, and this
3701
- // comment said it was while sitting one line above the `dispatchOpen`
3702
- // ternary that conditions it: A and D are both excused by an open
3703
- // dispatch, per the note at the top of this loop. Corrected 2026-08-18.
3704
- // It shipped with its own settings flag one commit earlier
3705
- // and that flag is gone: `hooks.turn_end_gate.*` was deleted on
3706
- // 2026-08-12 because a default-off safety gate is an absent one. Its
3707
- // gating is where the comment above says gating belongs — inside the
3708
- // detector: no CI observed, or a settled read, or no completion claim
3709
- // ⇒ no finding.
3779
+ detectUnverifiedEdit(toolCalls, runState),
3780
+ // Round 7 § Phase 1 — detector D. It is NOT unconditional: A and D are
3781
+ // both excused by an open dispatch, per the note above. It shipped with
3782
+ // its own settings flag one commit earlier and that flag is gone:
3783
+ // `hooks.turn_end_gate.*` was deleted on 2026-08-12 because a
3784
+ // default-off safety gate is an absent one. Its gating is where the
3785
+ // note above says gating belongs — inside the detector: no CI observed,
3786
+ // or a settled read, or no completion claim ⇒ no finding.
3710
3787
  dispatchOpen ? null : detectCompletionClaim(lastAssistant, readCiSettled(workspaceRoot, rawSessionId)),
3711
3788
  // Detector E is UNCONDITIONAL, and that is the opposite choice from A
3712
3789
  // and D one line above rather than an oversight. An open dispatch
@@ -3716,24 +3793,71 @@ function main6() {
3716
3793
  // that produced the measured failure — IS the continuation that drops
3717
3794
  // the decision, so narrowing E the same way would silence it in exactly
3718
3795
  // the case it exists for.
3719
- detectDroppedDecision(turnTexts, closingFromPayload),
3796
+ detectDroppedDecision(inputs.turnTexts, inputs.closingFromPayload),
3720
3797
  // Detector F takes the OTHER side of that same question, and the two
3721
3798
  // sitting adjacent is the clearest place to say why. E fires on a
3722
3799
  // question already asked, which an open dispatch cannot excuse. F fires
3723
3800
  // on a completion CLAIM, and a turn waiting on a subagent has not
3724
3801
  // finished — so its closing is not the claim F is about. Same slot,
3725
3802
  // opposite trigger, opposite treatment of `dispatchOpen`.
3726
- dispatchOpen ? null : detectUntestedChange(lastAssistant, toolCalls)
3803
+ dispatchOpen ? null : detectUntestedChange(lastAssistant, toolCalls, runState)
3727
3804
  ]) {
3728
3805
  if (f) findings.push(f);
3729
3806
  }
3730
- if (findings.length === 0) return EXIT_ALLOW4;
3807
+ return findings;
3808
+ }
3809
+ function recordShadow(inputs, layer) {
3810
+ if (is_replay_mode()) return;
3811
+ try {
3812
+ const findings = runDetectors(inputs);
3813
+ const file = sessionShadowFile(inputs.workspaceRoot, inputs.sessionKey);
3814
+ let prev = null;
3815
+ try {
3816
+ prev = parseShadowRecord(fs8.readFileSync(file, "utf-8"));
3817
+ } catch {
3818
+ }
3819
+ atomic_write_json(
3820
+ file,
3821
+ foldShadow(prev, {
3822
+ detectors: findings.map((f) => f.detector),
3823
+ turnOrdinal: inputs.turnOrdinal,
3824
+ at: (/* @__PURE__ */ new Date()).toISOString(),
3825
+ layer
3826
+ })
3827
+ );
3828
+ } catch {
3829
+ }
3830
+ }
3831
+ function main6() {
3832
+ let envelope;
3833
+ let payload;
3834
+ try {
3835
+ [envelope, payload] = unwrap(readHookStdin(), "claude");
3836
+ } catch {
3837
+ return EXIT_ALLOW3;
3838
+ }
3839
+ if (payload["stop_hook_active"] === true) {
3840
+ try {
3841
+ const retryInputs = assembleDetectorInputs(envelope, payload);
3842
+ if (retryInputs !== null) recordShadow(retryInputs, "stop_hook_active");
3843
+ } catch {
3844
+ }
3845
+ return EXIT_ALLOW3;
3846
+ }
3847
+ const inputs = assembleDetectorInputs(envelope, payload);
3848
+ if (inputs === null) return EXIT_ALLOW3;
3849
+ if (alreadyRefusedTurn(inputs.workspaceRoot, inputs.sessionKey, inputs.turnOrdinal)) {
3850
+ recordShadow(inputs, "refused_turn");
3851
+ return EXIT_ALLOW3;
3852
+ }
3853
+ const findings = runDetectors(inputs);
3854
+ if (findings.length === 0) return EXIT_ALLOW3;
3731
3855
  markRefusedTurn(
3732
- workspaceRoot,
3733
- sessionKey,
3734
- turnOrdinal,
3856
+ inputs.workspaceRoot,
3857
+ inputs.sessionKey,
3858
+ inputs.turnOrdinal,
3735
3859
  findings.map((f) => f.detector),
3736
- promptId
3860
+ inputs.promptId
3737
3861
  );
3738
3862
  const lines = findings.map(
3739
3863
  (f) => ` \xB7 ${f.detector}: ${f.reason}
@@ -3760,8 +3884,8 @@ function _isCliEntry6() {
3760
3884
  return true;
3761
3885
  }
3762
3886
  try {
3763
- const here = fs7.realpathSync(fileURLToPath6(import.meta.url));
3764
- const argv = fs7.realpathSync(path7.resolve(process.argv[1]));
3887
+ const here = fs8.realpathSync(fileURLToPath6(import.meta.url));
3888
+ const argv = fs8.realpathSync(path7.resolve(process.argv[1]));
3765
3889
  return here === argv;
3766
3890
  } catch {
3767
3891
  return false;
@@ -3774,7 +3898,7 @@ if (_isCliEntry6()) {
3774
3898
  // src/scripts/hooks/evidence_independence.ts
3775
3899
  import { spawnSync as spawnSync3 } from "node:child_process";
3776
3900
  import crypto3 from "node:crypto";
3777
- import fs8 from "node:fs";
3901
+ import fs9 from "node:fs";
3778
3902
  import path8 from "node:path";
3779
3903
  import { fileURLToPath as fileURLToPath7, pathToFileURL as pathToFileURL7 } from "node:url";
3780
3904
 
@@ -3794,8 +3918,6 @@ function isTestPath(p) {
3794
3918
  }
3795
3919
 
3796
3920
  // src/scripts/hooks/evidence_independence.ts
3797
- var EXIT_ALLOW5 = 0;
3798
- var EXIT_BLOCK2 = 1;
3799
3921
  var STATE_FILE3 = path8.join("agents", "state", "evidence-dispatch.json");
3800
3922
  function _isObject5(v) {
3801
3923
  return typeof v === "object" && v !== null && !Array.isArray(v);
@@ -3864,7 +3986,7 @@ function extractDispatch(envelope) {
3864
3986
  }
3865
3987
  function _ledgerStamp(consumer_root) {
3866
3988
  try {
3867
- const raw = fs8.readFileSync(
3989
+ const raw = fs9.readFileSync(
3868
3990
  path8.join(consumer_root, "agents", "state", "git-authorization.json"),
3869
3991
  "utf8"
3870
3992
  );
@@ -3876,7 +3998,7 @@ function _ledgerStamp(consumer_root) {
3876
3998
  }
3877
3999
  function _load(target) {
3878
4000
  try {
3879
- const decoded = JSON.parse(fs8.readFileSync(target, "utf8"));
4001
+ const decoded = JSON.parse(fs9.readFileSync(target, "utf8"));
3880
4002
  if (_isObject5(decoded) && Array.isArray(decoded["evaluations"])) {
3881
4003
  return decoded;
3882
4004
  }
@@ -3887,12 +4009,12 @@ function _load(target) {
3887
4009
  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.";
3888
4010
  function decide(tool, prompt, priorEvaluations) {
3889
4011
  if (!isDispatchTool(tool) || !isEvaluationPrompt(prompt)) {
3890
- return { exit: EXIT_ALLOW5, stdout: "", stderr: "", evaluations: priorEvaluations };
4012
+ return { exit: EXIT_ALLOW3, stdout: "", stderr: "", evaluations: priorEvaluations };
3891
4013
  }
3892
4014
  const preloaded = preloadedVerdict(prompt);
3893
4015
  if (preloaded !== null) {
3894
4016
  return {
3895
- exit: EXIT_BLOCK2,
4017
+ exit: EXIT_BLOCK,
3896
4018
  stdout: "",
3897
4019
  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.
3898
4020
  `,
@@ -3900,11 +4022,11 @@ function decide(tool, prompt, priorEvaluations) {
3900
4022
  };
3901
4023
  }
3902
4024
  if (!isSelfScoped(prompt)) {
3903
- return { exit: EXIT_ALLOW5, stdout: "", stderr: "", evaluations: priorEvaluations };
4025
+ return { exit: EXIT_ALLOW3, stdout: "", stderr: "", evaluations: priorEvaluations };
3904
4026
  }
3905
4027
  if (priorEvaluations >= 1) {
3906
4028
  return {
3907
- exit: EXIT_ALLOW5,
4029
+ exit: EXIT_ALLOW3,
3908
4030
  stdout: `${JSON.stringify({
3909
4031
  decision: "warn",
3910
4032
  reason: `Second evaluation dispatch of your own work in this turn. If both passes judge the SAME subject, that is verdict shopping \u2014 commissioning another with a different prompt or scope selects the answer instead of measuring it. Report what the first pass returned, then re-plan. If this is an implementation fan-out rather than a second review, carry on: this check reads prose and cannot tell the two apart with certainty.`
@@ -3915,7 +4037,7 @@ function decide(tool, prompt, priorEvaluations) {
3915
4037
  };
3916
4038
  }
3917
4039
  return {
3918
- exit: EXIT_ALLOW5,
4040
+ exit: EXIT_ALLOW3,
3919
4041
  stdout: `${JSON.stringify({ decision: "warn", reason: WARN_TEXT })}
3920
4042
  `,
3921
4043
  stderr: "",
@@ -3957,7 +4079,7 @@ function run3(stdin_text, options) {
3957
4079
  envelope = decoded;
3958
4080
  }
3959
4081
  } catch {
3960
- return EXIT_ALLOW5;
4082
+ return EXIT_ALLOW3;
3961
4083
  }
3962
4084
  }
3963
4085
  const target = path8.join(options.consumer_root, STATE_FILE3);
@@ -3976,7 +4098,7 @@ function run3(stdin_text, options) {
3976
4098
  if (staged !== null && touchesTestAndCode(staged)) {
3977
4099
  process.stdout.write(`${JSON.stringify({ decision: "warn", reason: flag })}
3978
4100
  `);
3979
- return EXIT_ALLOW5;
4101
+ return EXIT_ALLOW3;
3980
4102
  }
3981
4103
  }
3982
4104
  const decision = decide(tool, prompt, state.evaluations.length);
@@ -4033,8 +4155,8 @@ function _isCliEntry7() {
4033
4155
  return true;
4034
4156
  }
4035
4157
  try {
4036
- const here = fs8.realpathSync(fileURLToPath7(import.meta.url));
4037
- const argv = fs8.realpathSync(path8.resolve(process.argv[1]));
4158
+ const here = fs9.realpathSync(fileURLToPath7(import.meta.url));
4159
+ const argv = fs9.realpathSync(path8.resolve(process.argv[1]));
4038
4160
  return here === argv;
4039
4161
  } catch {
4040
4162
  return false;
@@ -4045,7 +4167,7 @@ if (_isCliEntry7()) {
4045
4167
  }
4046
4168
 
4047
4169
  // src/scripts/hooks/delegation_nudge_hook.ts
4048
- import fs9 from "node:fs";
4170
+ import fs10 from "node:fs";
4049
4171
  import path9 from "node:path";
4050
4172
  import { fileURLToPath as fileURLToPath8, pathToFileURL as pathToFileURL8 } from "node:url";
4051
4173
 
@@ -4327,8 +4449,6 @@ function resolveSubagentRouting(inp) {
4327
4449
  }
4328
4450
 
4329
4451
  // src/scripts/hooks/delegation_nudge_hook.ts
4330
- var EXIT_ALLOW6 = 0;
4331
- var EXIT_WARN3 = 2;
4332
4452
  function _isObject6(v) {
4333
4453
  return typeof v === "object" && v !== null && !Array.isArray(v);
4334
4454
  }
@@ -4508,28 +4628,28 @@ function main8() {
4508
4628
  const raw = readHookStdin();
4509
4629
  envelope = raw.trim() ? JSON.parse(raw) : {};
4510
4630
  } catch {
4511
- return EXIT_ALLOW6;
4631
+ return EXIT_ALLOW3;
4512
4632
  }
4513
4633
  const env = _isObject6(envelope) ? envelope : {};
4514
4634
  const event = env["event"];
4515
4635
  const slot = typeof event === "string" && event !== "" ? event : "user_prompt_submit";
4516
4636
  if (slot !== "user_prompt_submit") {
4517
- return EXIT_ALLOW6;
4637
+ return EXIT_ALLOW3;
4518
4638
  }
4519
4639
  const payload = _isObject6(env["payload"]) ? env["payload"] : env;
4520
4640
  const prompt = _extractPrompt2(payload);
4521
4641
  if (!prompt) {
4522
- return EXIT_ALLOW6;
4642
+ return EXIT_ALLOW3;
4523
4643
  }
4524
4644
  if (isSyntheticPrompt(prompt)) {
4525
- return EXIT_ALLOW6;
4645
+ return EXIT_ALLOW3;
4526
4646
  }
4527
4647
  const root = _workspaceRoot(env);
4528
4648
  const platform = env["platform"];
4529
4649
  const hostId = typeof platform === "string" && platform ? platform : null;
4530
4650
  const result = classifyPrompt(prompt, root, hostId);
4531
4651
  if (!result) {
4532
- return EXIT_ALLOW6;
4652
+ return EXIT_ALLOW3;
4533
4653
  }
4534
4654
  process.stdout.write(
4535
4655
  `${JSON.stringify({
@@ -4539,7 +4659,7 @@ function main8() {
4539
4659
  })}
4540
4660
  `
4541
4661
  );
4542
- return EXIT_WARN3;
4662
+ return EXIT_WARN2;
4543
4663
  }
4544
4664
  function _isCliEntry8() {
4545
4665
  if (true) {
@@ -4553,8 +4673,8 @@ function _isCliEntry8() {
4553
4673
  return true;
4554
4674
  }
4555
4675
  try {
4556
- const here = fs9.realpathSync(fileURLToPath8(import.meta.url));
4557
- const argv = fs9.realpathSync(path9.resolve(process.argv[1]));
4676
+ const here = fs10.realpathSync(fileURLToPath8(import.meta.url));
4677
+ const argv = fs10.realpathSync(path9.resolve(process.argv[1]));
4558
4678
  return here === argv;
4559
4679
  } catch {
4560
4680
  return false;
@@ -4951,13 +5071,13 @@ function measureDelivered(projectRulesDir, globalRulesDir) {
4951
5071
  return {
4952
5072
  project: {
4953
5073
  dir: projectRulesDir,
4954
- present: fs10.existsSync(projectRulesDir),
5074
+ present: fs11.existsSync(projectRulesDir),
4955
5075
  files: p.files,
4956
5076
  tokens: _tokens(p.chars)
4957
5077
  },
4958
5078
  global: {
4959
5079
  dir: globalRulesDir,
4960
- present: fs10.existsSync(globalRulesDir),
5080
+ present: fs11.existsSync(globalRulesDir),
4961
5081
  files: g.files,
4962
5082
  tokens: _tokens(g.chars)
4963
5083
  },
@@ -4968,10 +5088,10 @@ function storeKey(store) {
4968
5088
  return createHash3("sha256").update(path10.resolve(store)).digest("hex").slice(0, 12);
4969
5089
  }
4970
5090
  function scanStore(store, limit) {
4971
- const files = fs10.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => ({ f, m: fs10.statSync(path10.join(store, f)).mtimeMs })).sort((a, b) => b.m - a.m).slice(0, limit).map((x) => x.f);
5091
+ const files = fs11.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => ({ f, m: fs11.statSync(path10.join(store, f)).mtimeMs })).sort((a, b) => b.m - a.m).slice(0, limit).map((x) => x.f);
4972
5092
  const per_session = [];
4973
5093
  for (const f of files) {
4974
- const lines = fs10.readFileSync(path10.join(store, f), "utf8").split("\n");
5094
+ const lines = fs11.readFileSync(path10.join(store, f), "utf8").split("\n");
4975
5095
  const r = scanSession(f.replace(/\.jsonl$/, "").slice(0, 8), lines);
4976
5096
  if (r.assistant_turns > 0) {
4977
5097
  per_session.push(r);
@@ -5032,8 +5152,8 @@ function scanStore(store, limit) {
5032
5152
  };
5033
5153
  }
5034
5154
  function recordRate(file, scanned_at, rate) {
5035
- fs10.mkdirSync(path10.dirname(file), { recursive: true });
5036
- fs10.appendFileSync(file, `${JSON.stringify({ measured_at: scanned_at, ...rate })}
5155
+ fs11.mkdirSync(path10.dirname(file), { recursive: true });
5156
+ fs11.appendFileSync(file, `${JSON.stringify({ measured_at: scanned_at, ...rate })}
5037
5157
  `, "utf8");
5038
5158
  }
5039
5159
  function render(report) {
@@ -5164,14 +5284,14 @@ function main9(argv) {
5164
5284
  return 2;
5165
5285
  }
5166
5286
  const resolved = store ?? defaultStore(process.cwd());
5167
- if (!fs10.existsSync(resolved)) {
5287
+ if (!fs11.existsSync(resolved)) {
5168
5288
  process.stderr.write(`conformance:behavior: no transcript store at ${resolved}
5169
5289
  `);
5170
5290
  return 1;
5171
5291
  }
5172
5292
  const report = scanStore(resolved, Number.isFinite(limit) && limit > 0 ? limit : 30);
5173
5293
  if (out) {
5174
- fs10.writeFileSync(out, `${JSON.stringify(report, null, 2)}
5294
+ fs11.writeFileSync(out, `${JSON.stringify(report, null, 2)}
5175
5295
  `, "utf8");
5176
5296
  }
5177
5297
  if (record) {
@@ -5179,7 +5299,7 @@ function main9(argv) {
5179
5299
  }
5180
5300
  if (why !== null) {
5181
5301
  const id = why;
5182
- fs10.writeSync(
5302
+ fs11.writeSync(
5183
5303
  1,
5184
5304
  json ? `${JSON.stringify(
5185
5305
  {
@@ -5199,7 +5319,7 @@ function main9(argv) {
5199
5319
  );
5200
5320
  return 0;
5201
5321
  }
5202
- fs10.writeSync(1, json ? `${JSON.stringify(report, null, 2)}
5322
+ fs11.writeSync(1, json ? `${JSON.stringify(report, null, 2)}
5203
5323
  ` : `${render(report)}
5204
5324
  `);
5205
5325
  return 0;
@@ -5216,8 +5336,8 @@ function _isCliEntry9() {
5216
5336
  return true;
5217
5337
  }
5218
5338
  try {
5219
- const here = fs10.realpathSync(fileURLToPath9(import.meta.url));
5220
- const argv = fs10.realpathSync(path10.resolve(process.argv[1]));
5339
+ const here = fs11.realpathSync(fileURLToPath9(import.meta.url));
5340
+ const argv = fs11.realpathSync(path10.resolve(process.argv[1]));
5221
5341
  return here === argv;
5222
5342
  } catch {
5223
5343
  return false;
@@ -5228,7 +5348,7 @@ if (_isCliEntry9()) {
5228
5348
  }
5229
5349
 
5230
5350
  // src/scripts/report_carrier_divergence.ts
5231
- import * as fs11 from "node:fs";
5351
+ import * as fs12 from "node:fs";
5232
5352
  import * as os3 from "node:os";
5233
5353
  import * as path11 from "node:path";
5234
5354
  import process2 from "node:process";
@@ -5240,7 +5360,7 @@ var PROJECTION_SOURCE = path11.join("dist", "agent-src", "rules");
5240
5360
  var GLOBAL_RULES = path11.join(".claude", "rules");
5241
5361
  function _listRules(dir) {
5242
5362
  try {
5243
- return fs11.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
5363
+ return fs12.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
5244
5364
  } catch {
5245
5365
  return [];
5246
5366
  }
@@ -5248,7 +5368,7 @@ function _listRules(dir) {
5248
5368
  function _ruleType(file) {
5249
5369
  let text;
5250
5370
  try {
5251
- text = fs11.readFileSync(file, "utf-8");
5371
+ text = fs12.readFileSync(file, "utf-8");
5252
5372
  } catch {
5253
5373
  return "";
5254
5374
  }
@@ -5259,8 +5379,8 @@ function compareCarriers(projectDir, globalDir, sourceDir) {
5259
5379
  const out = {
5260
5380
  projectDir,
5261
5381
  globalDir,
5262
- globalPresent: fs11.existsSync(globalDir),
5263
- projectPresent: fs11.existsSync(projectDir),
5382
+ globalPresent: fs12.existsSync(globalDir),
5383
+ projectPresent: fs12.existsSync(projectDir),
5264
5384
  shared: 0,
5265
5385
  identical: [],
5266
5386
  provenanceOnly: [],
@@ -5293,8 +5413,8 @@ function compareCarriers(projectDir, globalDir, sourceDir) {
5293
5413
  let projectCopy;
5294
5414
  let globalCopy;
5295
5415
  try {
5296
- projectCopy = fs11.readFileSync(path11.join(projectDir, rule));
5297
- globalCopy = fs11.readFileSync(path11.join(globalDir, rule));
5416
+ projectCopy = fs12.readFileSync(path11.join(projectDir, rule));
5417
+ globalCopy = fs12.readFileSync(path11.join(globalDir, rule));
5298
5418
  verdict = comparePair(projectCopy, globalCopy);
5299
5419
  } catch {
5300
5420
  out.shared -= 1;
@@ -5481,7 +5601,7 @@ function _isCliEntry10() {
5481
5601
  if (process2.argv[1] === void 0) return false;
5482
5602
  if (import.meta.url === pathToFileURL10(path11.resolve(process2.argv[1])).href) return true;
5483
5603
  try {
5484
- return fs11.realpathSync(_HERE2) === fs11.realpathSync(path11.resolve(process2.argv[1]));
5604
+ return fs12.realpathSync(_HERE2) === fs12.realpathSync(path11.resolve(process2.argv[1]));
5485
5605
  } catch {
5486
5606
  return false;
5487
5607
  }
@@ -5491,7 +5611,7 @@ if (_isCliEntry10()) {
5491
5611
  }
5492
5612
 
5493
5613
  // src/scripts/report_skill_activation.ts
5494
- import * as fs12 from "node:fs";
5614
+ import * as fs13 from "node:fs";
5495
5615
  import * as os4 from "node:os";
5496
5616
  import * as path12 from "node:path";
5497
5617
  import { fileURLToPath as fileURLToPath11, pathToFileURL as pathToFileURL11 } from "node:url";
@@ -5507,25 +5627,25 @@ function censusSkills(root) {
5507
5627
  withTriggerCorpus: [],
5508
5628
  withDeterministicObligation: []
5509
5629
  };
5510
- if (!fs12.existsSync(root)) {
5630
+ if (!fs13.existsSync(root)) {
5511
5631
  return out;
5512
5632
  }
5513
- for (const entry of fs12.readdirSync(root, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
5633
+ for (const entry of fs13.readdirSync(root, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
5514
5634
  if (!entry.isDirectory()) {
5515
5635
  continue;
5516
5636
  }
5517
5637
  const file = path12.join(root, entry.name, "SKILL.md");
5518
- if (!fs12.existsSync(file)) {
5638
+ if (!fs13.existsSync(file)) {
5519
5639
  continue;
5520
5640
  }
5521
5641
  out.total += 1;
5522
- const text = fs12.readFileSync(file, "utf8");
5642
+ const text = fs13.readFileSync(file, "utf8");
5523
5643
  const fmEnd = text.startsWith("---") ? text.indexOf("\n---", 3) : -1;
5524
5644
  const fm = fmEnd === -1 ? "" : text.slice(0, fmEnd);
5525
5645
  if (TRIGGER_KEYS.some((k) => new RegExp(`^${k}`, "m").test(fm))) {
5526
5646
  out.withTriggerKey.push(entry.name);
5527
5647
  }
5528
- if (fs12.existsSync(path12.join(root, entry.name, "evals", "triggers.json"))) {
5648
+ if (fs13.existsSync(path12.join(root, entry.name, "evals", "triggers.json"))) {
5529
5649
  out.withTriggerCorpus.push(entry.name);
5530
5650
  }
5531
5651
  const body = fmEnd === -1 ? text : text.slice(fmEnd + 4);
@@ -5540,13 +5660,13 @@ function defaultStore2(cwd) {
5540
5660
  }
5541
5661
  function measureUsage(store, limit) {
5542
5662
  const rep = { store, sessions: 0, assistantTurns: 0, invocations: 0, bySkill: {} };
5543
- if (!fs12.existsSync(store)) {
5663
+ if (!fs13.existsSync(store)) {
5544
5664
  return rep;
5545
5665
  }
5546
- const files = fs12.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => path12.join(store, f)).sort((a, b) => fs12.statSync(b).mtimeMs - fs12.statSync(a).mtimeMs).slice(0, limit);
5666
+ const files = fs13.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => path12.join(store, f)).sort((a, b) => fs13.statSync(b).mtimeMs - fs13.statSync(a).mtimeMs).slice(0, limit);
5547
5667
  rep.sessions = files.length;
5548
5668
  for (const file of files) {
5549
- for (const line of fs12.readFileSync(file, "utf8").split("\n")) {
5669
+ for (const line of fs13.readFileSync(file, "utf8").split("\n")) {
5550
5670
  if (!line.trim()) {
5551
5671
  continue;
5552
5672
  }
@@ -5624,10 +5744,10 @@ function buildRecord(census, usage, today) {
5624
5744
  }
5625
5745
  function readRecord(repoRoot) {
5626
5746
  const file = path12.join(repoRoot, RECORD_PATH);
5627
- if (!fs12.existsSync(file)) {
5747
+ if (!fs13.existsSync(file)) {
5628
5748
  return null;
5629
5749
  }
5630
- return JSON.parse(fs12.readFileSync(file, "utf8"));
5750
+ return JSON.parse(fs13.readFileSync(file, "utf8"));
5631
5751
  }
5632
5752
  function render3(census, usage, published = null) {
5633
5753
  const lines = [];
@@ -5788,8 +5908,8 @@ function main11(argv) {
5788
5908
  }
5789
5909
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
5790
5910
  const file = path12.join(root, RECORD_PATH);
5791
- fs12.mkdirSync(path12.dirname(file), { recursive: true });
5792
- fs12.writeFileSync(file, `${JSON.stringify(buildRecord(census, usage, today), null, 2)}
5911
+ fs13.mkdirSync(path12.dirname(file), { recursive: true });
5912
+ fs13.writeFileSync(file, `${JSON.stringify(buildRecord(census, usage, today), null, 2)}
5793
5913
  `);
5794
5914
  process.stdout.write(`
5795
5915
  wrote ${RECORD_PATH} (measured ${today})
@@ -5801,7 +5921,7 @@ if (process.argv[1] !== void 0 && import.meta.url === pathToFileURL11(path12.res
5801
5921
  }
5802
5922
 
5803
5923
  // src/scripts/report_skill_obligation_violations.ts
5804
- import * as fs13 from "node:fs";
5924
+ import * as fs14 from "node:fs";
5805
5925
  import * as os5 from "node:os";
5806
5926
  import * as path13 from "node:path";
5807
5927
  import process3 from "node:process";
@@ -5859,7 +5979,7 @@ function extractObligations(repoRoot) {
5859
5979
  for (const skill of census.withDeterministicObligation) {
5860
5980
  let text;
5861
5981
  try {
5862
- text = fs13.readFileSync(path13.join(skillsRoot, skill, "SKILL.md"), "utf-8");
5982
+ text = fs14.readFileSync(path13.join(skillsRoot, skill, "SKILL.md"), "utf-8");
5863
5983
  } catch {
5864
5984
  continue;
5865
5985
  }
@@ -5988,13 +6108,13 @@ function scanStore2(repoRoot, store, limit) {
5988
6108
  };
5989
6109
  let files;
5990
6110
  try {
5991
- files = fs13.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => ({ p: path13.join(store, f), m: fs13.statSync(path13.join(store, f)).mtimeMs })).sort((a, b) => b.m - a.m).slice(0, limit).map((x) => x.p);
6111
+ files = fs14.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => ({ p: path13.join(store, f), m: fs14.statSync(path13.join(store, f)).mtimeMs })).sort((a, b) => b.m - a.m).slice(0, limit).map((x) => x.p);
5992
6112
  } catch {
5993
6113
  return report;
5994
6114
  }
5995
6115
  report.sessions = files.length;
5996
6116
  for (const file of files) {
5997
- const lines = fs13.readFileSync(file, "utf-8").split("\n");
6117
+ const lines = fs14.readFileSync(file, "utf-8").split("\n");
5998
6118
  const id = path13.basename(file).replace(/\.jsonl$/, "").slice(0, 8);
5999
6119
  const entries = _parseSession(lines);
6000
6120
  if (loadedSkillsFrom(entries).size > 0) report.sessionsWithASkill += 1;
@@ -6122,7 +6242,7 @@ function _isCliEntry11() {
6122
6242
  if (process3.argv[1] === void 0) return false;
6123
6243
  if (import.meta.url === pathToFileURL12(path13.resolve(process3.argv[1])).href) return true;
6124
6244
  try {
6125
- return fs13.realpathSync(_HERE4) === fs13.realpathSync(path13.resolve(process3.argv[1]));
6245
+ return fs14.realpathSync(_HERE4) === fs14.realpathSync(path13.resolve(process3.argv[1]));
6126
6246
  } catch {
6127
6247
  return false;
6128
6248
  }
@@ -6146,7 +6266,7 @@ function buildFunnel(opts = {}) {
6146
6266
  divergence: compareCarriers(projectRulesDir, globalRulesDir, sourceDir),
6147
6267
  skills: censusSkills(path14.join(repoRoot, SKILLS_ROOT)),
6148
6268
  usage: measureUsage(store, limit),
6149
- storePresent: fs14.existsSync(store),
6269
+ storePresent: fs15.existsSync(store),
6150
6270
  sk2: scanStore2(repoRoot, store, limit)
6151
6271
  };
6152
6272
  }
@@ -6321,7 +6441,7 @@ function _isCliEntry12() {
6321
6441
  if (process4.argv[1] === void 0) return false;
6322
6442
  if (import.meta.url === pathToFileURL13(path14.resolve(process4.argv[1])).href) return true;
6323
6443
  try {
6324
- return fs14.realpathSync(_HERE5) === fs14.realpathSync(path14.resolve(process4.argv[1]));
6444
+ return fs15.realpathSync(_HERE5) === fs15.realpathSync(path14.resolve(process4.argv[1]));
6325
6445
  } catch {
6326
6446
  return false;
6327
6447
  }
@@ -6368,10 +6488,10 @@ function installLogPath() {
6368
6488
  return path15.join(os7.homedir(), ".event4u", "agent-config", "install-log.jsonl");
6369
6489
  }
6370
6490
  function _installLogExpected(projectRoot) {
6371
- return fs15.existsSync(manifest_path(projectRoot));
6491
+ return fs16.existsSync(manifest_path(projectRoot));
6372
6492
  }
6373
6493
  function _check_txlog_clean(logPath = installLogPath(), projectRoot = null) {
6374
- const entries = fs15.existsSync(logPath) ? readRecentEntries(logPath) : [];
6494
+ const entries = fs16.existsSync(logPath) ? readRecentEntries(logPath) : [];
6375
6495
  if (entries.length === 0) {
6376
6496
  if (projectRoot !== null && _installLogExpected(projectRoot)) {
6377
6497
  return {
@@ -6409,7 +6529,7 @@ function routeTargetPaths(target) {
6409
6529
  }
6410
6530
  function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
6411
6531
  const routerPath = path15.join(packageRoot, "dist", "router.json");
6412
- if (!fs15.existsSync(routerPath)) {
6532
+ if (!fs16.existsSync(routerPath)) {
6413
6533
  return {
6414
6534
  id: "router-pointers",
6415
6535
  status: "fail",
@@ -6419,7 +6539,7 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
6419
6539
  }
6420
6540
  let router;
6421
6541
  try {
6422
- router = JSON.parse(fs15.readFileSync(routerPath, "utf8"));
6542
+ router = JSON.parse(fs16.readFileSync(routerPath, "utf8"));
6423
6543
  } catch (exc) {
6424
6544
  return {
6425
6545
  id: "router-pointers",
@@ -6434,7 +6554,7 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
6434
6554
  let targets = 0;
6435
6555
  for (const kernelId of router["kernel"] ?? []) {
6436
6556
  ids += 1;
6437
- if (!fs15.existsSync(path15.join(rulesDir, `${kernelId}.md`))) {
6557
+ if (!fs16.existsSync(path15.join(rulesDir, `${kernelId}.md`))) {
6438
6558
  broken.push(`kernel:${kernelId} \u2192 rules/${kernelId}.md missing`);
6439
6559
  }
6440
6560
  }
@@ -6442,7 +6562,7 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
6442
6562
  for (const entry of router[tier] ?? []) {
6443
6563
  const ruleId = String(entry["id"] ?? "");
6444
6564
  ids += 1;
6445
- if (!fs15.existsSync(path15.join(rulesDir, `${ruleId}.md`))) {
6565
+ if (!fs16.existsSync(path15.join(rulesDir, `${ruleId}.md`))) {
6446
6566
  broken.push(`${tier}:${ruleId} \u2192 rules/${ruleId}.md missing`);
6447
6567
  }
6448
6568
  for (const target of entry["routes_to"] ?? []) {
@@ -6452,7 +6572,7 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
6452
6572
  broken.push(`${ruleId} \u2192 unparseable routes_to ${JSON.stringify(target)}`);
6453
6573
  continue;
6454
6574
  }
6455
- if (!candidates.some((rel) => fs15.existsSync(path15.join(packageRoot, rel)))) {
6575
+ if (!candidates.some((rel) => fs16.existsSync(path15.join(packageRoot, rel)))) {
6456
6576
  broken.push(`${ruleId} \u2192 ${target} (${candidates[0]} missing)`);
6457
6577
  }
6458
6578
  }
@@ -6486,7 +6606,7 @@ function detectSmokePlatform(projectRoot) {
6486
6606
  function dispatcherRunner(packageRoot) {
6487
6607
  const localTsx = path15.join(packageRoot, "node_modules", ".bin", "tsx");
6488
6608
  const script = path15.join(packageRoot, "src", "scripts", "hooks", "dispatch_hook.ts");
6489
- if (fs15.existsSync(localTsx)) {
6609
+ if (fs16.existsSync(localTsx)) {
6490
6610
  return [localTsx, script];
6491
6611
  }
6492
6612
  return ["npx", "tsx", script];
@@ -6537,11 +6657,11 @@ function _check_hook_dispatcher(projectRoot, packageRoot = PACKAGE_ROOT, opts =
6537
6657
  }
6538
6658
  function readSettingsMode(projectRoot) {
6539
6659
  const settingsPath = path15.join(projectRoot, ".agent-settings.yml");
6540
- if (!fs15.existsSync(settingsPath)) {
6660
+ if (!fs16.existsSync(settingsPath)) {
6541
6661
  return "eager-all";
6542
6662
  }
6543
6663
  try {
6544
- const parsed = YAML.parse(fs15.readFileSync(settingsPath, "utf8"));
6664
+ const parsed = YAML.parse(fs16.readFileSync(settingsPath, "utf8"));
6545
6665
  const lean = parsed?.["lean_projection"] ?? null;
6546
6666
  const mode = lean?.["mode"];
6547
6667
  return typeof mode === "string" && mode.length > 0 ? mode : "eager-all";
@@ -6552,7 +6672,7 @@ function readSettingsMode(projectRoot) {
6552
6672
  function kernelIdsFromRouter(packageRoot) {
6553
6673
  try {
6554
6674
  const router = JSON.parse(
6555
- fs15.readFileSync(path15.join(packageRoot, "dist", "router.json"), "utf8")
6675
+ fs16.readFileSync(path15.join(packageRoot, "dist", "router.json"), "utf8")
6556
6676
  );
6557
6677
  return new Set((router["kernel"] ?? []).map(String));
6558
6678
  } catch {
@@ -6570,7 +6690,7 @@ function _check_lean_projection(projectRoot, packageRoot = PACKAGE_ROOT, project
6570
6690
  };
6571
6691
  }
6572
6692
  const rulesDir = projectedRulesDir ?? path15.join(projectRoot, ".augment", "rules");
6573
- if (!fs15.existsSync(rulesDir)) {
6693
+ if (!fs16.existsSync(rulesDir)) {
6574
6694
  return {
6575
6695
  id: "lean-projection",
6576
6696
  status: "skipped",
@@ -6579,7 +6699,7 @@ function _check_lean_projection(projectRoot, packageRoot = PACKAGE_ROOT, project
6579
6699
  };
6580
6700
  }
6581
6701
  const kernel = kernelIdsFromRouter(packageRoot);
6582
- const files = fs15.readdirSync(rulesDir).filter((f) => f.endsWith(".md") && f !== "INDEX.md").sort();
6702
+ const files = fs16.readdirSync(rulesDir).filter((f) => f.endsWith(".md") && f !== "INDEX.md").sort();
6583
6703
  const nonKernel = files.filter((f) => !kernel.has(f.replace(/\.md$/, "")));
6584
6704
  if (nonKernel.length === 0) {
6585
6705
  return {
@@ -6593,7 +6713,7 @@ function _check_lean_projection(projectRoot, packageRoot = PACKAGE_ROOT, project
6593
6713
  for (const f of nonKernel) {
6594
6714
  let text = "";
6595
6715
  try {
6596
- text = fs15.readFileSync(path15.join(rulesDir, f), "utf8");
6716
+ text = fs16.readFileSync(path15.join(rulesDir, f), "utf8");
6597
6717
  } catch {
6598
6718
  continue;
6599
6719
  }
@@ -6625,7 +6745,7 @@ function _check_host_manifest(projectRoot) {
6625
6745
  const detected = Object.entries(detectToolPresence(projectRoot)).filter(([, v]) => v === true).map(([k]) => k);
6626
6746
  const detectedNote = detected.length > 0 ? `detected surfaces: ${detected.join(", ")}` : "no surfaces detected";
6627
6747
  const settingsPath = path15.join(projectRoot, ".agent-settings.yml");
6628
- if (!fs15.existsSync(settingsPath)) {
6748
+ if (!fs16.existsSync(settingsPath)) {
6629
6749
  return {
6630
6750
  id: "host-manifest",
6631
6751
  status: "ok",
@@ -6635,7 +6755,7 @@ function _check_host_manifest(projectRoot) {
6635
6755
  }
6636
6756
  let parsed = null;
6637
6757
  try {
6638
- parsed = YAML.parse(fs15.readFileSync(settingsPath, "utf8"));
6758
+ parsed = YAML.parse(fs16.readFileSync(settingsPath, "utf8"));
6639
6759
  } catch (exc) {
6640
6760
  return {
6641
6761
  id: "host-manifest",
@@ -6814,8 +6934,8 @@ function _isCliEntry13() {
6814
6934
  return true;
6815
6935
  }
6816
6936
  try {
6817
- const here = fs15.realpathSync(fileURLToPath14(import.meta.url));
6818
- const argv = fs15.realpathSync(path15.resolve(process5.argv[1]));
6937
+ const here = fs16.realpathSync(fileURLToPath14(import.meta.url));
6938
+ const argv = fs16.realpathSync(path15.resolve(process5.argv[1]));
6819
6939
  return here === argv;
6820
6940
  } catch {
6821
6941
  return false;