@event4u/agent-config 16.0.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 (382) 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 +289 -140
  5. package/CONTRIBUTING.md +9 -0
  6. package/README.md +2 -2
  7. package/dist/agent-src/commands/analyze/inbox.md +26 -0
  8. package/dist/agent-src/commands/analyze/roadmap-repos.md +27 -0
  9. package/dist/agent-src/commands/challenge-me/closure.md +178 -0
  10. package/dist/agent-src/commands/challenge-me.md +9 -2
  11. package/dist/agent-src/commands/council/default.md +25 -5
  12. package/dist/agent-src/commands/feature/plan.md +28 -1
  13. package/dist/agent-src/commands/feature/roadmap.md +28 -1
  14. package/dist/agent-src/commands/implement-ticket.md +27 -0
  15. package/dist/agent-src/commands/jira-ticket.md +27 -0
  16. package/dist/agent-src/commands/pr/create.md +29 -0
  17. package/dist/agent-src/commands/pr/merge.md +60 -9
  18. package/dist/agent-src/commands/roadmap/ai-council.md +7 -1
  19. package/dist/agent-src/commands/roadmap/create.md +74 -27
  20. package/dist/agent-src/commands/roadmap/materialize.md +27 -0
  21. package/dist/agent-src/commands/roadmap/process-full.md +123 -18
  22. package/dist/agent-src/commands/team.md +22 -0
  23. package/dist/agent-src/commands/work.md +4 -0
  24. package/dist/agent-src/contexts/authority/scope-growth-ownership.md +80 -0
  25. package/dist/agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +47 -0
  26. package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +44 -3
  27. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +19 -23
  28. package/dist/agent-src/contexts/execution/decision-closure-execution.md +93 -0
  29. package/dist/agent-src/contexts/execution/fix-loop-ladder.md +108 -0
  30. package/dist/agent-src/contexts/execution/plan-confidence-gate.md +4 -2
  31. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +75 -6
  32. package/dist/agent-src/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
  33. package/dist/agent-src/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
  34. package/dist/agent-src/guidelines/design-fidelity-routing.md +29 -0
  35. package/dist/agent-src/rules/autonomous-execution.md +15 -8
  36. package/dist/agent-src/rules/delegation-policy.md +23 -45
  37. package/dist/agent-src/rules/design-fidelity.md +32 -37
  38. package/dist/agent-src/rules/evaluator-independence.md +43 -49
  39. package/dist/agent-src/rules/test-first.md +41 -0
  40. package/dist/agent-src/rules/ui-audit-gate.md +1 -2
  41. package/dist/agent-src/skills/accessibility-auditor/data/aria-patterns.csv +1 -1
  42. package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +3 -3
  43. package/dist/agent-src/skills/ai-council/SKILL.md +11 -3
  44. package/dist/agent-src/skills/api-design/data/manifest.json +3 -3
  45. package/dist/agent-src/skills/database/data/manifest.json +4 -4
  46. package/dist/agent-src/skills/database/data/query-tuning.csv +3 -3
  47. package/dist/agent-src/skills/design-review/SKILL.md +80 -4
  48. package/dist/agent-src/skills/design-review/references/verification-automation.md +32 -0
  49. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +30 -5
  50. package/dist/agent-src/skills/existing-ui-audit/references/component-taxonomy-detection.md +56 -0
  51. package/dist/agent-src/skills/experiment-loop/SKILL.md +31 -1
  52. package/dist/agent-src/skills/markitdown/SKILL.md +4 -0
  53. package/dist/agent-src/skills/playwright-testing/SKILL.md +30 -6
  54. package/dist/agent-src/skills/roadmap-writing/SKILL.md +1 -1
  55. package/dist/agent-src/skills/storybook-workshop/SKILL.md +42 -3
  56. package/dist/agent-src/skills/threat-modeling/SKILL.md +9 -4
  57. package/dist/agent-src/skills/threat-modeling/data/manifest.json +4 -4
  58. package/dist/agent-src/skills/ui-component-architect/SKILL.md +23 -0
  59. package/dist/agent-src/skills/verify-repair-loop/SKILL.md +37 -3
  60. package/dist/agent-src/templates/agent-settings.md +1 -1
  61. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  62. package/dist/agent-src/templates/roadmaps.md +218 -0
  63. package/dist/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
  64. package/dist/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
  65. package/dist/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
  66. package/dist/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
  67. package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +260 -21
  68. package/dist/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
  69. package/dist/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
  70. package/dist/agent-src/templates/scripts/work_engine/state.ts +57 -7
  71. package/dist/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
  72. package/dist/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
  73. package/dist/cli/python/knowledge_ingest.js +1 -1
  74. package/dist/cli/python/workspace_drive.js +1 -1
  75. package/dist/cli-delegate/check_update_banner.js +40 -2
  76. package/dist/cli-delegate/chunk-4E2W63DA.js +335 -0
  77. package/dist/cli-delegate/{chunk-S3VFXMTM.js → chunk-4XFP2Y3E.js} +1 -1
  78. package/dist/cli-delegate/chunk-5R7HOLAF.js +99 -0
  79. package/dist/cli-delegate/{chunk-IVBPRCQO.js → chunk-6KFEC3G7.js} +1 -1
  80. package/dist/cli-delegate/{chunk-2TAQ2K4V.js → chunk-6KM6ORRY.js} +55 -386
  81. package/dist/cli-delegate/{chunk-AK6HODNQ.js → chunk-7XGBX6ZH.js} +973 -555
  82. package/dist/cli-delegate/{chunk-2XY7ILBT.js → chunk-FC75PGJE.js} +42 -2
  83. package/dist/cli-delegate/{chunk-55ACLDUE.js → chunk-FP36SWJD.js} +587 -141
  84. package/dist/cli-delegate/{chunk-3FKR64R5.js → chunk-IGD6ERX6.js} +62 -7
  85. package/dist/cli-delegate/{chunk-GYC3VRYN.js → chunk-JNMVLWLR.js} +99 -14
  86. package/dist/cli-delegate/{chunk-H32DGUJH.js → chunk-P6ZHP5W2.js} +1 -1
  87. package/dist/cli-delegate/{chunk-57PN4GYJ.js → chunk-RGNIWTEA.js} +1 -1
  88. package/dist/cli-delegate/chunk-ROYYTEZU.js +39 -0
  89. package/dist/cli-delegate/{chunk-2DK2VJWG.js → chunk-WGXTVKCQ.js} +20 -6
  90. package/dist/cli-delegate/chunk-YDPILCOL.js +152 -0
  91. package/dist/cli-delegate/{chunk-QJWAUMJW.js → chunk-YJ72UIAK.js} +730 -15
  92. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  93. package/dist/cli-delegate/cmd_brand_status.js +1 -1
  94. package/dist/cli-delegate/cmd_conformance.js +985 -831
  95. package/dist/cli-delegate/cmd_doctor.js +7 -5
  96. package/dist/cli-delegate/cmd_explain.js +2 -2
  97. package/dist/cli-delegate/cmd_export.js +9 -7
  98. package/dist/cli-delegate/cmd_fleet.js +4 -2
  99. package/dist/cli-delegate/cmd_handoff.js +7 -7
  100. package/dist/cli-delegate/cmd_mcp_available.js +1 -1
  101. package/dist/cli-delegate/cmd_memory_get.js +1 -1
  102. package/dist/cli-delegate/cmd_migrate.js +2 -2
  103. package/dist/cli-delegate/cmd_packs_active.js +2 -2
  104. package/dist/cli-delegate/cmd_preflight.js +4 -2
  105. package/dist/cli-delegate/cmd_prune.js +10 -8
  106. package/dist/cli-delegate/cmd_route_audit.js +3 -3
  107. package/dist/cli-delegate/cmd_route_explain.js +1 -1
  108. package/dist/cli-delegate/cmd_session_recycle.js +4 -4
  109. package/dist/cli-delegate/cmd_settings_get.js +28 -2
  110. package/dist/cli-delegate/cmd_settings_migrate.js +9 -7
  111. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  112. package/dist/cli-delegate/cmd_sync.js +10 -8
  113. package/dist/cli-delegate/cmd_uninstall.js +16 -13
  114. package/dist/cli-delegate/cmd_update.js +1 -1
  115. package/dist/cli-delegate/cmd_upgrade.js +1 -1
  116. package/dist/cli-delegate/cmd_validate.js +12 -10
  117. package/dist/cli-delegate/cmd_versions.js +1 -1
  118. package/dist/discovery/deprecation-report.md +1 -1
  119. package/dist/discovery/discovery-manifest.json +90 -43
  120. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  121. package/dist/discovery/discovery-manifest.summary.md +4 -4
  122. package/dist/discovery/dormancy-report.md +1 -1
  123. package/dist/discovery/orphan-report.md +1 -1
  124. package/dist/discovery/packs.json +10 -8
  125. package/dist/discovery/trust-report.md +3 -3
  126. package/dist/discovery/workspaces.json +8 -6
  127. package/dist/hooks/dispatch.js +7682 -2772
  128. package/dist/hooks/dispatch.sha256 +1 -0
  129. package/dist/install/claudeRuleRewrite.js +14 -3
  130. package/dist/install/conflict.js +60 -10
  131. package/dist/install/conflictTracker.js +218 -0
  132. package/dist/install/fsPrimitives.js +54 -0
  133. package/dist/install/install.mjs +2200 -1559
  134. package/dist/install/manifestFiles.js +259 -0
  135. package/dist/install/preflight.js +14 -2
  136. package/dist/install/preserve.js +157 -0
  137. package/dist/install/recordedOwnership.js +132 -0
  138. package/dist/install/txlog.js +11 -2
  139. package/dist/mcp/registry-manifest.json +3 -3
  140. package/dist/mcp/server.json +2 -2
  141. package/dist/mcp/server.mjs +3066 -1470
  142. package/dist/router.json +1 -1
  143. package/dist/scripts/_lib/agent_settings.js +3 -2
  144. package/dist/scripts/_lib/fs_atomic.js +167 -0
  145. package/dist/scripts/_lib/installed_tools.js +460 -0
  146. package/dist/scripts/_lib/lean_projection_mode.js +3 -2
  147. package/dist/scripts/_lib/settings_renamed_keys.js +78 -0
  148. package/dist/server/routes/install.js +8 -0
  149. package/dist/server/routes/wizard.js +9 -3
  150. package/dist/server/schemas/settings.js +40 -5
  151. package/dist/shared/missionExecution.js +97 -0
  152. package/dist/ui/assets/index-Cwq4d0hX.js +23 -0
  153. package/dist/ui/assets/index-Cyjy8bWO.css +1 -0
  154. package/dist/ui/index.html +2 -2
  155. package/docs/MIGRATION.md +103 -0
  156. package/docs/contracts/settings-classes.md +28 -7
  157. package/docs/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
  158. package/docs/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
  159. package/docs/guidelines/design-fidelity-routing.md +29 -0
  160. package/package.json +4 -4
  161. package/src/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
  162. package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
  163. package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
  164. package/src/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
  165. package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +260 -21
  166. package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
  167. package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
  168. package/src/agent-src/templates/scripts/work_engine/state.ts +57 -7
  169. package/src/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
  170. package/src/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
  171. package/src/config/agent-settings.template.yml +168 -27
  172. package/src/config/auto-rule-budget.json +16 -0
  173. package/src/config/ci-local-parity.yml +36 -5
  174. package/src/config/continuity-surface.json +23 -2
  175. package/src/config/gate-coverage.yml +377 -13
  176. package/src/config/gate-violation-baselines.json +10 -6
  177. package/src/config/hook-latency-budget.json +21 -0
  178. package/src/config/hook-token-budget.json +4 -2
  179. package/src/config/loop-surfaces.yaml +215 -0
  180. package/src/config/metric-registry.yml +114 -0
  181. package/src/config/placeholder-drift-budget.json +10 -0
  182. package/src/config/platform-anchor.json +3 -2
  183. package/src/config/release-gate-locality.yml +11 -0
  184. package/src/config/routing-coverage-seed.json +33 -5
  185. package/src/config/rule-enforcement-baseline.json +0 -1
  186. package/src/install/claudeRuleRewrite.ts +16 -2
  187. package/src/install/conflict.ts +65 -9
  188. package/src/install/conflictTracker.ts +299 -0
  189. package/src/install/fsPrimitives.ts +54 -0
  190. package/src/install/manifestFiles.ts +312 -0
  191. package/src/install/preflight.ts +16 -2
  192. package/src/install/preserve.ts +205 -0
  193. package/src/install/recordedOwnership.ts +149 -0
  194. package/src/install/txlog.ts +11 -2
  195. package/src/install/types.ts +19 -4
  196. package/src/scripts/_cli/cmd_conformance.ts +88 -15
  197. package/src/scripts/_cli/cmd_doctor.ts +7 -20
  198. package/src/scripts/_cli/cmd_route_explain.ts +9 -2
  199. package/src/scripts/_cli/cmd_settings_get.ts +44 -0
  200. package/src/scripts/_cli/doctor_execution.ts +173 -0
  201. package/src/scripts/_cli/doctor_network_posture.ts +226 -0
  202. package/src/scripts/_lib/activation_payload.ts +247 -0
  203. package/src/scripts/_lib/agent_settings.ts +4 -2
  204. package/src/scripts/_lib/authority_path.ts +102 -0
  205. package/src/scripts/_lib/body_portable.ts +102 -0
  206. package/src/scripts/_lib/cascade_base.ts +94 -0
  207. package/src/scripts/_lib/cc_transcript.ts +132 -1
  208. package/src/scripts/_lib/changelog_eras.ts +7 -2
  209. package/src/scripts/_lib/concern_sla_window.ts +200 -0
  210. package/src/scripts/_lib/continuation_ladder.ts +149 -2
  211. package/src/scripts/_lib/council_transport.ts +129 -0
  212. package/src/scripts/_lib/delivery_ready.ts +112 -0
  213. package/src/scripts/_lib/forge_protection.ts +381 -0
  214. package/src/scripts/_lib/forge_reader.ts +558 -0
  215. package/src/scripts/_lib/hook_platform_keys.ts +27 -0
  216. package/src/scripts/_lib/host_env_write.ts +111 -0
  217. package/src/scripts/_lib/json_python_parity.ts +127 -0
  218. package/src/scripts/_lib/lean_projection_mode.ts +14 -3
  219. package/src/scripts/_lib/llm_proposer_transport.ts +14 -4
  220. package/src/scripts/_lib/loop_surfaces.ts +382 -0
  221. package/src/scripts/_lib/mission_record.ts +190 -0
  222. package/src/scripts/_lib/obligation_frequency.ts +217 -0
  223. package/src/scripts/_lib/obligations.ts +513 -0
  224. package/src/scripts/_lib/orchestration_record.ts +11 -20
  225. package/src/scripts/_lib/probe_inputs.ts +271 -0
  226. package/src/scripts/_lib/read_surface_scan.ts +322 -0
  227. package/src/scripts/_lib/release_highlights.ts +201 -11
  228. package/src/scripts/_lib/release_holds.ts +493 -0
  229. package/src/scripts/_lib/release_migration_gate.ts +184 -0
  230. package/src/scripts/_lib/retrieval_sanitize.ts +77 -16
  231. package/src/scripts/_lib/rides_along.ts +102 -0
  232. package/src/scripts/_lib/settings_renamed_keys.ts +82 -0
  233. package/src/scripts/_lib/shell_write_shapes.ts +108 -0
  234. package/src/scripts/_lib/structural_hiding.ts +622 -0
  235. package/src/scripts/_lib/test_delta.ts +166 -0
  236. package/src/scripts/_lib/test_provenance.ts +171 -0
  237. package/src/scripts/_lib/turn_end_refusals.ts +525 -4
  238. package/src/scripts/_lib/turn_end_transcript.ts +216 -0
  239. package/src/scripts/_lib/typed_op_grant.ts +246 -0
  240. package/src/scripts/_lib/typed_op_watch.ts +185 -0
  241. package/src/scripts/_lib/verification_command.ts +324 -0
  242. package/src/scripts/_lib/verification_evidence.ts +647 -0
  243. package/src/scripts/_lib/verify_clause.ts +280 -0
  244. package/src/scripts/ai_council/config.ts +8 -11
  245. package/src/scripts/ai_council/fallback_config.ts +5 -3
  246. package/src/scripts/ai_council/ownership_classes.ts +118 -0
  247. package/src/scripts/ai_team/team_dispatch.ts +4 -0
  248. package/src/scripts/ask_block_census.ts +285 -15
  249. package/src/scripts/before_complete_hook.ts +340 -23
  250. package/src/scripts/bench_hook_latency.ts +412 -5
  251. package/src/scripts/cache_realization_report.ts +61 -1
  252. package/src/scripts/check_argument_hint.ts +287 -0
  253. package/src/scripts/check_claims.ts +125 -1
  254. package/src/scripts/check_enforcement_coverage.ts +38 -9
  255. package/src/scripts/check_enforcement_matrix.ts +866 -0
  256. package/src/scripts/check_finding_dispositions.ts +22 -3
  257. package/src/scripts/check_gate_reachability.ts +173 -10
  258. package/src/scripts/check_generator_sync.ts +1025 -0
  259. package/src/scripts/check_host_docs_digest.ts +786 -0
  260. package/src/scripts/check_host_format_column.ts +559 -0
  261. package/src/scripts/check_input_references.ts +228 -0
  262. package/src/scripts/check_inputs_parity.ts +235 -0
  263. package/src/scripts/check_kernel_edit_ratified.ts +47 -5
  264. package/src/scripts/check_kill_switch_table.ts +294 -0
  265. package/src/scripts/check_placeholder_drift.ts +221 -0
  266. package/src/scripts/check_preamble_payload_budget.ts +75 -2
  267. package/src/scripts/check_read_surface_coverage.ts +515 -0
  268. package/src/scripts/check_release_highlights.ts +33 -1
  269. package/src/scripts/check_release_holds.ts +568 -0
  270. package/src/scripts/check_routing_coverage.ts +378 -11
  271. package/src/scripts/check_rule_projection_integrity.ts +22 -12
  272. package/src/scripts/check_static_parity.ts +17 -0
  273. package/src/scripts/check_test_delta.ts +294 -0
  274. package/src/scripts/check_test_weakening.ts +299 -0
  275. package/src/scripts/ci_settle.ts +145 -19
  276. package/src/scripts/closure_scan.ts +447 -0
  277. package/src/scripts/compile_hook_manifest.ts +102 -18
  278. package/src/scripts/council_record_shape.ts +209 -0
  279. package/src/scripts/external_sources_denylist.json +29 -4
  280. package/src/scripts/hook_manifest.json +1 -1
  281. package/src/scripts/hook_manifest.yaml +127 -11
  282. package/src/scripts/hooks/block_config_weakening.ts +229 -4
  283. package/src/scripts/hooks/block_kernel_rule_writes.ts +9 -78
  284. package/src/scripts/hooks/block_plumbing_writes.ts +314 -0
  285. package/src/scripts/hooks/bundle_integrity.ts +291 -0
  286. package/src/scripts/hooks/chain_nudge_hook.ts +1 -2
  287. package/src/scripts/hooks/code_graph_context_hook.ts +1 -2
  288. package/src/scripts/hooks/comment_discipline_hook.ts +1 -3
  289. package/src/scripts/hooks/concern_registry.ts +6 -0
  290. package/src/scripts/hooks/concern_timings.ts +94 -0
  291. package/src/scripts/hooks/delegation_nudge_hook.ts +1 -2
  292. package/src/scripts/hooks/design_pass_hook.ts +221 -5
  293. package/src/scripts/hooks/design_slop_hook.ts +1 -2
  294. package/src/scripts/hooks/dispatch_hook.ts +25 -19
  295. package/src/scripts/hooks/edit_shape_hook.ts +1 -2
  296. package/src/scripts/hooks/end_review_nudge_hook.ts +1 -1
  297. package/src/scripts/hooks/evidence_independence.ts +97 -2
  298. package/src/scripts/hooks/exit_codes.ts +128 -0
  299. package/src/scripts/hooks/host_lowering.json +1 -0
  300. package/src/scripts/hooks/host_lowering.ts +128 -3
  301. package/src/scripts/hooks/host_lowering.yaml +262 -46
  302. package/src/scripts/hooks/host_semantics.ts +30 -2
  303. package/src/scripts/hooks/injection_budget.ts +9 -2
  304. package/src/scripts/hooks/interruption_ledger_hook.ts +1 -2
  305. package/src/scripts/hooks/journal_record_hook.ts +1 -2
  306. package/src/scripts/hooks/memory_recall_hook.ts +539 -0
  307. package/src/scripts/hooks/obligation_settle_hook.ts +328 -0
  308. package/src/scripts/hooks/one_question_per_ask_hook.ts +25 -4
  309. package/src/scripts/hooks/orchestration_record_hook.ts +1 -2
  310. package/src/scripts/hooks/permission_gate.ts +1 -2
  311. package/src/scripts/hooks/push_settle_hook.ts +1 -3
  312. package/src/scripts/hooks/replay_hook.ts +2 -1
  313. package/src/scripts/hooks/reread_guard_hook.ts +1 -2
  314. package/src/scripts/hooks/review_baseline_hook.ts +1 -2
  315. package/src/scripts/hooks/rtk_wrap_hook.ts +1 -2
  316. package/src/scripts/hooks/rule_inject_hook.ts +55 -4
  317. package/src/scripts/hooks/run_continuation_hook.ts +30 -35
  318. package/src/scripts/hooks/skill_route_hook.ts +1 -2
  319. package/src/scripts/hooks/source_first_gate_hook.ts +1 -2
  320. package/src/scripts/hooks/spawn_guard_shadow_hook.ts +1 -2
  321. package/src/scripts/hooks/subagent_ledger_hook.ts +1 -2
  322. package/src/scripts/hooks/suggestion_capture_hook.ts +1 -1
  323. package/src/scripts/hooks/table_fingerprint.ts +49 -0
  324. package/src/scripts/hooks/telemetry_flush_hook.ts +1 -2
  325. package/src/scripts/hooks/telemetry_usage_hook.ts +1 -2
  326. package/src/scripts/hooks/tool_result_bytes_hook.ts +93 -9
  327. package/src/scripts/hooks/turn_end_gate_hook.ts +337 -324
  328. package/src/scripts/hooks/ui_route_nudge_hook.ts +1 -2
  329. package/src/scripts/hooks_doctor.ts +85 -0
  330. package/src/scripts/hooks_status.ts +19 -0
  331. package/src/scripts/install.ts +109 -210
  332. package/src/scripts/janitor.ts +7 -0
  333. package/src/scripts/lint_decision_classes.ts +464 -0
  334. package/src/scripts/lint_deny_text.ts +419 -0
  335. package/src/scripts/lint_exit_codes.ts +262 -0
  336. package/src/scripts/lint_hook_manifest.ts +66 -0
  337. package/src/scripts/lint_major_migration_sections.ts +450 -0
  338. package/src/scripts/lint_plan_risk_register.ts +47 -3
  339. package/src/scripts/lint_roadmap_blockers.ts +139 -12
  340. package/src/scripts/lint_roadmap_producers.ts +202 -0
  341. package/src/scripts/lint_skill_descriptions.ts +1 -1
  342. package/src/scripts/lint_skill_scripts_readonly.ts +1 -1
  343. package/src/scripts/mcp_server/prompts.ts +142 -1
  344. package/src/scripts/measure_rule_budget.ts +101 -5
  345. package/src/scripts/measure_skill_ranker_baseline.ts +305 -51
  346. package/src/scripts/measure_turn_end_gate.ts +193 -0
  347. package/src/scripts/memory_lookup.ts +1 -1
  348. package/src/scripts/new_roadmap.ts +37 -0
  349. package/src/scripts/orchestration_record.ts +0 -5
  350. package/src/scripts/prepack-check.mjs +53 -0
  351. package/src/scripts/release.ts +14 -98
  352. package/src/scripts/release_drill.ts +267 -1
  353. package/src/scripts/release_publication.ts +64 -1
  354. package/src/scripts/report_enforcement_drift.ts +566 -0
  355. package/src/scripts/report_host_injection_effect.ts +3 -11
  356. package/src/scripts/report_invocation_surface.ts +383 -0
  357. package/src/scripts/report_obligation_carriers.ts +699 -0
  358. package/src/scripts/report_obligation_writer_split.ts +236 -0
  359. package/src/scripts/report_skill_menu_census.ts +208 -0
  360. package/src/scripts/report_standing_payload_by_host.ts +26 -152
  361. package/src/scripts/roadmap_verify_share.ts +213 -0
  362. package/src/scripts/schemas/agent-settings.schema.json +23 -0
  363. package/src/scripts/schemas/command.schema.json +48 -0
  364. package/src/scripts/schemas/review-findings.schema.json +4 -0
  365. package/src/scripts/schemas/skill.schema.json +44 -0
  366. package/src/scripts/scope_growth.ts +219 -0
  367. package/src/scripts/self_review_gate.ts +359 -10
  368. package/src/scripts/semantic_parity_census.ts +314 -0
  369. package/src/scripts/session_eol_report.ts +54 -1
  370. package/src/scripts/sweep_dead_scan_roots.ts +1 -1
  371. package/src/scripts/trigger_eval_grandfather.json +0 -3
  372. package/src/scripts/trigger_eval_rotation.ts +122 -27
  373. package/src/scripts/ui_conformance_probe.ts +774 -0
  374. package/src/scripts/update_prices.ts +85 -3
  375. package/src/scripts/write_bundle_digest.mjs +74 -0
  376. package/src/server/schemas/settings.ts +62 -7
  377. package/src/shared/missionExecution.ts +126 -0
  378. package/src/templates/consumer-settings/ONBOARDING.md +1 -0
  379. package/dist/cli-delegate/chunk-523HCFYO.js +0 -90
  380. package/dist/ui/assets/index-D0jaCRPM.js +0 -29
  381. package/dist/ui/assets/index-jSfMW8_t.css +0 -1
  382. /package/dist/cli-delegate/{chunk-YUM5KURG.js → chunk-7RQP4UDH.js} +0 -0
@@ -0,0 +1,111 @@
1
+ /**
2
+ * The only host environment variables this package will ever write.
3
+ *
4
+ * WHY ITS OWN MODULE. One place owns the question "which host variable may this
5
+ * package write", and the answer is a two-row allow table rather than a rule
6
+ * applied at a call site. `install.ts` imports it; nothing else writes host env.
7
+ * The move out of `install.ts` also keeps that file, already far over its size
8
+ * cap, from paying for this feature — the same reason the doctor's
9
+ * network-posture block moved into its own module.
10
+ *
11
+ * THE GUARANTEE IS THE SHAPE, not a check. Both traffic variables —
12
+ * `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` and `DISABLE_AUTOUPDATER` — reach
13
+ * the host's update-disabled resolver, the blanket one as its THIRD RUNG
14
+ * (measured on Claude Code 2.1.284, `docs/setup/host-traffic-environment.md`).
15
+ * A writer able to express either could disable a consumer's security updates.
16
+ * Owner-decided 2026-09-30 that it never may, and an allow table of two holds
17
+ * that where a deny list of two would have to stay complete as the host grows
18
+ * more variables.
19
+ *
20
+ * Both writable entries are request SIZE caps. Neither suppresses a call, an
21
+ * update check or a download — they bound how big a request is, never how many.
22
+ */
23
+
24
+ import * as fs from 'node:fs';
25
+ import * as path from 'node:path';
26
+
27
+ import * as YAML from 'yaml';
28
+
29
+ /** The allow table. Two rows, and adding a third is a governance change. */
30
+ export const WRITABLE_HOST_ENV: readonly { readonly variable: string; readonly settingsKey: string }[] = [
31
+ { variable: 'BASH_MAX_OUTPUT_LENGTH', settingsKey: 'bash_max_output_length' },
32
+ { variable: 'MAX_MCP_OUTPUT_TOKENS', settingsKey: 'max_mcp_output_tokens' },
33
+ ];
34
+
35
+ /** Where a project's settings document lives, canonical first. */
36
+ const SETTINGS_CANDIDATES: readonly string[][] = [
37
+ ['agents', 'settings', '.agent-settings.yml'],
38
+ ['.agent-settings.yml'],
39
+ ];
40
+
41
+ /**
42
+ * The project settings document, or `null` when there is none to read.
43
+ *
44
+ * `null` on an absent or unparseable file rather than `{}`: the caller reads a
45
+ * key whose ABSENCE means "write nothing", and collapsing an unreadable file to
46
+ * an empty object would give the same answer as a readable file with the key
47
+ * unset. Both write nothing, but only one of them is a fact.
48
+ */
49
+ export function readSettingsDoc(projectRoot: string): unknown | null {
50
+ for (const parts of SETTINGS_CANDIDATES) {
51
+ const p = path.join(projectRoot, ...parts);
52
+ let text: string;
53
+ try {
54
+ text = fs.readFileSync(p, 'utf-8');
55
+ } catch {
56
+ continue;
57
+ }
58
+ let doc: unknown;
59
+ try {
60
+ doc = YAML.parse(text, { version: '1.1' });
61
+ } catch {
62
+ return null;
63
+ }
64
+ return doc === null || doc === undefined || typeof doc !== 'object' ? null : doc;
65
+ }
66
+ return null;
67
+ }
68
+
69
+ /**
70
+ * The `env` block for the host settings file, or `null` when nothing is set.
71
+ *
72
+ * `null` rather than `{}`: an empty object would still be merged into the
73
+ * consumer's file, leaving an `env: {}` nobody asked for and making "this
74
+ * package wrote nothing" indistinguishable from "this package wrote a blank".
75
+ *
76
+ * A non-integer or non-positive value is IGNORED rather than written. The schema
77
+ * already refuses it; this branch is for a hand-edited settings file, where
78
+ * writing a value the host would silently reinterpret is worse than writing none.
79
+ */
80
+ export function hostEnvBlock(settings: unknown): Record<string, string> | null {
81
+ const root = settings as Record<string, unknown> | null | undefined;
82
+ const he = root?.['host_environment'] as Record<string, unknown> | undefined;
83
+ const caps = he?.['request_size_caps'] as Record<string, unknown> | undefined;
84
+ if (caps === undefined) return null;
85
+ const out: Record<string, string> = {};
86
+ for (const { variable, settingsKey } of WRITABLE_HOST_ENV) {
87
+ const v = caps[settingsKey];
88
+ if (typeof v === 'number' && Number.isInteger(v) && v > 0) out[variable] = String(v);
89
+ }
90
+ return Object.keys(out).length === 0 ? null : out;
91
+ }
92
+
93
+ /**
94
+ * The host bridge object with the `env` block merged in, if there is one.
95
+ *
96
+ * Takes the bridge and returns it rather than being called for its side effect,
97
+ * so the caller is one expression and `install.ts` — already far over its size
98
+ * cap — pays one line for this feature instead of six. `announce` is injected
99
+ * rather than imported: this module has no opinion about how a caller reports,
100
+ * and importing the installer's logger would make a leaf depend on its consumer.
101
+ */
102
+ export function withHostEnv(
103
+ bridge: Record<string, unknown>,
104
+ projectRoot: string,
105
+ announce: (msg: string) => void,
106
+ ): Record<string, unknown> {
107
+ const env = hostEnvBlock(readSettingsDoc(projectRoot));
108
+ if (env === null) return bridge;
109
+ announce(`.claude/settings.json: wrote ${Object.keys(env).join(', ')} (request-size caps)`);
110
+ return { ...bridge, env };
111
+ }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * `json.dumps` byte-parity helpers — the emit half of the ADR-200 port.
3
+ *
4
+ * The retired Python installer wrote every JSON artefact through
5
+ * `json.dumps(..., ensure_ascii=False)`, and the port's contract is
6
+ * byte-identical output, not merely equivalent JSON. `JSON.stringify` does not
7
+ * satisfy it: it escapes nothing above `\u001f` the same way, renders `NaN` /
8
+ * `Infinity` as `null` rather than as Python's bare tokens, and offers no
9
+ * separator control for the compact NDJSON form. So the three dump shapes the
10
+ * installer needs are written out here, mirroring CPython's encoder:
11
+ *
12
+ * - {@link jsonDumpsIndent} — `json.dumps(data, indent=N, ensure_ascii=False)`
13
+ * with `sort_keys=False` (insertion order preserved, as JS object key order
14
+ * already matches Python dict order for our string keys);
15
+ * - {@link jsonDumpsCompact} — `json.dumps(obj, separators=(",", ":"))`;
16
+ * - {@link jsonStrNoAscii} — the string encoder both build on.
17
+ *
18
+ * Sibling of `src/scripts/install.ts`, which is its only caller. It lives here
19
+ * rather than inline because the dump shapes are pure, total functions of their
20
+ * input with no installer state — and because `install.ts` is far over the
21
+ * 1,500-line source ceiling (`check_source_size_budget`), so a self-contained
22
+ * unit belongs outside it.
23
+ *
24
+ * This module is in the installer's bundled import closure
25
+ * (`npm run build:install-bundle`), so it carries no module-level side effect
26
+ * and no `process.exit` — see `check_installer_import_purity`.
27
+ */
28
+
29
+ export function jsonStrNoAscii(s: string): string {
30
+ // json.dumps(ensure_ascii=False): escape control chars + " + \, keep >=0x20
31
+ let out = '"';
32
+ for (const ch of s) {
33
+ const code = ch.codePointAt(0) as number;
34
+ switch (ch) {
35
+ case '"':
36
+ out += '\\"';
37
+ break;
38
+ case '\\':
39
+ out += '\\\\';
40
+ break;
41
+ case '\n':
42
+ out += '\\n';
43
+ break;
44
+ case '\r':
45
+ out += '\\r';
46
+ break;
47
+ case '\t':
48
+ out += '\\t';
49
+ break;
50
+ case '\b':
51
+ out += '\\b';
52
+ break;
53
+ case '\f':
54
+ out += '\\f';
55
+ break;
56
+ default:
57
+ if (code < 0x20) {
58
+ out += '\\u' + code.toString(16).padStart(4, '0');
59
+ } else {
60
+ out += ch;
61
+ }
62
+ }
63
+ }
64
+ return out + '"';
65
+ }
66
+
67
+ function _jsonScalar(value: unknown): string | null {
68
+ if (value === null || value === undefined) return 'null';
69
+ if (typeof value === 'boolean') return value ? 'true' : 'false';
70
+ if (typeof value === 'number') {
71
+ if (!Number.isFinite(value)) {
72
+ if (Number.isNaN(value)) return 'NaN';
73
+ return value > 0 ? 'Infinity' : '-Infinity';
74
+ }
75
+ // Our payloads carry only integers; render as-is.
76
+ return String(value);
77
+ }
78
+ if (typeof value === 'string') return jsonStrNoAscii(value);
79
+ return null;
80
+ }
81
+
82
+ function _dumpIndent(value: unknown, indent: number, depth: number): string {
83
+ const scalar = _jsonScalar(value);
84
+ if (scalar !== null) return scalar;
85
+ const pad = ' '.repeat(indent * (depth + 1));
86
+ const closePad = ' '.repeat(indent * depth);
87
+ if (Array.isArray(value)) {
88
+ if (value.length === 0) return '[]';
89
+ const items = value.map((v) => pad + _dumpIndent(v, indent, depth + 1));
90
+ return `[\n${items.join(',\n')}\n${closePad}]`;
91
+ }
92
+ if (typeof value === 'object' && value !== null) {
93
+ const obj = value as Record<string, unknown>;
94
+ const keys = Object.keys(obj);
95
+ if (keys.length === 0) return '{}';
96
+ const items = keys.map(
97
+ (k) => `${pad}${jsonStrNoAscii(k)}: ${_dumpIndent(obj[k], indent, depth + 1)}`,
98
+ );
99
+ return `{\n${items.join(',\n')}\n${closePad}}`;
100
+ }
101
+ return jsonStrNoAscii(String(value));
102
+ }
103
+
104
+ /** `json.dumps(data, indent=N, ensure_ascii=False)` (sort_keys=False). */
105
+ export function jsonDumpsIndent(value: unknown, indent: number): string {
106
+ return _dumpIndent(value, indent, 0);
107
+ }
108
+
109
+ /** `json.dumps(obj, separators=(",", ":"))` — compact, ensure_ascii=False here. */
110
+ export function jsonDumpsCompact(value: unknown): string {
111
+ const scalar = _jsonScalar(value);
112
+ if (scalar !== null) return scalar;
113
+ if (Array.isArray(value)) {
114
+ return '[' + value.map((v) => jsonDumpsCompact(v)).join(',') + ']';
115
+ }
116
+ if (typeof value === 'object' && value !== null) {
117
+ const obj = value as Record<string, unknown>;
118
+ return (
119
+ '{' +
120
+ Object.keys(obj)
121
+ .map((k) => `${jsonStrNoAscii(k)}:${jsonDumpsCompact(obj[k])}`)
122
+ .join(',') +
123
+ '}'
124
+ );
125
+ }
126
+ return jsonStrNoAscii(String(value));
127
+ }
@@ -11,11 +11,22 @@
11
11
  *
12
12
  * So each side supplies the raw string from its own reader and this module
13
13
  * decides what it means. Anything unrecognised — absent key, typo, `null`,
14
- * a non-string — is `eager-all`, today's shipped behaviour: a mode nobody can
15
- * spell must never silently thin the standing corpus.
14
+ * a non-string — is `eager-all`: the parser fallback, which is NOT the value
15
+ * the template ships (see the type below), because a mode nobody can spell
16
+ * must never silently thin the standing corpus.
16
17
  */
17
18
 
18
- /** The three projection shapes. `eager-all` is the shipped default. */
19
+ /**
20
+ * The three projection shapes, and the two different defaults they answer to.
21
+ *
22
+ * The TEMPLATE default — what a consumer is given — is `delivery` with
23
+ * `hosts: [claude-code]`, shipped in `src/config/agent-settings.template.yml`
24
+ * since ADR-267, so on that one host a normal read resolves to `delivery`.
25
+ * The PARSER FALLBACK — what applies when no value resolves from any layer —
26
+ * is `eager-all` (`DEFAULT_LEAN_PROJECTION_MODE` below), deliberately not
27
+ * flipped with the template per ADR-267 decision 4: the template says what was
28
+ * chosen, the constant says what happens when nothing could be read.
29
+ */
19
30
  export type LeanProjectionMode = 'eager-all' | 'thin' | 'delivery';
20
31
 
21
32
  export const DEFAULT_LEAN_PROJECTION_MODE: LeanProjectionMode = 'eager-all';
@@ -37,6 +37,7 @@
37
37
  */
38
38
 
39
39
  import { load_anthropic_key } from '../ai_council/clients.js';
40
+ import { sanitize_text } from './retrieval_sanitize.js';
40
41
  import type {
41
42
  GenerationRequest,
42
43
  GenerationResult,
@@ -140,10 +141,19 @@ export function anthropicGenerator(keyPath: string | null = null): TextGenerator
140
141
  );
141
142
  }
142
143
  const data = (await resp.json()) as AnthropicResponse;
143
- const text = (data.content ?? [])
144
- .filter((b) => b.type === 'text')
145
- .map((b) => b.text ?? '')
146
- .join('');
144
+ // Sanitize AT THE TRANSPORT, which is the choke point and one call site.
145
+ // A flag every caller has to remember is the instruction-only
146
+ // enforcement this package diagnoses in itself, and a partially
147
+ // sanitized world — some callers filtered, some not — is worse than
148
+ // either end state. The floor is a no-op on ordinary text, so what
149
+ // changes here is only a response that carries a hidden-instruction
150
+ // vector; see `docs/contracts/retrieval-read-surfaces.md`.
151
+ const text = sanitize_text(
152
+ (data.content ?? [])
153
+ .filter((b) => b.type === 'text')
154
+ .map((b) => b.text ?? '')
155
+ .join(''),
156
+ );
147
157
  // An empty body is returned AS an empty body rather than repaired here.
148
158
  // The arm's own output contract refuses it and classifies the refusal;
149
159
  // repairing it in the transport would move a decision into the layer
@@ -0,0 +1,382 @@
1
+ /**
2
+ * The loop-surface inventory, and the reach question it exists to answer.
3
+ *
4
+ * Why an inventory at all.
5
+ *
6
+ * `archive/road-to-wired-instruments.md` closed four instances of one defect —
7
+ * an instrument this repository built, tested, and then connected to nothing —
8
+ * one instance at a time, and shipped five hand-written `grep` acceptance
9
+ * criteria instead of a detector. Two further instances were open the whole
10
+ * time and nothing in the tree could notice them. This module is the reader
11
+ * half of the fix: a declared list of loop surfaces and the exported helpers
12
+ * they are supposed to call, plus the one predicate that says whether a
13
+ * declared helper is called by anything at all.
14
+ *
15
+ * What it is NOT.
16
+ *
17
+ * Reader input. Nothing executes from it, no surface changes behaviour because
18
+ * of it, and there is no runner. That is Risk 3 of the roadmap that introduced
19
+ * it, stated as a constraint rather than a hope: the moment something
20
+ * dispatches off this file it has become the loop engine a recorded decision
21
+ * forbids.
22
+ *
23
+ * The reach predicate, and the correction that shaped it.
24
+ *
25
+ * The obvious predicate — "no file other than its own imports this export" —
26
+ * was measured against `loop_guards.ts` and calls nine of its fourteen exports
27
+ * dead. Most of those are wrong: a helper used by its own neighbours in the
28
+ * same file is reached every time that file is. So the criterion is
29
+ * occurrence-based and counts the declaration itself:
30
+ *
31
+ * an export is DEAD when it appears in `src/` exactly once,
32
+ * including its own declaration.
33
+ *
34
+ * Two is enough, deliberately. A second occurrence anywhere in `src/` means
35
+ * some production line names it; whether that line sits in the same file or
36
+ * another one is not a difference this predicate needs to resolve, and
37
+ * resolving it would mean writing a second import resolver.
38
+ *
39
+ * `tests/` is outside `src/` and therefore outside the count by construction —
40
+ * which is the point. A test-only caller is exactly the state both open
41
+ * instances are in.
42
+ *
43
+ * Why `cap` accepts a markdown anchor as well as a symbol.
44
+ *
45
+ * The roadmap step that specified this file asked for `file:symbol` and
46
+ * "never prose". Reproduced against the tree on 2026-09-13, that is
47
+ * unsatisfiable for two of the five surfaces and a category error for both:
48
+ * `verify-repair-loop` and `experiment-loop` are SKILLS, and a skill's bound
49
+ * is a documented knob, not a TypeScript constant. `grep -rn max_attempts
50
+ * src/` returns hits only inside the skill's own directory.
51
+ *
52
+ * The property that requirement was reaching for is preserved exactly:
53
+ * a bound must resolve to something that demonstrably EXISTS. So a `.ts`
54
+ * target must be a defined symbol, a `.md` target must be a literal token
55
+ * present in that file, and a markdown-bound row must additionally declare
56
+ * `bound_kind: prose` — the prose-ness is recorded rather than inferred, and
57
+ * `inventoryVerdict` reports those surfaces as a named set so a prose bound
58
+ * can never be read as equal to a code one.
59
+ */
60
+
61
+ import * as fs from 'node:fs';
62
+ import * as path from 'node:path';
63
+
64
+ import { parse as parseYaml } from 'yaml';
65
+
66
+ export const INVENTORY_REL = 'src/config/loop-surfaces.yaml';
67
+
68
+ /** Fields every surface row must carry. Order is the order they are reported in. */
69
+ export const SURFACE_FIELDS = [
70
+ 'cap',
71
+ 'no_progress',
72
+ 'success_stop',
73
+ 'checker',
74
+ 'human_gate',
75
+ 'terminal_vocabulary',
76
+ 'production_consumers',
77
+ ] as const;
78
+
79
+ export type SurfaceField = (typeof SURFACE_FIELDS)[number];
80
+
81
+ /** A loop surface — a thing that iterates under a bound. */
82
+ export interface LoopSurface {
83
+ id: string;
84
+ /** `code` or `prose` — which kind of artefact the surface itself is. */
85
+ kind: string;
86
+ /** `code` when `cap`/`no_progress` name symbols, `prose` when they name doc anchors. */
87
+ bound_kind: string;
88
+ cap: string;
89
+ no_progress: string;
90
+ success_stop: string;
91
+ checker: string;
92
+ human_gate: string;
93
+ terminal_vocabulary: string;
94
+ production_consumers: string[];
95
+ }
96
+
97
+ /**
98
+ * An exported helper a surface is meant to call.
99
+ *
100
+ * Separated from surfaces because the reach question only applies here: a
101
+ * surface is reached by being bound (a hook slot, a skill invocation), an
102
+ * instrument is reached by being called, and only the second is answerable by
103
+ * counting occurrences of a symbol.
104
+ */
105
+ export interface LoopInstrument {
106
+ id: string;
107
+ file: string;
108
+ role: string;
109
+ /** `experimental` exempts it from the reach requirement — with a deadline. */
110
+ status?: string | undefined;
111
+ /** ISO `YYYY-MM-DD`. Required under `status: experimental`; a past date reds. */
112
+ expires?: string | undefined;
113
+ note?: string | undefined;
114
+ }
115
+
116
+ export interface Inventory {
117
+ surfaces: LoopSurface[];
118
+ instruments: LoopInstrument[];
119
+ }
120
+
121
+ /** `path/to/file.ts:SYMBOL` or `path/to/file.md:ANCHOR`. */
122
+ const BOUND_TARGET = /^([A-Za-z0-9_./-]+\.(?:ts|js|md)):([A-Za-z_$][A-Za-z0-9_$-]*)$/;
123
+
124
+ function str(row: Record<string, unknown>, key: string): string {
125
+ const v = row[key];
126
+ return v === undefined || v === null ? '' : String(v);
127
+ }
128
+
129
+ export function readInventory(root: string): Inventory | null {
130
+ const p = path.join(root, INVENTORY_REL);
131
+ if (!fs.existsSync(p)) return null;
132
+ const doc = parseYaml(fs.readFileSync(p, 'utf-8')) as {
133
+ surfaces?: Record<string, Record<string, unknown>>;
134
+ instruments?: Record<string, Record<string, unknown>>;
135
+ } | null;
136
+
137
+ const surfaces: LoopSurface[] = [];
138
+ for (const [id, row] of Object.entries(doc?.surfaces ?? {})) {
139
+ surfaces.push({
140
+ id,
141
+ kind: str(row, 'kind'),
142
+ bound_kind: str(row, 'bound_kind'),
143
+ cap: str(row, 'cap'),
144
+ no_progress: str(row, 'no_progress'),
145
+ success_stop: str(row, 'success_stop'),
146
+ checker: str(row, 'checker'),
147
+ human_gate: str(row, 'human_gate'),
148
+ terminal_vocabulary: str(row, 'terminal_vocabulary'),
149
+ production_consumers: Array.isArray(row['production_consumers'])
150
+ ? (row['production_consumers'] as unknown[]).map(String)
151
+ : [],
152
+ });
153
+ }
154
+
155
+ const instruments: LoopInstrument[] = [];
156
+ for (const [id, row] of Object.entries(doc?.instruments ?? {})) {
157
+ instruments.push({
158
+ id,
159
+ file: str(row, 'file'),
160
+ role: str(row, 'role'),
161
+ status: row['status'] === undefined ? undefined : String(row['status']),
162
+ expires: row['expires'] === undefined ? undefined : String(row['expires']),
163
+ note: row['note'] === undefined ? undefined : String(row['note']),
164
+ });
165
+ }
166
+
167
+ surfaces.sort((a, b) => a.id.localeCompare(b.id));
168
+ instruments.sort((a, b) => a.id.localeCompare(b.id));
169
+ return { surfaces, instruments };
170
+ }
171
+
172
+ /** Every `.ts` / `.js` file under `src/`, sorted. Cached per root. */
173
+ const sourceCache = new Map<string, string[]>();
174
+
175
+ export function sourceFiles(root: string): string[] {
176
+ const hit = sourceCache.get(root);
177
+ if (hit !== undefined) return hit;
178
+ const out: string[] = [];
179
+ const walk = (dir: string): void => {
180
+ let entries: fs.Dirent[];
181
+ try {
182
+ entries = fs.readdirSync(dir, { withFileTypes: true });
183
+ } catch {
184
+ return;
185
+ }
186
+ for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
187
+ const full = path.join(dir, e.name);
188
+ if (e.isDirectory()) {
189
+ if (e.name === 'node_modules') continue;
190
+ walk(full);
191
+ } else if (e.name.endsWith('.ts') || e.name.endsWith('.js')) {
192
+ out.push(full);
193
+ }
194
+ }
195
+ };
196
+ walk(path.join(root, 'src'));
197
+ sourceCache.set(root, out);
198
+ return out;
199
+ }
200
+
201
+ /** Drop the file-list cache. The self-test builds throwaway trees. */
202
+ export function resetSourceCache(): void {
203
+ sourceCache.clear();
204
+ }
205
+
206
+ function escapeRe(s: string): string {
207
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
208
+ }
209
+
210
+ /**
211
+ * How many times `symbol` occurs anywhere under `src/`, declaration included.
212
+ *
213
+ * Word-bounded, so `stallSignal` does not match `stallSignalled`. Deliberately
214
+ * text-level: a symbol resolver would have to model re-exports, barrel files
215
+ * and type-only imports to beat this, and would then be a second
216
+ * implementation of the compiler. The loose version's failure mode is a count
217
+ * that is too HIGH — reporting a dead export as live, which is the quiet
218
+ * direction. It never manufactures an accusation.
219
+ */
220
+ export function occurrences(root: string, symbol: string): number {
221
+ if (symbol === '') return 0;
222
+ const re = new RegExp(`\\b${escapeRe(symbol)}\\b`, 'g');
223
+ let n = 0;
224
+ for (const f of sourceFiles(root)) {
225
+ n += (fs.readFileSync(f, 'utf-8').match(re) ?? []).length;
226
+ }
227
+ return n;
228
+ }
229
+
230
+ /** Does `file` DEFINE `symbol` (a `.ts` target) or merely contain it (a `.md` anchor)? */
231
+ export function resolvesTarget(root: string, file: string, token: string): boolean {
232
+ const p = path.join(root, file);
233
+ if (!fs.existsSync(p)) return false;
234
+ const text = fs.readFileSync(p, 'utf-8');
235
+ const esc = escapeRe(token);
236
+ if (file.endsWith('.md')) return new RegExp(`\\b${esc}\\b`).test(text);
237
+ const decl = new RegExp(`\\b(?:const|let|var|function|interface|type|class|enum)\\s+${esc}\\b`);
238
+ if (decl.test(text)) return true;
239
+ // Object keys, union members and class fields: `TOKEN:` / `TOKEN(` at line start.
240
+ return new RegExp(`^\\s*(?:readonly\\s+)?${esc}\\s*[:(]`, 'm').test(text);
241
+ }
242
+
243
+ export interface ReachFinding {
244
+ id: string;
245
+ file: string;
246
+ /** `dead` · `expired` · `no-expiry` · `bad-status` — why it reds. */
247
+ reason: string;
248
+ detail: string;
249
+ }
250
+
251
+ export interface ShapeFinding {
252
+ id: string;
253
+ reason: string;
254
+ detail: string;
255
+ }
256
+
257
+ export interface InventoryVerdict {
258
+ /** Surfaces with a missing field, or a bound that resolves to nothing. */
259
+ shape: ShapeFinding[];
260
+ /** Declared instruments with no production consumer and no valid exemption. */
261
+ reach: ReachFinding[];
262
+ /** Instruments reported live — the does-not-over-fire evidence. */
263
+ live: string[];
264
+ /** Instruments held by a valid, unexpired `experimental` exemption. */
265
+ exempt: string[];
266
+ /** Surfaces whose bound is documented prose, not a code symbol. Reported, never red. */
267
+ proseBound: string[];
268
+ }
269
+
270
+ /** An instrument is reached when it occurs more than once in `src/`. */
271
+ export const REACH_MINIMUM_OCCURRENCES = 2;
272
+
273
+ const BOUND_FIELDS = ['cap', 'no_progress'] as const;
274
+
275
+ function checkSurface(root: string, s: LoopSurface, shape: ShapeFinding[]): void {
276
+ for (const f of SURFACE_FIELDS) {
277
+ const v = s[f];
278
+ const empty = Array.isArray(v) ? v.length === 0 : String(v).trim() === '';
279
+ if (empty) shape.push({ id: s.id, reason: 'missing-field', detail: `\`${f}\` is empty` });
280
+ }
281
+ if (s.bound_kind !== 'code' && s.bound_kind !== 'prose') {
282
+ shape.push({
283
+ id: s.id,
284
+ reason: 'bad-bound-kind',
285
+ detail: `\`bound_kind: ${s.bound_kind}\` — must be \`code\` or \`prose\``,
286
+ });
287
+ }
288
+ for (const f of BOUND_FIELDS) {
289
+ const raw = String(s[f]).trim();
290
+ if (raw === '') continue;
291
+ const m = BOUND_TARGET.exec(raw);
292
+ if (m === null) {
293
+ shape.push({
294
+ id: s.id,
295
+ reason: 'not-a-target',
296
+ detail: `\`${f}: ${raw}\` is not \`path/to/file.{ts,js,md}:TOKEN\``,
297
+ });
298
+ continue;
299
+ }
300
+ const [, file, token] = m as unknown as [string, string, string];
301
+ // A markdown bound must SAY it is prose. Undeclared, it would read as a
302
+ // code bound in every report that aggregates this file.
303
+ if (file.endsWith('.md') && s.bound_kind !== 'prose') {
304
+ shape.push({
305
+ id: s.id,
306
+ reason: 'undeclared-prose-bound',
307
+ detail: `\`${f}\` points into ${file} but the row is \`bound_kind: ${s.bound_kind}\``,
308
+ });
309
+ }
310
+ if (!resolvesTarget(root, file, token)) {
311
+ shape.push({
312
+ id: s.id,
313
+ reason: 'unresolved-target',
314
+ detail: `\`${f}\` names ${token} in ${file}, which does not carry it`,
315
+ });
316
+ }
317
+ }
318
+ }
319
+
320
+ function checkInstrument(root: string, i: LoopInstrument, day: string, v: InventoryVerdict): void {
321
+ if (i.status !== undefined && i.status !== 'experimental') {
322
+ v.reach.push({
323
+ id: i.id,
324
+ file: i.file,
325
+ reason: 'bad-status',
326
+ detail: `\`status: ${i.status}\` is not a recognised exemption (only \`experimental\`)`,
327
+ });
328
+ return;
329
+ }
330
+ if (i.status === 'experimental') {
331
+ const exp = (i.expires ?? '').trim();
332
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(exp)) {
333
+ v.reach.push({
334
+ id: i.id,
335
+ file: i.file,
336
+ reason: 'no-expiry',
337
+ detail: 'an `experimental` exemption needs `expires: YYYY-MM-DD`',
338
+ });
339
+ return;
340
+ }
341
+ if (exp <= day) {
342
+ v.reach.push({
343
+ id: i.id,
344
+ file: i.file,
345
+ reason: 'expired',
346
+ detail: `the \`experimental\` exemption lapsed on ${exp} — decide, or move the date with a reason`,
347
+ });
348
+ return;
349
+ }
350
+ v.exempt.push(i.id);
351
+ return;
352
+ }
353
+ const n = occurrences(root, i.id);
354
+ if (n < REACH_MINIMUM_OCCURRENCES) {
355
+ v.reach.push({
356
+ id: i.id,
357
+ file: i.file,
358
+ reason: 'dead',
359
+ detail: `occurs ${String(n)}× in src/ (own declaration included) — nothing calls it`,
360
+ });
361
+ } else {
362
+ v.live.push(i.id);
363
+ }
364
+ }
365
+
366
+ export function inventoryVerdict(root: string, today = new Date()): InventoryVerdict {
367
+ const v: InventoryVerdict = { shape: [], reach: [], live: [], exempt: [], proseBound: [] };
368
+ const inv = readInventory(root);
369
+ if (inv === null) return v;
370
+
371
+ for (const s of inv.surfaces) {
372
+ checkSurface(root, s, v.shape);
373
+ if (s.bound_kind === 'prose') v.proseBound.push(s.id);
374
+ }
375
+ const day = today.toISOString().slice(0, 10);
376
+ for (const i of inv.instruments) checkInstrument(root, i, day, v);
377
+
378
+ v.live.sort();
379
+ v.exempt.sort();
380
+ v.proseBound.sort();
381
+ return v;
382
+ }