@event4u/agent-config 16.1.0 → 16.3.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 (467) 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 +98 -257
  5. package/README.md +14 -12
  6. package/dist/agent-src/commands/analyze/inbox.md +1 -1
  7. package/dist/agent-src/commands/analyze/roadmap-repos.md +1 -1
  8. package/dist/agent-src/commands/bug/fix.md +17 -2
  9. package/dist/agent-src/commands/feature/plan.md +1 -1
  10. package/dist/agent-src/commands/feature/roadmap.md +1 -1
  11. package/dist/agent-src/commands/fix/ci.md +1 -1
  12. package/dist/agent-src/commands/implement-ticket.md +1 -1
  13. package/dist/agent-src/commands/jira-ticket.md +1 -1
  14. package/dist/agent-src/commands/roadmap/create.md +1 -1
  15. package/dist/agent-src/commands/roadmap/materialize.md +1 -1
  16. package/dist/agent-src/commands/roadmap/process-full.md +5 -5
  17. package/dist/agent-src/commands/roadmap/resolve-blockers.md +361 -0
  18. package/dist/agent-src/commands/roadmap/triage-parked.md +325 -0
  19. package/dist/agent-src/commands/roadmap.md +18 -4
  20. package/dist/agent-src/commands/tests/create.md +17 -3
  21. package/dist/agent-src/commands/tests/execute.md +22 -5
  22. package/dist/agent-src/commands/work.md +4 -0
  23. package/dist/agent-src/contexts/authority/decision-revisit-gate-enforcement.md +43 -0
  24. package/dist/agent-src/contexts/communication/rules-auto/design-fidelity-enforcement.md +24 -0
  25. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +6 -6
  26. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +4 -8
  27. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +32 -2
  28. package/dist/agent-src/contexts/execution/session-canary-enforcement-history.md +67 -0
  29. package/dist/agent-src/contexts/execution/toolchain-resolver.md +101 -7
  30. package/dist/agent-src/rules/active-remediation.md +1 -1
  31. package/dist/agent-src/rules/autonomous-execution.md +1 -0
  32. package/dist/agent-src/rules/broken-access-control.md +1 -0
  33. package/dist/agent-src/rules/command-suggestion-policy.md +1 -0
  34. package/dist/agent-src/rules/context-hygiene.md +1 -1
  35. package/dist/agent-src/rules/decision-revisit-gate.md +16 -33
  36. package/dist/agent-src/rules/design-fidelity.md +13 -16
  37. package/dist/agent-src/rules/doc-screenshot-hygiene.md +1 -0
  38. package/dist/agent-src/rules/domain-safety-disclaimer.md +1 -0
  39. package/dist/agent-src/rules/domain-safety-pii.md +1 -0
  40. package/dist/agent-src/rules/domain-safety-retention.md +1 -0
  41. package/dist/agent-src/rules/engineering-safety-floor.md +1 -0
  42. package/dist/agent-src/rules/evaluator-independence.md +3 -2
  43. package/dist/agent-src/rules/fast-path-marker-visibility.md +1 -0
  44. package/dist/agent-src/rules/finance-safety-floor.md +1 -0
  45. package/dist/agent-src/rules/git-history-discipline.md +1 -0
  46. package/dist/agent-src/rules/image-likeness-and-rights.md +1 -0
  47. package/dist/agent-src/rules/legal-safety-floor.md +3 -2
  48. package/dist/agent-src/rules/lethal-trifecta-guard.md +1 -0
  49. package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +1 -0
  50. package/dist/agent-src/rules/media-governance-routing.md +14 -17
  51. package/dist/agent-src/rules/persona-governance.md +2 -2
  52. package/dist/agent-src/rules/provider-lifecycle-discipline.md +1 -1
  53. package/dist/agent-src/rules/question-not-instruction.md +1 -0
  54. package/dist/agent-src/rules/roadmap-progress-sync.md +1 -1
  55. package/dist/agent-src/rules/runtime-safety.md +1 -0
  56. package/dist/agent-src/rules/secret-vcs-guard.md +1 -0
  57. package/dist/agent-src/rules/security-sensitive-stop.md +3 -2
  58. package/dist/agent-src/rules/session-canary.md +8 -62
  59. package/dist/agent-src/rules/settings-ask-protocol.md +1 -0
  60. package/dist/agent-src/rules/source-confidentiality.md +1 -0
  61. package/dist/agent-src/rules/strategy-safety-floor.md +1 -0
  62. package/dist/agent-src/rules/tool-safety.md +1 -0
  63. package/dist/agent-src/rules/ui-audit-gate.md +1 -2
  64. package/dist/agent-src/rules/untrusted-input-defense.md +1 -0
  65. package/dist/agent-src/rules/user-interrupt-priority.md +1 -0
  66. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +115 -20
  67. package/dist/agent-src/scripts/deferral_carry.ts +339 -0
  68. package/dist/agent-src/scripts/update_roadmap_progress.ts +4 -1
  69. package/dist/agent-src/skills/accessibility-auditor/data/aria-patterns.csv +1 -1
  70. package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +2 -2
  71. package/dist/agent-src/skills/api-design/data/manifest.json +2 -2
  72. package/dist/agent-src/skills/api-testing/SKILL.md +17 -0
  73. package/dist/agent-src/skills/code-intelligence/SKILL.md +14 -2
  74. package/dist/agent-src/skills/design-review/SKILL.md +30 -4
  75. package/dist/agent-src/skills/design-review/references/verification-automation.md +32 -0
  76. package/dist/agent-src/skills/design-system-capture/references/import-procedure.md +1 -1
  77. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +18 -3
  78. package/dist/agent-src/skills/existing-ui-audit/references/component-taxonomy-detection.md +56 -0
  79. package/dist/agent-src/skills/fe-design/SKILL.md +1 -1
  80. package/dist/agent-src/skills/iconography/SKILL.md +1 -1
  81. package/dist/agent-src/skills/js-library-packaging/SKILL.md +2 -2
  82. package/dist/agent-src/skills/markitdown/SKILL.md +4 -0
  83. package/dist/agent-src/skills/monorepo-workspace/SKILL.md +1 -1
  84. package/dist/agent-src/skills/playwright-testing/SKILL.md +30 -6
  85. package/dist/agent-src/skills/quality-tools/SKILL.md +11 -1
  86. package/dist/agent-src/skills/quality-tools/references/go-tools.md +76 -0
  87. package/dist/agent-src/skills/quality-tools/references/python-tools.md +104 -0
  88. package/dist/agent-src/skills/review-routing/SKILL.md +2 -2
  89. package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +1 -1
  90. package/dist/agent-src/skills/storybook-workshop/SKILL.md +3 -3
  91. package/dist/agent-src/skills/tailwind-engineer/SKILL.md +1 -1
  92. package/dist/agent-src/skills/test-case-discovery/SKILL.md +7 -0
  93. package/dist/agent-src/skills/test-driven-development/SKILL.md +17 -0
  94. package/dist/agent-src/skills/test-performance/SKILL.md +19 -1
  95. package/dist/agent-src/skills/ui-component-architect/SKILL.md +25 -2
  96. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  97. package/dist/agent-src/templates/roadmaps.md +30 -0
  98. package/dist/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
  99. package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +302 -21
  100. package/dist/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
  101. package/dist/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
  102. package/dist/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
  103. package/dist/agent-src/templates/scripts/work_engine/state.ts +57 -7
  104. package/dist/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
  105. package/dist/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
  106. package/dist/cli/python/knowledge_ingest.js +1 -1
  107. package/dist/cli/python/workspace_drive.js +1 -1
  108. package/dist/cli/registry.js +1 -1
  109. package/dist/cli-delegate/archive_completed_roadmaps.js +92 -12
  110. package/dist/cli-delegate/check_update_banner.js +12 -1
  111. package/dist/cli-delegate/chunk-523HCFYO.js +90 -0
  112. package/dist/cli-delegate/{chunk-M2DPGFZ4.js → chunk-6V6TA25H.js} +22 -152
  113. package/dist/cli-delegate/chunk-7IQE3D5S.js +191 -0
  114. package/dist/cli-delegate/{chunk-6KFEC3G7.js → chunk-CQWX336L.js} +1 -1
  115. package/dist/cli-delegate/chunk-DEIV7XP7.js +108 -0
  116. package/dist/cli-delegate/chunk-EEHDXSYD.js +2040 -0
  117. package/dist/cli-delegate/{chunk-P6ZHP5W2.js → chunk-GKE7ZVFA.js} +4 -4
  118. package/dist/cli-delegate/chunk-JQOM7TTM.js +1201 -0
  119. package/dist/cli-delegate/{chunk-UNCRJBT3.js → chunk-JQVYKIZA.js} +41 -0
  120. package/dist/cli-delegate/{chunk-FC75PGJE.js → chunk-JY2I2DXV.js} +12 -1
  121. package/dist/cli-delegate/chunk-KB5FZ655.js +208 -0
  122. package/dist/cli-delegate/{chunk-YUM5KURG.js → chunk-KR6EM23A.js} +235 -1455
  123. package/dist/cli-delegate/{chunk-LTYVIY76.js → chunk-LIYTLH6O.js} +1 -1
  124. package/dist/cli-delegate/chunk-NQ7WT43P.js +2729 -0
  125. package/dist/cli-delegate/chunk-NXEILF2K.js +2160 -0
  126. package/dist/cli-delegate/{chunk-5R7HOLAF.js → chunk-NYX3L2PZ.js} +1 -1
  127. package/dist/cli-delegate/{chunk-4E2W63DA.js → chunk-OKJUIAPS.js} +62 -1
  128. package/dist/cli-delegate/{chunk-QAJS5YWY.js → chunk-PHD7OOLE.js} +2 -1
  129. package/dist/cli-delegate/{chunk-RGNIWTEA.js → chunk-QLDHIPWR.js} +1 -1
  130. package/dist/cli-delegate/{chunk-HDVVGPQW.js → chunk-RQNNW7IF.js} +1405 -567
  131. package/dist/cli-delegate/{chunk-S3VFXMTM.js → chunk-S4UMZNES.js} +1 -1
  132. package/dist/cli-delegate/{chunk-6KM6ORRY.js → chunk-S4YWNRKO.js} +24 -8
  133. package/dist/cli-delegate/{chunk-GJCBB2UG.js → chunk-TRI6DQZ6.js} +3054 -325
  134. package/dist/cli-delegate/chunk-ULQRUR23.js +208 -0
  135. package/dist/cli-delegate/{chunk-NUHLI2RX.js → chunk-YCU3TIXH.js} +2 -0
  136. package/dist/cli-delegate/chunk-ZBZV2G5R.js +210 -0
  137. package/dist/cli-delegate/{chunk-WGXTVKCQ.js → chunk-ZUZKZYCN.js} +5 -3
  138. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  139. package/dist/cli-delegate/cmd_brand_status.js +1 -1
  140. package/dist/cli-delegate/cmd_conformance.js +1862 -809
  141. package/dist/cli-delegate/cmd_doctor.js +13 -6
  142. package/dist/cli-delegate/cmd_doctor_neighbours.js +23 -0
  143. package/dist/cli-delegate/cmd_explain.js +5 -5
  144. package/dist/cli-delegate/cmd_export.js +11 -8
  145. package/dist/cli-delegate/cmd_fleet.js +3 -3
  146. package/dist/cli-delegate/cmd_handoff.js +10 -7
  147. package/dist/cli-delegate/cmd_mcp_available.js +1 -1
  148. package/dist/cli-delegate/cmd_memory_get.js +3 -1
  149. package/dist/cli-delegate/cmd_migrate.js +2 -2
  150. package/dist/cli-delegate/cmd_packs_active.js +3 -3
  151. package/dist/cli-delegate/cmd_preflight.js +6 -6
  152. package/dist/cli-delegate/cmd_prune.js +11 -8
  153. package/dist/cli-delegate/cmd_refresh.js +2 -1
  154. package/dist/cli-delegate/cmd_route_audit.js +8 -6
  155. package/dist/cli-delegate/cmd_route_explain.js +4 -4
  156. package/dist/cli-delegate/cmd_session_recycle.js +4 -3
  157. package/dist/cli-delegate/cmd_settings_get.js +1 -1
  158. package/dist/cli-delegate/cmd_settings_migrate.js +11 -8
  159. package/dist/cli-delegate/cmd_settings_set.js +4 -4
  160. package/dist/cli-delegate/cmd_sync.js +11 -8
  161. package/dist/cli-delegate/cmd_uninstall.js +11 -8
  162. package/dist/cli-delegate/cmd_update.js +1 -1
  163. package/dist/cli-delegate/cmd_upgrade.js +3 -2
  164. package/dist/cli-delegate/cmd_validate.js +11 -8
  165. package/dist/cli-delegate/cmd_versions.js +1 -1
  166. package/dist/cli-delegate/deferral_carry.js +16 -0
  167. package/dist/cli-delegate/gate_execute.js +2 -2
  168. package/dist/cli-delegate/roadmap_gates.js +5 -5
  169. package/dist/cli-delegate/update_roadmap_progress.js +1 -1
  170. package/dist/discovery/deprecation-report.md +1 -1
  171. package/dist/discovery/discovery-manifest.json +132 -80
  172. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  173. package/dist/discovery/discovery-manifest.summary.md +3 -3
  174. package/dist/discovery/dormancy-report.md +1 -1
  175. package/dist/discovery/orphan-report.md +1 -1
  176. package/dist/discovery/packs.json +7 -5
  177. package/dist/discovery/trust-report.md +2 -2
  178. package/dist/discovery/workspaces.json +6 -4
  179. package/dist/hooks/dispatch.js +7972 -5186
  180. package/dist/hooks/dispatch.sha256 +1 -0
  181. package/dist/install/claudePathsPlan.js +47 -6
  182. package/dist/install/claudeRuleRewrite.js +7 -0
  183. package/dist/install/emit_host_rules_cli.js +39 -36
  184. package/dist/install/globalRuleScope.js +87 -0
  185. package/dist/install/host_rule_ownership.js +160 -0
  186. package/dist/install/install.mjs +2226 -1055
  187. package/dist/install/installThinLayer.js +261 -0
  188. package/dist/install/installedRuleLinks.js +176 -0
  189. package/dist/install/wizard-plan.js +24 -0
  190. package/dist/mcp/registry-manifest.json +3 -3
  191. package/dist/mcp/server.json +2 -2
  192. package/dist/mcp/server.mjs +40913 -34865
  193. package/dist/scripts/_lib/agent_settings.js +11 -0
  194. package/dist/scripts/_lib/claude_settings_hooks.js +10 -8
  195. package/dist/scripts/_lib/installed_layer.js +378 -0
  196. package/dist/scripts/_lib/lean_projection_mode.js +218 -15
  197. package/dist/scripts/_lib/rule_consequence_class.js +45 -0
  198. package/dist/scripts/_lib/rule_injection.js +67 -10
  199. package/dist/scripts/_lib/rule_law_section.js +122 -0
  200. package/dist/scripts/_lib/thin_rules.js +458 -0
  201. package/dist/scripts/_lib/windsurf_render.js +60 -0
  202. package/dist/scripts/condense.js +14 -26
  203. package/dist/scripts/project_thin_rules.js +14 -294
  204. package/dist/server/schemas/settings.js +29 -0
  205. package/dist/shared/skillRanking.js +74 -13
  206. package/dist/ui/assets/index-Cwq4d0hX.js +23 -0
  207. package/dist/ui/assets/index-Cyjy8bWO.css +1 -0
  208. package/dist/ui/index.html +2 -2
  209. package/docs/contracts/settings-classes.md +10 -6
  210. package/package.json +4 -4
  211. package/src/agent-src/scripts/archive_completed_roadmaps.ts +115 -20
  212. package/src/agent-src/scripts/deferral_carry.ts +339 -0
  213. package/src/agent-src/scripts/update_roadmap_progress.ts +4 -1
  214. package/src/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
  215. package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +302 -21
  216. package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
  217. package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
  218. package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
  219. package/src/agent-src/templates/scripts/work_engine/state.ts +57 -7
  220. package/src/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
  221. package/src/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
  222. package/src/config/agent-settings.template.yml +91 -22
  223. package/src/config/ci-local-parity.yml +18 -0
  224. package/src/config/continuity-surface.json +9 -2
  225. package/src/config/evaluator-budgets.json +4 -4
  226. package/src/config/gate-coverage.yml +327 -3
  227. package/src/config/gate-violation-baselines.json +21 -9
  228. package/src/config/hook-bundle-budget.json +25 -0
  229. package/src/config/hook-latency-budget.json +21 -0
  230. package/src/config/hook-token-budget.json +8 -3
  231. package/src/config/host-instruction-limits.json +33 -0
  232. package/src/config/metric-registry.yml +114 -0
  233. package/src/config/placeholder-drift-budget.json +10 -0
  234. package/src/config/routing-coverage-seed.json +38 -3
  235. package/src/config/rule-activation-census.json +7 -3
  236. package/src/config/rule-consequence-class.json +161 -0
  237. package/src/config/rule-law-ceilings.json +99 -0
  238. package/src/install/claudePathsPlan.ts +46 -4
  239. package/src/install/claudeRuleRewrite.ts +10 -1
  240. package/src/install/emit_host_rules_cli.ts +43 -34
  241. package/src/install/globalRuleScope.ts +109 -0
  242. package/src/install/host_rule_ownership.ts +170 -0
  243. package/src/install/installThinLayer.ts +312 -0
  244. package/src/install/installedRuleLinks.ts +220 -0
  245. package/src/install/wizard-plan.ts +24 -0
  246. package/src/scripts/_cli/cmd_doctor.ts +28 -90
  247. package/src/scripts/_cli/cmd_doctor_neighbours.ts +293 -0
  248. package/src/scripts/_cli/doctor_execution.ts +100 -17
  249. package/src/scripts/_cli/doctor_network_posture.ts +226 -0
  250. package/src/scripts/_dispatch.bash +16 -2
  251. package/src/scripts/_lib/agent_settings.ts +11 -0
  252. package/src/scripts/_lib/body_portable.ts +102 -0
  253. package/src/scripts/_lib/cc_transcript.ts +132 -1
  254. package/src/scripts/_lib/claude_settings_hooks.ts +10 -8
  255. package/src/scripts/_lib/concern_sla_window.ts +200 -0
  256. package/src/scripts/_lib/estate_offsets.ts +241 -0
  257. package/src/scripts/_lib/exemption_shape.ts +143 -0
  258. package/src/scripts/_lib/fnv.ts +23 -0
  259. package/src/scripts/_lib/foreign_scan.ts +106 -0
  260. package/src/scripts/_lib/forge_protection.ts +114 -0
  261. package/src/scripts/_lib/forge_reader.ts +558 -0
  262. package/src/scripts/_lib/gitignore_block.ts +314 -0
  263. package/src/scripts/_lib/global_deploy_inventory.ts +32 -0
  264. package/src/scripts/_lib/graph_feeder_record.ts +214 -0
  265. package/src/scripts/_lib/hook_settings.ts +73 -0
  266. package/src/scripts/_lib/host_capability.ts +40 -19
  267. package/src/scripts/_lib/host_env_write.ts +111 -0
  268. package/src/scripts/_lib/host_hook_merge.ts +165 -0
  269. package/src/scripts/_lib/installed_layer.ts +538 -0
  270. package/src/scripts/_lib/json_merge.ts +82 -0
  271. package/src/scripts/_lib/json_pointers.ts +93 -11
  272. package/src/scripts/_lib/lean_projection_mode.ts +275 -15
  273. package/src/scripts/_lib/llm_proposer_transport.ts +14 -4
  274. package/src/scripts/_lib/machine_wake.ts +16 -3
  275. package/src/scripts/_lib/neighbour_census.ts +760 -0
  276. package/src/scripts/_lib/neighbour_scan.ts +122 -0
  277. package/src/scripts/_lib/neighbour_tool_use.ts +189 -0
  278. package/src/scripts/_lib/obligations.ts +119 -8
  279. package/src/scripts/_lib/probe_inputs.ts +271 -0
  280. package/src/scripts/_lib/prompt_shape.ts +46 -0
  281. package/src/scripts/_lib/read_surface_scan.ts +322 -0
  282. package/src/scripts/_lib/release_highlights.ts +2 -1
  283. package/src/scripts/_lib/release_migration_gate.ts +19 -3
  284. package/src/scripts/_lib/reserved_name_sweep.ts +73 -0
  285. package/src/scripts/_lib/retrieval_sanitize.ts +77 -16
  286. package/src/scripts/_lib/review_coverage.ts +134 -0
  287. package/src/scripts/_lib/rule_consequence_class.ts +71 -0
  288. package/src/scripts/_lib/rule_injection.ts +130 -10
  289. package/src/scripts/_lib/rule_law_section.ts +147 -0
  290. package/src/scripts/_lib/rule_layer_overlap.ts +58 -0
  291. package/src/scripts/_lib/shell_write_shapes.ts +108 -0
  292. package/src/scripts/_lib/skill_origin.ts +254 -0
  293. package/src/scripts/_lib/structural_hiding.ts +622 -0
  294. package/src/scripts/_lib/structured_ask.ts +64 -21
  295. package/src/scripts/_lib/thin_rules.ts +540 -0
  296. package/src/scripts/_lib/touched_file_quality.ts +434 -0
  297. package/src/scripts/_lib/turn_end_refusals.ts +600 -4
  298. package/src/scripts/_lib/turn_end_transcript.ts +216 -0
  299. package/src/scripts/_lib/verification_command.ts +324 -0
  300. package/src/scripts/_lib/verification_evidence.ts +647 -0
  301. package/src/scripts/_lib/verify_clause.ts +280 -0
  302. package/src/scripts/_lib/windsurf_render.ts +63 -0
  303. package/src/scripts/ai_council/cli_call_budget.ts +13 -5
  304. package/src/scripts/ai_council/clients.ts +26 -57
  305. package/src/scripts/ai_council/config.ts +1 -1
  306. package/src/scripts/ai_council/vendor_defaults.ts +45 -0
  307. package/src/scripts/ai_team/team_dispatch.ts +4 -0
  308. package/src/scripts/audit_skill_overlap.ts +261 -0
  309. package/src/scripts/before_complete_hook.ts +573 -25
  310. package/src/scripts/bench_hook_latency.ts +412 -5
  311. package/src/scripts/bench_touched_file_quality.ts +238 -0
  312. package/src/scripts/cache_realization_report.ts +61 -1
  313. package/src/scripts/check_argument_hint.ts +287 -0
  314. package/src/scripts/check_claims.ts +125 -1
  315. package/src/scripts/check_enforcement_coverage.ts +38 -9
  316. package/src/scripts/check_enforcement_matrix.ts +621 -22
  317. package/src/scripts/check_estate_count.ts +130 -153
  318. package/src/scripts/check_finding_dispositions.ts +138 -7
  319. package/src/scripts/check_generator_sync.ts +71 -0
  320. package/src/scripts/check_hook_bundle_composition.ts +370 -0
  321. package/src/scripts/check_host_docs_digest.ts +786 -0
  322. package/src/scripts/check_host_format_column.ts +559 -0
  323. package/src/scripts/check_input_references.ts +228 -0
  324. package/src/scripts/check_inputs_parity.ts +235 -0
  325. package/src/scripts/check_kernel_edit_ratified.ts +89 -9
  326. package/src/scripts/check_kill_switch_table.ts +294 -0
  327. package/src/scripts/check_memory.ts +7 -1
  328. package/src/scripts/check_placeholder_drift.ts +221 -0
  329. package/src/scripts/check_read_surface_coverage.ts +515 -0
  330. package/src/scripts/check_release_highlights.ts +10 -0
  331. package/src/scripts/check_release_holds.ts +2 -1
  332. package/src/scripts/check_release_pr_shape.ts +38 -15
  333. package/src/scripts/check_routing_coverage.ts +378 -11
  334. package/src/scripts/check_standing_rule_delivery.ts +33 -13
  335. package/src/scripts/check_static_parity.ts +17 -0
  336. package/src/scripts/check_verify_expectation_delta.ts +521 -0
  337. package/src/scripts/ci_settle.ts +145 -19
  338. package/src/scripts/closure_scan.ts +115 -1
  339. package/src/scripts/code_graph/detect.ts +127 -7
  340. package/src/scripts/code_graph/foreign.ts +194 -0
  341. package/src/scripts/code_graph/query.ts +217 -4
  342. package/src/scripts/code_graph/types.ts +26 -2
  343. package/src/scripts/code_graph/validate.ts +3 -1
  344. package/src/scripts/code_graph/verbs.ts +103 -0
  345. package/src/scripts/compile_hook_manifest.ts +90 -14
  346. package/src/scripts/condense.ts +14 -27
  347. package/src/scripts/estate_exemption_shape.ts +202 -0
  348. package/src/scripts/external_sources_denylist.json +29 -4
  349. package/src/scripts/generate_host_cost_table.ts +1 -1
  350. package/src/scripts/hook_manifest.json +1 -1
  351. package/src/scripts/hook_manifest.yaml +192 -14
  352. package/src/scripts/hooks/block_config_weakening.ts +294 -4
  353. package/src/scripts/hooks/block_kernel_rule_writes.ts +9 -78
  354. package/src/scripts/hooks/block_plumbing_writes.ts +342 -0
  355. package/src/scripts/hooks/bundle_integrity.ts +291 -0
  356. package/src/scripts/hooks/chain_nudge_hook.ts +1 -2
  357. package/src/scripts/hooks/code_graph_context_hook.ts +253 -25
  358. package/src/scripts/hooks/comment_discipline_hook.ts +1 -3
  359. package/src/scripts/hooks/concern_failure_policy.ts +280 -0
  360. package/src/scripts/hooks/concern_registry.ts +2 -0
  361. package/src/scripts/hooks/concern_timings.ts +94 -0
  362. package/src/scripts/hooks/delegation_nudge_hook.ts +1 -2
  363. package/src/scripts/hooks/design_pass_hook.ts +100 -9
  364. package/src/scripts/hooks/design_slop_hook.ts +1 -2
  365. package/src/scripts/hooks/dispatch_hook.ts +77 -71
  366. package/src/scripts/hooks/edit_shape_hook.ts +1 -2
  367. package/src/scripts/hooks/end_review_nudge_hook.ts +1 -1
  368. package/src/scripts/hooks/evidence_independence.ts +1 -2
  369. package/src/scripts/hooks/exit_codes.ts +129 -0
  370. package/src/scripts/hooks/host_lowering.json +1 -0
  371. package/src/scripts/hooks/host_lowering.ts +146 -3
  372. package/src/scripts/hooks/host_lowering.yaml +262 -46
  373. package/src/scripts/hooks/host_semantics.ts +30 -2
  374. package/src/scripts/hooks/injection_budget.ts +9 -2
  375. package/src/scripts/hooks/interruption_ledger_hook.ts +1 -2
  376. package/src/scripts/hooks/journal_record_hook.ts +1 -2
  377. package/src/scripts/hooks/memory_recall_hook.ts +1 -2
  378. package/src/scripts/hooks/obligation_settle_hook.ts +151 -5
  379. package/src/scripts/hooks/one_question_per_ask_hook.ts +43 -6
  380. package/src/scripts/hooks/orchestration_record_hook.ts +1 -2
  381. package/src/scripts/hooks/payload_stub.ts +10 -1
  382. package/src/scripts/hooks/permission_gate.ts +1 -2
  383. package/src/scripts/hooks/push_settle_hook.ts +1 -3
  384. package/src/scripts/hooks/replay_hook.ts +2 -1
  385. package/src/scripts/hooks/reread_guard_hook.ts +1 -2
  386. package/src/scripts/hooks/review_baseline_hook.ts +1 -2
  387. package/src/scripts/hooks/rtk_wrap_hook.ts +1 -2
  388. package/src/scripts/hooks/rule_inject_hook.ts +765 -68
  389. package/src/scripts/hooks/run_continuation_hook.ts +13 -15
  390. package/src/scripts/hooks/skill_route_hook.ts +1 -2
  391. package/src/scripts/hooks/source_first_gate_hook.ts +1 -2
  392. package/src/scripts/hooks/spawn_guard_shadow_hook.ts +1 -2
  393. package/src/scripts/hooks/subagent_ledger_hook.ts +1 -2
  394. package/src/scripts/hooks/suggestion_capture_hook.ts +1 -1
  395. package/src/scripts/hooks/table_fingerprint.ts +70 -0
  396. package/src/scripts/hooks/telemetry_flush_hook.ts +1 -2
  397. package/src/scripts/hooks/telemetry_usage_hook.ts +65 -10
  398. package/src/scripts/hooks/tool_result_bytes_hook.ts +93 -9
  399. package/src/scripts/hooks/turn_end_gate_hook.ts +421 -324
  400. package/src/scripts/hooks/ui_route_nudge_hook.ts +1 -2
  401. package/src/scripts/hooks_status.ts +112 -0
  402. package/src/scripts/injection_scan_hook.ts +7 -0
  403. package/src/scripts/install.ts +75 -189
  404. package/src/scripts/installed_layer_report.ts +103 -0
  405. package/src/scripts/language_mirror_hook.ts +15 -6
  406. package/src/scripts/learning_sidecar.ts +119 -8
  407. package/src/scripts/lint_deny_text.ts +419 -0
  408. package/src/scripts/lint_exit_codes.ts +262 -0
  409. package/src/scripts/lint_hook_manifest.ts +108 -0
  410. package/src/scripts/lint_major_migration_sections.ts +156 -26
  411. package/src/scripts/lint_pack_boundaries.ts +63 -5
  412. package/src/scripts/lint_roadmap_blockers.ts +78 -12
  413. package/src/scripts/lint_rule_law_section.ts +517 -0
  414. package/src/scripts/lint_skill_descriptions.ts +1 -1
  415. package/src/scripts/lint_skill_scripts_readonly.ts +1 -1
  416. package/src/scripts/lint_workflow_security.ts +161 -5
  417. package/src/scripts/mcp_server/consumer_tool_catalog.json +53 -0
  418. package/src/scripts/mcp_server/graph_tools.ts +72 -3
  419. package/src/scripts/mcp_server/prompts.ts +142 -1
  420. package/src/scripts/measure_routing_signal.ts +54 -16
  421. package/src/scripts/measure_skill_ranker_baseline.ts +515 -53
  422. package/src/scripts/measure_turn_end_gate.ts +193 -0
  423. package/src/scripts/memory_learn_hook.ts +91 -6
  424. package/src/scripts/memory_lookup.ts +1 -1
  425. package/src/scripts/model_rule_injection.ts +52 -5
  426. package/src/scripts/prepack-check.mjs +53 -0
  427. package/src/scripts/project_thin_rules.ts +34 -327
  428. package/src/scripts/release_publication.ts +13 -1
  429. package/src/scripts/report_evidence_temperature.ts +657 -0
  430. package/src/scripts/report_installed_rule_links.ts +157 -0
  431. package/src/scripts/report_invocation_surface.ts +383 -0
  432. package/src/scripts/report_label_agreement.ts +233 -0
  433. package/src/scripts/report_obligation_carriers.ts +699 -0
  434. package/src/scripts/report_obligation_mechanism.ts +230 -0
  435. package/src/scripts/report_obligation_writer_split.ts +236 -0
  436. package/src/scripts/report_single_token_triggers.ts +277 -0
  437. package/src/scripts/report_skill_menu_census.ts +208 -0
  438. package/src/scripts/report_skill_ranker_confusion.ts +442 -0
  439. package/src/scripts/report_standing_payload_by_host.ts +188 -15
  440. package/src/scripts/report_verification_record_coverage.ts +306 -0
  441. package/src/scripts/roadmap_verify_share.ts +213 -0
  442. package/src/scripts/rule_activation_census.ts +5 -2
  443. package/src/scripts/rule_trigger_eval.ts +9 -9
  444. package/src/scripts/schemas/command.schema.json +44 -0
  445. package/src/scripts/schemas/review-findings.schema.json +7 -2
  446. package/src/scripts/schemas/rule.schema.json +5 -0
  447. package/src/scripts/schemas/skill.schema.json +44 -0
  448. package/src/scripts/self_review_gate.ts +421 -17
  449. package/src/scripts/semantic_parity_census.ts +314 -0
  450. package/src/scripts/session_eol_report.ts +54 -1
  451. package/src/scripts/skill_tools/score_skill_relevance.ts +290 -25
  452. package/src/scripts/sweep_dead_scan_roots.ts +1 -1
  453. package/src/scripts/sweep_skill_ranker_signals.ts +129 -0
  454. package/src/scripts/sync_gitignore.ts +29 -235
  455. package/src/scripts/trigger_eval_grandfather.json +0 -7
  456. package/src/scripts/trigger_eval_rotation.ts +122 -27
  457. package/src/scripts/ui_conformance_probe.ts +156 -4
  458. package/src/scripts/update_prices.ts +85 -3
  459. package/src/scripts/write_bundle_digest.mjs +74 -0
  460. package/src/server/schemas/settings.ts +41 -0
  461. package/src/shared/skillRanking.ts +117 -11
  462. package/src/templates/consumer-settings/ONBOARDING.md +1 -0
  463. package/dist/cli-delegate/chunk-FXYUYU4N.js +0 -4416
  464. package/dist/cli-delegate/chunk-QJWAUMJW.js +0 -1072
  465. package/dist/cli-delegate/chunk-YDPILCOL.js +0 -152
  466. package/dist/ui/assets/index-DH1xc62v.js +0 -29
  467. package/dist/ui/assets/index-jSfMW8_t.css +0 -1
@@ -7,14 +7,24 @@
7
7
  * none, and a gate that blocks unobserved is the shape that discipline exists to
8
8
  * prevent.
9
9
  *
10
- * This module is the reader. It owns three things and deliberately not a fourth:
10
+ * This module is the reader. It owns five things and deliberately not a sixth:
11
11
  *
12
12
  * 1. the on-disk record shape, extended so a session's refusals are COUNTED
13
13
  * rather than overwritten (§ Phase 1 step 1.1);
14
14
  * 2. a TTL prune, which the gate's own header admits is missing (step 1.2);
15
15
  * 3. the split by recorded package version, so claim 10's prediction — that
16
16
  * refusals correlate with the local 12.1 install — is TESTED rather than
17
- * assumed (step 1.3).
17
+ * assumed (step 1.3);
18
+ * 4. the SHADOW record — what a retry would have been refused for, written on
19
+ * an allow path (`road-to-a-stop-that-holds` step 2.1);
20
+ * 5. the rollup over those records (step 2.2). Read `ShadowLayerStats` before
21
+ * quoting anything it produces: the share it computes is NOT the Q1 the
22
+ * demotion contract registers bars against, and the difference is named
23
+ * there rather than left to a reader of the number.
24
+ *
25
+ * (This list read "three things and deliberately not a fourth" until 2026-10-01.
26
+ * It was accurate when written and silently false for the two steps after it —
27
+ * an independent review caught it. Items 4 and 5 are the two that landed.)
18
28
  *
19
29
  * The fourth thing it does not own is a **rate over all sessions**, and the
20
30
  * reason is structural rather than an omission. A record is written only when a
@@ -335,6 +345,318 @@ export function readInstallBoundary(): InstallBoundary {
335
345
  }
336
346
  }
337
347
 
348
+ // ---------------------------------------------------------------------------
349
+ // The shadow read — road-to-a-stop-that-holds step 2.1
350
+ // ---------------------------------------------------------------------------
351
+
352
+ /**
353
+ * One observation of "this retry would still have been refused".
354
+ *
355
+ * `turn-end-detector-demotion.md` § Q1 asks what share of the turns the gate
356
+ * allows on a re-entrancy layer would have been refused again. Both layers
357
+ * return `EXIT_ALLOW` unconditionally, so that share is not derivable from the
358
+ * refusal record: a retry leaves no trace at all, and a detector that fired
359
+ * twice is indistinguishable from one that was satisfied on the second pass.
360
+ * This row is that trace.
361
+ *
362
+ * `layer` is which allow path produced it, because the two are not the same
363
+ * measurement. `stop_hook_active` is the HOST's answer and is set on any stop
364
+ * hook's block, including another concern's; `refused_turn` is this gate's own
365
+ * marker and therefore always follows a refusal by this gate. Pooling them
366
+ * would let another concern's retries inflate Q1's numerator against a
367
+ * denominator that only counts this gate's refusals.
368
+ */
369
+ /**
370
+ * Who set the `stop_hook_active` flag this row was recorded under.
371
+ *
372
+ * `layer` already says WHICH allow path produced the row, and the doc above
373
+ * says the host sets `stop_hook_active` on ANY stop hook's block — including a
374
+ * neighbour's. What it could not say is which: a `stop_hook_active` row and a
375
+ * `refused_turn` row were the only two things the record distinguished, so a
376
+ * retry caused by a foreign concern and a retry caused by this gate were
377
+ * indistinguishable once the layer was `stop_hook_active`.
378
+ *
379
+ * - `ours` — this session's refusal record exists and parses, so this gate
380
+ * refused at some point before this stop.
381
+ * - `foreign` — the record is ABSENT. This gate never refused in this session,
382
+ * and the host set the flag anyway, so something else did.
383
+ * - `unknown` — the record exists and could not be read. The distinction from
384
+ * `foreign` is the whole point: "we did not refuse" and "we
385
+ * cannot tell whether we refused" are different findings, and
386
+ * collapsing them would let an unreadable state directory read
387
+ * as evidence of a neighbour.
388
+ *
389
+ * On the `refused_turn` layer the answer is `ours` by construction — that
390
+ * marker is this gate's own and no other writer sets it.
391
+ */
392
+ export type ShadowSetBy = 'ours' | 'foreign' | 'unknown';
393
+
394
+ export interface WouldRefuseAgainRow {
395
+ detector: RefusalDetectorId;
396
+ /** The turn ordinal the shadow ran on — joins to `refused_turn`. */
397
+ turn: number;
398
+ at: string;
399
+ layer: ShadowLayer;
400
+ /**
401
+ * Absent on rows written before this field existed, which parse as
402
+ * `unknown` rather than being dropped — an old row is still a real
403
+ * observation of a retry, and discarding it would silently lower Q1's
404
+ * numerator.
405
+ */
406
+ set_by: ShadowSetBy;
407
+ }
408
+
409
+ /**
410
+ * The two allow paths a shadow read can run on, in the order `main()` reaches
411
+ * them.
412
+ *
413
+ * A LIST rather than a bare union, and the counter below is derived from it,
414
+ * for the reason `emptyCounts` gives one screen up: a hand-written counter
415
+ * object satisfied `Record<Union, number>` on the day it was typed and read
416
+ * back `undefined` the moment a member was added, so every `+=` over it
417
+ * produced `NaN` and the whole rollup went quiet. Adding a third layer here
418
+ * cannot repeat that.
419
+ */
420
+ export const SHADOW_LAYERS = ['stop_hook_active', 'refused_turn'] as const;
421
+
422
+ export type ShadowLayer = (typeof SHADOW_LAYERS)[number];
423
+
424
+ /** A zeroed retry counter per layer, DERIVED from `SHADOW_LAYERS`. */
425
+ export function emptyRetryCounts(): Record<ShadowLayer, number> {
426
+ const out = {} as Record<ShadowLayer, number>;
427
+ for (const l of SHADOW_LAYERS) out[l] = 0;
428
+ return out;
429
+ }
430
+
431
+ /**
432
+ * Per-session shadow state, in its own file beside the refusal record.
433
+ *
434
+ * SEPARATE FILE, deliberately, and this is the one design decision in the step
435
+ * worth defending. The plan says "into the session state" and the refusal
436
+ * record is the obvious place — but `refused_turn` in that record is the
437
+ * re-entrancy wedge guard, `parseRecord` rejects any record lacking it, and a
438
+ * Layer-1 retry can occur with no refusal by this gate at all (another stop
439
+ * concern blocked). Writing a shadow row there would mean either synthesising
440
+ * a `refused_turn` this gate never wrote — corrupting the one field a wedge
441
+ * depends on — or loosening the parser that protects it. A sibling file costs
442
+ * one more path and touches neither.
443
+ *
444
+ * It lives in this module rather than in the gate for the reason the header
445
+ * already gives about the pruner: a directory with two writers each holding
446
+ * their own idea of where the files are is how a reader and a writer end up
447
+ * disagreeing.
448
+ *
449
+ * Two limits bound what a reading of this record may claim.
450
+ *
451
+ * **It counts retries with a READABLE TRANSCRIPT, not all retries.** A retry
452
+ * whose transcript is absent, over `TRANSCRIPT_READ_MAX_BYTES`, or carries no
453
+ * assistant text records nothing at all — the gate's input assembly returns
454
+ * null and there is no detector verdict to shadow. The numerator is unaffected
455
+ * and the denominator shrinks, so the bias is UPWARD: a Q1 read off this
456
+ * record is an upper bound, never a point estimate.
457
+ *
458
+ * **On a host that sends no `session_id` it pools across sessions.** The key
459
+ * is `deriveSessionKey(rawSessionId || 'unknown-session')`, so every such
460
+ * session shares one file and one counter. The gate documents the same
461
+ * collision for the re-entrancy marker and argues it degrades safely, toward
462
+ * under-refusing. That argument does NOT transfer here: a merged measurement
463
+ * does not under-report, it reports one number for several sessions and a
464
+ * reader takes it for one. Named rather than inherited, because the two
465
+ * failures are opposite in kind.
466
+ */
467
+ export interface ShadowRecord {
468
+ /** Newest last. Bounded by `SHADOW_MAX_ROWS`. */
469
+ would_refuse_again: WouldRefuseAgainRow[];
470
+ /**
471
+ * Retries observed on an allow path in this session, refusing or NOT,
472
+ * SPLIT BY LAYER.
473
+ *
474
+ * Kept because a shadow row array alone cannot distinguish "no retry
475
+ * happened" from "every retry came back clean", and those are opposite
476
+ * readings of the same empty list.
477
+ *
478
+ * Split because pooling it made a per-layer Q1 uncomputable, which the
479
+ * first version shipped and an independent review caught before any row
480
+ * accumulated. `layer` is on every ROW for a stated reason — a
481
+ * `stop_hook_active` retry follows ANY stop concern's block, not only this
482
+ * gate's — so a pooled numerator would read another concern's retries
483
+ * against this gate's refusals. A pooled DENOMINATOR has the same defect
484
+ * one level down, and worse: a CLEAN retry adds no row at all, so it left
485
+ * no layer trace anywhere. The numerator could be split and the
486
+ * denominator could not, which is not a sanity check on anything.
487
+ *
488
+ * Keyed rather than two fields so a third layer, if one is ever added,
489
+ * cannot silently land in neither bucket.
490
+ */
491
+ retries_observed: Record<ShadowLayer, number>;
492
+ /** Rows dropped to the cap. A non-zero value makes the array a sample. */
493
+ dropped: number;
494
+ first_at: string;
495
+ last_at: string;
496
+ }
497
+
498
+ /**
499
+ * Row cap per session.
500
+ *
501
+ * A stated bound, not a measured optimum. A row is ~90 bytes and a session
502
+ * with 200 retries is already pathological, so the cap exists to keep an
503
+ * unbounded write off the Stop path rather than to fit a distribution. Oldest
504
+ * rows are dropped first: Q1 is a rate, and the most recent observations are
505
+ * the ones a reader is deciding on. A non-zero `dropped` on a real session is
506
+ * the signal that the number was chosen too low.
507
+ */
508
+ export const SHADOW_MAX_ROWS = 200;
509
+
510
+ /**
511
+ * The shadow file's suffix, named rather than inlined because TWO readers need
512
+ * to agree on it: the writer below and `pruneAgedRefusalState`, which scans the
513
+ * same directory for `*.json` and would otherwise keep every shadow record
514
+ * forever.
515
+ */
516
+ export const SHADOW_SUFFIX = '.shadow.json';
517
+
518
+ export function sessionShadowFile(workspaceRoot: string, sessionKey: string): string {
519
+ return path.join(refusalStateDir(workspaceRoot), `${sessionKey}${SHADOW_SUFFIX}`);
520
+ }
521
+
522
+ /**
523
+ * Parse one shadow record. `null` for anything unreadable — same fail-open
524
+ * contract as `parseRecord`, and for the same reason: this is the Stop path.
525
+ */
526
+ export function parseShadowRecord(raw: string): ShadowRecord | null {
527
+ let decoded: unknown;
528
+ try {
529
+ decoded = JSON.parse(raw);
530
+ } catch {
531
+ return null;
532
+ }
533
+ if (typeof decoded !== 'object' || decoded === null || Array.isArray(decoded)) return null;
534
+ const o = decoded as Record<string, unknown>;
535
+ const rowsRaw = o['would_refuse_again'];
536
+ if (!Array.isArray(rowsRaw)) return null;
537
+ const rows: WouldRefuseAgainRow[] = [];
538
+ for (const r of rowsRaw) {
539
+ if (typeof r !== 'object' || r === null || Array.isArray(r)) continue;
540
+ const ro = r as Record<string, unknown>;
541
+ const det = ro['detector'];
542
+ const turn = ro['turn'];
543
+ const at = ro['at'];
544
+ const layer = ro['layer'];
545
+ if (!isDetector(det)) continue;
546
+ if (typeof turn !== 'number' || !Number.isFinite(turn)) continue;
547
+ if (typeof at !== 'string') continue;
548
+ if (layer !== 'stop_hook_active' && layer !== 'refused_turn') continue;
549
+ const setByRaw = ro['set_by'];
550
+ const set_by: ShadowSetBy =
551
+ setByRaw === 'ours' || setByRaw === 'foreign' || setByRaw === 'unknown'
552
+ ? setByRaw
553
+ : 'unknown';
554
+ rows.push({ detector: det, turn, at, layer, set_by });
555
+ }
556
+ const num = (v: unknown): number =>
557
+ typeof v === 'number' && Number.isFinite(v) && v >= 0 ? v : 0;
558
+ const observed = o['retries_observed'];
559
+ const byLayer = emptyRetryCounts();
560
+ if (typeof observed === 'object' && observed !== null && !Array.isArray(observed)) {
561
+ for (const l of SHADOW_LAYERS) {
562
+ byLayer[l] = num((observed as Record<string, unknown>)[l]);
563
+ }
564
+ }
565
+ return {
566
+ would_refuse_again: rows,
567
+ retries_observed: byLayer,
568
+ dropped: num(o['dropped']),
569
+ first_at: typeof o['first_at'] === 'string' ? o['first_at'] : '',
570
+ last_at: typeof o['last_at'] === 'string' ? o['last_at'] : '',
571
+ };
572
+ }
573
+
574
+ /**
575
+ * Fold one retry observation into a shadow record. Pure — the caller writes.
576
+ *
577
+ * `detectors` EMPTY is a real and load-bearing input: a retry whose detectors
578
+ * all came back silent raises `retries_observed` for its layer and adds no
579
+ * row, which is the only way Q1 can ever read below 1.
580
+ */
581
+ export function foldShadow(
582
+ prev: ShadowRecord | null,
583
+ input: {
584
+ detectors: readonly RefusalDetectorId[];
585
+ turnOrdinal: number;
586
+ at: string;
587
+ layer: ShadowLayer;
588
+ /**
589
+ * Omitted by a caller that cannot establish it, which is `unknown` —
590
+ * never `foreign`, because a missing answer is not evidence of a
591
+ * neighbour.
592
+ */
593
+ setBy?: ShadowSetBy;
594
+ },
595
+ ): ShadowRecord {
596
+ const rows = [...(prev?.would_refuse_again ?? [])];
597
+ const set_by: ShadowSetBy = input.setBy ?? 'unknown';
598
+ for (const d of input.detectors) {
599
+ rows.push({
600
+ detector: d,
601
+ turn: input.turnOrdinal,
602
+ at: input.at,
603
+ layer: input.layer,
604
+ set_by,
605
+ });
606
+ }
607
+ let dropped = prev?.dropped ?? 0;
608
+ if (rows.length > SHADOW_MAX_ROWS) {
609
+ dropped += rows.length - SHADOW_MAX_ROWS;
610
+ rows.splice(0, rows.length - SHADOW_MAX_ROWS);
611
+ }
612
+ const retries = emptyRetryCounts();
613
+ for (const l of SHADOW_LAYERS) retries[l] = prev?.retries_observed[l] ?? 0;
614
+ retries[input.layer] += 1;
615
+ return {
616
+ would_refuse_again: rows,
617
+ retries_observed: retries,
618
+ dropped,
619
+ first_at: prev?.first_at !== undefined && prev.first_at !== '' ? prev.first_at : input.at,
620
+ last_at: input.at,
621
+ };
622
+ }
623
+
624
+ /**
625
+ * Who set `stop_hook_active`, decided from this gate's OWN refusal record.
626
+ *
627
+ * Three states, and the third is not a hedge. `ENOENT` means this gate never
628
+ * refused in this session, so a flag the host set anyway came from somewhere
629
+ * else — `foreign`. Any OTHER read failure means the answer is unavailable, and
630
+ * reporting that as `foreign` would turn a broken state directory into evidence
631
+ * that a neighbour acted. The lane's own framing is the reason: what this suite
632
+ * can say about a neighbour is only what it observes, and an unreadable file is
633
+ * not an observation.
634
+ *
635
+ * `parseRecord` returning `null` on a present file is also `unknown`: the file
636
+ * exists, so something wrote it, and we cannot say what.
637
+ */
638
+ export function shadowSetBy(workspaceRoot: string, sessionKey: string): ShadowSetBy {
639
+ let raw: string;
640
+ try {
641
+ raw = fs.readFileSync(sessionRefusalFile(workspaceRoot, sessionKey), 'utf-8');
642
+ } catch (err) {
643
+ const code = (err as NodeJS.ErrnoException | undefined)?.code;
644
+ return code === 'ENOENT' ? 'foreign' : 'unknown';
645
+ }
646
+ return parseRecord(raw) === null ? 'unknown' : 'ours';
647
+ }
648
+
649
+ /** This session's shadow record, or `null` when it has never retried. */
650
+ export function readShadowRecord(workspaceRoot: string, sessionKey: string): ShadowRecord | null {
651
+ try {
652
+ return parseShadowRecord(
653
+ fs.readFileSync(sessionShadowFile(workspaceRoot, sessionKey), 'utf-8'),
654
+ );
655
+ } catch {
656
+ return null;
657
+ }
658
+ }
659
+
338
660
  // ---------------------------------------------------------------------------
339
661
  // TTL — step 1.2
340
662
  // ---------------------------------------------------------------------------
@@ -350,8 +672,9 @@ export function readInstallBoundary(): InstallBoundary {
350
672
  * LIVENESS and this bounds EVIDENCE — pruning evidence on a liveness clock would
351
673
  * delete the corpus this roadmap exists to read.
352
674
  *
353
- * *Revisit-if:* a rollup window longer than 90 days is needed to read a rate, or
354
- * the directory's file count becomes a measured cost rather than a projected one.
675
+ * Two things would show 90 is the wrong number: a rollup window longer than it
676
+ * being needed to read a rate, or the directory's file count becoming a
677
+ * measured cost rather than a projected one.
355
678
  */
356
679
  export const REFUSAL_STATE_MAX_AGE_DAYS = 90;
357
680
 
@@ -389,6 +712,53 @@ export function pruneAgedRefusalState(
389
712
  if (!name.endsWith('.json')) continue;
390
713
  result.scanned += 1;
391
714
  const file = path.join(dir, name);
715
+ // A shadow record is a sibling in the same directory whose name also
716
+ // ends `.json`, and `parseRecord` rejects it for lacking `refused_at`.
717
+ // Left to the branch below it would land in `kept` FOREVER — the
718
+ // unbounded growth this pruner exists to stop, reintroduced by the
719
+ // instrument step 2.1 added. Its own clock is `last_at`.
720
+ if (name.endsWith(SHADOW_SUFFIX)) {
721
+ let shadow: ShadowRecord | null = null;
722
+ try {
723
+ shadow = parseShadowRecord(fs.readFileSync(file, 'utf-8'));
724
+ } catch {
725
+ shadow = null;
726
+ }
727
+ const shadowAt = shadow === null ? NaN : Date.parse(shadow.last_at);
728
+ // KEEP-ON-UNPARSEABLE IS NOT INHERITED, and the asymmetry with the
729
+ // branch below is the point. A refusal record that will not parse
730
+ // may still carry the wedge marker, so deleting it is the one
731
+ // irreversible move available and it is refused. A shadow record
732
+ // is pure measurement with nothing to protect — keeping one whose
733
+ // `last_at` cannot be read means it never ages out at all, which
734
+ // is the unbounded growth this pruner exists to stop, surviving
735
+ // for exactly the subset nobody can read. An unreadable one is
736
+ // pruned on the filesystem mtime, which is the weaker clock this
737
+ // function refuses for refusal records precisely because they are
738
+ // evidence; for a corrupt measurement it is the only clock left.
739
+ if (!Number.isFinite(shadowAt)) {
740
+ let mtime = NaN;
741
+ try {
742
+ mtime = fs.statSync(file).mtimeMs;
743
+ } catch {
744
+ mtime = NaN;
745
+ }
746
+ if (!Number.isFinite(mtime) || mtime >= cutoffMs) {
747
+ result.kept += 1;
748
+ continue;
749
+ }
750
+ } else if (shadowAt >= cutoffMs) {
751
+ result.kept += 1;
752
+ continue;
753
+ }
754
+ try {
755
+ fs.unlinkSync(file);
756
+ result.pruned += 1;
757
+ } catch {
758
+ result.kept += 1;
759
+ }
760
+ continue;
761
+ }
392
762
  let rec: RefusalRecord | null = null;
393
763
  try {
394
764
  rec = parseRecord(fs.readFileSync(file, 'utf-8'));
@@ -492,6 +862,12 @@ export function collectRefusalStats(workspaceRoot: string): RefusalStats {
492
862
  const versions = new Map<string, VersionBucket>();
493
863
  for (const name of entries) {
494
864
  if (!name.endsWith('.json')) continue;
865
+ // Shadow records share the directory and the extension. `parseRecord`
866
+ // would reject each one anyway, so this line changes no number — it is
867
+ // here because relying on another function's strictness for a
868
+ // correctness property is how the property disappears when that
869
+ // function is loosened.
870
+ if (name.endsWith(SHADOW_SUFFIX)) continue;
495
871
  let rec: RefusalRecord | null = null;
496
872
  try {
497
873
  rec = parseRecord(fs.readFileSync(path.join(dir, name), 'utf-8'));
@@ -535,3 +911,223 @@ export function collectRefusalStats(workspaceRoot: string): RefusalStats {
535
911
  stats.byVersion = [...versions.values()].sort((a, b) => (a.version < b.version ? 1 : -1));
536
912
  return stats;
537
913
  }
914
+
915
+ // ---------------------------------------------------------------------------
916
+ // Q1 — the shadow rollup (step 2.2)
917
+ // ---------------------------------------------------------------------------
918
+
919
+ /**
920
+ * One layer's Q1 inputs, kept as counts so the ratio is computed where it is
921
+ * read rather than stored.
922
+ *
923
+ * SPLIT BY LAYER BEFORE DETECTOR, and that order is the shape's whole point
924
+ * rather than a presentation choice. A `stop_hook_active` retry follows ANY
925
+ * stop concern's block — not only this gate's — so pooling the two layers
926
+ * divides another concern's retries into this gate's refusals and calls the
927
+ * result a re-refusal share. `ShadowRecord.retries_observed` is keyed the same
928
+ * way so the denominator splits with the numerator; an independent review
929
+ * found the first version pooling the denominator, which left the per-layer
930
+ * ratio uncomputable even though every row already carried its `layer`.
931
+ */
932
+ export interface ShadowLayerStats {
933
+ layer: ShadowLayer;
934
+ /**
935
+ * Retries observed on this layer across every readable record — the
936
+ * denominator, counting retries that came back CLEAN as well as refusing
937
+ * ones. A clean retry writes no row, so without this a zero numerator and
938
+ * "no retry happened" are the same empty list.
939
+ */
940
+ retries: number;
941
+ /** Shadow rows on this layer, by detector — the numerators. */
942
+ byDetector: DetectorCounts;
943
+ /**
944
+ * Rows on this layer, summed.
945
+ *
946
+ * MAY EXCEED `retries`, legitimately: one retry that would have been
947
+ * refused by two detectors writes two rows, and the demotion contract's
948
+ * attribution rule counts it once for each. A per-detector ratio stays
949
+ * within [0, 1]; this total is not a share and is printed as a count.
950
+ */
951
+ rows: number;
952
+ }
953
+
954
+ /**
955
+ * Rollup over every session's shadow record under a workspace.
956
+ *
957
+ * Two bounds travel with every number this carries. Both are properties of the
958
+ * instrument rather than of a particular reading, both are stated on
959
+ * `ShadowRecord`, and both are repeated here because a consumer reads this
960
+ * type and not that comment:
961
+ *
962
+ * - A retry whose transcript is unreadable or oversized records NOTHING —
963
+ * no row and no retry. The denominator shrinks with the numerator, so a
964
+ * Q1 read off this is an UPPER bound rather than a point estimate.
965
+ * - On a host that sends no `session_id`, every session shares one record
966
+ * and one counter. `files` below then counts FILES and not sessions, and
967
+ * a reader taking one for the other over-states the sample.
968
+ */
969
+ export interface ShadowStats {
970
+ /**
971
+ * Shadow FILES parsed. Deliberately not named `sessions`: one file is one
972
+ * session only on a host that sends a `session_id`.
973
+ */
974
+ files: number;
975
+ /** Files present but unparseable. A non-zero value makes every rate a floor. */
976
+ unreadable: number;
977
+ /**
978
+ * Rows dropped to `SHADOW_MAX_ROWS`, summed. Non-zero means the row arrays
979
+ * are samples of their own sessions and every numerator is a floor.
980
+ */
981
+ dropped: number;
982
+ byLayer: ShadowLayerStats[];
983
+ earliest: string | null;
984
+ latest: string | null;
985
+ }
986
+
987
+ /** A zeroed rollup, DERIVED from `SHADOW_LAYERS` for the reason `emptyCounts` gives. */
988
+ export function emptyShadowStats(): ShadowStats {
989
+ return {
990
+ files: 0,
991
+ unreadable: 0,
992
+ dropped: 0,
993
+ byLayer: SHADOW_LAYERS.map((layer) => ({
994
+ layer,
995
+ retries: 0,
996
+ byDetector: emptyCounts(),
997
+ rows: 0,
998
+ })),
999
+ earliest: null,
1000
+ latest: null,
1001
+ };
1002
+ }
1003
+
1004
+ /**
1005
+ * Aggregate every shadow record under a workspace.
1006
+ *
1007
+ * Reads through `parseShadowRecord`, never by hand: the gate writes these
1008
+ * files, and a reader carrying its own idea of their shape is how a producer
1009
+ * and a consumer drift apart — the exact seam the 2026-09-30 review round
1010
+ * found uncrossed between the verification recorder and its classifier.
1011
+ */
1012
+ export function collectShadowStats(workspaceRoot: string): ShadowStats {
1013
+ const dir = refusalStateDir(workspaceRoot);
1014
+ const stats = emptyShadowStats();
1015
+ const byLayer = new Map(stats.byLayer.map((b) => [b.layer, b]));
1016
+ let entries: string[];
1017
+ try {
1018
+ entries = fs.readdirSync(dir);
1019
+ } catch {
1020
+ return stats;
1021
+ }
1022
+ for (const name of entries) {
1023
+ if (!name.endsWith(SHADOW_SUFFIX)) continue;
1024
+ let rec: ShadowRecord | null = null;
1025
+ try {
1026
+ rec = parseShadowRecord(fs.readFileSync(path.join(dir, name), 'utf-8'));
1027
+ } catch {
1028
+ rec = null;
1029
+ }
1030
+ if (rec === null) {
1031
+ stats.unreadable += 1;
1032
+ continue;
1033
+ }
1034
+ stats.files += 1;
1035
+ stats.dropped += rec.dropped;
1036
+ for (const layer of SHADOW_LAYERS) {
1037
+ const bucket = byLayer.get(layer);
1038
+ if (bucket !== undefined) bucket.retries += rec.retries_observed[layer] ?? 0;
1039
+ }
1040
+ for (const row of rec.would_refuse_again) {
1041
+ const bucket = byLayer.get(row.layer);
1042
+ if (bucket === undefined) continue;
1043
+ bucket.byDetector[row.detector] += 1;
1044
+ bucket.rows += 1;
1045
+ }
1046
+ // EMPTY STRINGS ARE SKIPPED, NOT COMPARED. `parseShadowRecord` coerces a
1047
+ // missing `first_at` to `''`, and `'' < <any ISO stamp>` is true, so a
1048
+ // record lacking the field would win the earliest comparison and the
1049
+ // report would print an empty window start beside a real end. An absent
1050
+ // timestamp is not an earlier one.
1051
+ if (rec.first_at !== '' && (stats.earliest === null || rec.first_at < stats.earliest)) {
1052
+ stats.earliest = rec.first_at;
1053
+ }
1054
+ if (rec.last_at !== '' && (stats.latest === null || rec.last_at > stats.latest)) {
1055
+ stats.latest = rec.last_at;
1056
+ }
1057
+ }
1058
+ return stats;
1059
+ }
1060
+
1061
+ /**
1062
+ * Detectors whose share may NOT be reported off a shadow rollup, because the
1063
+ * record cannot tell a dispatch-suppressed retry from a clean one.
1064
+ *
1065
+ * `runDetectors` in `turn_end_gate_hook.ts` skips A, D and F when a subagent
1066
+ * dispatch is open — they did not run, so the turn is not an observation of
1067
+ * them staying silent. `recordShadow` then increments `retries_observed`
1068
+ * unconditionally and writes no dispatch flag, so every dispatch-suppressed
1069
+ * retry lands in those three denominators as a "would not have refused" that
1070
+ * nobody observed. In a suite whose delegation policy dispatches by default
1071
+ * that is not a rare branch.
1072
+ *
1073
+ * The demotion contract already forbids publishing it: *"A rollup that cannot
1074
+ * tell a dispatch-suppressed turn from a clean one may not report Q2 for A or
1075
+ * D at all."* This set is that clause applied to the shadow rollup, extended to
1076
+ * F — the contract's attribution paragraph names only A and D because it
1077
+ * predates F, while its own § The six detectors table and the gate both gate
1078
+ * all three. The wider set is the safe reading of a censoring rule.
1079
+ *
1080
+ * Closing this needs a `dispatch_open` flag on the shadow row, which is a
1081
+ * PRODUCER change. Until then these three are censored rather than estimated.
1082
+ */
1083
+ export const DISPATCH_CENSORED_DETECTORS: ReadonlySet<RefusalDetectorId> =
1084
+ new Set<RefusalDetectorId>(['promissory', 'completion', 'untested']);
1085
+
1086
+ /**
1087
+ * The **retry-conditioned re-refusal share** for one detector on one layer, or
1088
+ * `null` when the layer observed no retry.
1089
+ *
1090
+ * ```
1091
+ * THIS IS NOT THE Q1 `turn-end-detector-demotion.md` REGISTERS BARS AGAINST.
1092
+ * NEVER COMPARE IT TO A BAR IN THAT TABLE.
1093
+ * ```
1094
+ *
1095
+ * The contract defines Q1 as *"of **a detector's** eligible initial refusals,
1096
+ * the share whose immediate retry is refused again by the same detector"*. Both
1097
+ * halves of that are conditioned on the detector. This function conditions
1098
+ * NEITHER: the denominator is every retry observed on the layer, whatever
1099
+ * refusal produced it, and the numerator is every row where this detector would
1100
+ * have fired, whatever refusal produced the retry.
1101
+ *
1102
+ * **They disagree in direction, so this is not a bound either.** Take ten
1103
+ * retries on a layer, nine following `verification` refusals and one following
1104
+ * a `language` refusal, with one `language` row. The contract's Q1 for B is
1105
+ * 1/1 = 100 % — bar crossed, a demotion study authorised. This function returns
1106
+ * 1/10 = 10 % — bar not crossed. Opposite verdicts on identical evidence. The
1107
+ * numerator can also run the other way, since a `language` row on a retry that
1108
+ * followed a `verification` refusal counts here and does not count there.
1109
+ *
1110
+ * **Why it is shaped this way, which is a producer limit rather than a choice.**
1111
+ * `ShadowRecord` carries no originating detector: `retries_observed` is keyed by
1112
+ * layer alone and a row records only what WOULD fire, never what did. The
1113
+ * session's `RefusalRecord` holds per-detector COUNTS for the whole session and
1114
+ * `refused_turn` for the last refused turn, so the per-turn origin needed to
1115
+ * condition this denominator is not on disk anywhere. Computing the registered
1116
+ * Q1 needs the producer to record it; that is named as the open residue on
1117
+ * `road-to-a-stop-that-holds` step 2.2 rather than papered over here.
1118
+ *
1119
+ * `null` RATHER THAN ZERO on an empty denominator, and that distinction is why
1120
+ * `retries_observed` exists at all: zero means "retries happened and this
1121
+ * detector would not have refused any of them", a finding about the detector,
1122
+ * while null means "nothing was observed", a finding about the sample.
1123
+ * Collapsing them publishes the second as the first.
1124
+ */
1125
+ export function retryConditionedShare(
1126
+ stats: ShadowStats,
1127
+ layer: ShadowLayer,
1128
+ detector: RefusalDetectorId,
1129
+ ): number | null {
1130
+ const bucket = stats.byLayer.find((b) => b.layer === layer);
1131
+ if (bucket === undefined || bucket.retries === 0) return null;
1132
+ return bucket.byDetector[detector] / bucket.retries;
1133
+ }