@event4u/agent-config 14.9.0 → 14.11.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 (426) 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 +384 -0
  5. package/README.md +1 -1
  6. package/dist/agent-src/commands/design.md +88 -0
  7. package/dist/agent-src/commands/project/analyze.md +47 -0
  8. package/dist/agent-src/commands/review/changes.md +67 -6
  9. package/dist/agent-src/commands/roadmap/create.md +49 -0
  10. package/dist/agent-src/commands/roadmap/next.md +27 -11
  11. package/dist/agent-src/commands/roadmap/process-full.md +14 -0
  12. package/dist/agent-src/commands/video/from-script.md +60 -3
  13. package/dist/agent-src/commands/video/stitch.md +14 -0
  14. package/dist/agent-src/contexts/communication/rules-auto/guidelines-mechanics.md +1 -0
  15. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +154 -2
  16. package/dist/agent-src/rules/active-remediation.md +3 -3
  17. package/dist/agent-src/rules/code-provenance.md +2 -2
  18. package/dist/agent-src/rules/context-hygiene.md +13 -33
  19. package/dist/agent-src/rules/council-availability.md +2 -2
  20. package/dist/agent-src/rules/decision-revisit-gate.md +3 -1
  21. package/dist/agent-src/rules/design-fidelity.md +18 -0
  22. package/dist/agent-src/rules/design-review-after-ui-write.md +2 -2
  23. package/dist/agent-src/rules/evaluator-independence.md +56 -44
  24. package/dist/agent-src/rules/fix-what-you-see.md +9 -1
  25. package/dist/agent-src/rules/framework-neutrality-in-generic-skills.md +1 -1
  26. package/dist/agent-src/rules/minimal-safe-diff.md +14 -20
  27. package/dist/agent-src/rules/missing-skill-recovery.md +13 -1
  28. package/dist/agent-src/rules/playbook-precedence.md +105 -0
  29. package/dist/agent-src/rules/recurring-criticism.md +3 -1
  30. package/dist/agent-src/rules/roadmap-progress-sync.md +14 -13
  31. package/dist/agent-src/rules/scale-discipline.md +24 -0
  32. package/dist/agent-src/rules/security-sensitive-stop.md +2 -2
  33. package/dist/agent-src/rules/settings-ask-protocol.md +2 -2
  34. package/dist/agent-src/rules/ui-audit-gate.md +27 -15
  35. package/dist/agent-src/rules/untrusted-input-defense.md +2 -2
  36. package/dist/agent-src/skills/accessibility-auditor/SKILL.md +19 -0
  37. package/dist/agent-src/skills/adr-create/SKILL.md +7 -0
  38. package/dist/agent-src/skills/agents-md-thin-root/SKILL.md +33 -0
  39. package/dist/agent-src/skills/ai-council/references/advanced-modes.md +17 -3
  40. package/dist/agent-src/skills/ai-council/references/output-and-synthesis.md +31 -0
  41. package/dist/agent-src/skills/ai-council/references/procedure.md +19 -0
  42. package/dist/agent-src/skills/alerting-doctrine/SKILL.md +225 -0
  43. package/dist/agent-src/skills/alerting-doctrine/evals/triggers.json +17 -0
  44. package/dist/agent-src/skills/async-python-patterns/SKILL.md +1 -1
  45. package/dist/agent-src/skills/authz-review/SKILL.md +0 -1
  46. package/dist/agent-src/skills/blast-radius-analyzer/SKILL.md +32 -0
  47. package/dist/agent-src/skills/brand/SKILL.md +3 -0
  48. package/dist/agent-src/skills/brand-asset-generation/SKILL.md +3 -0
  49. package/dist/agent-src/skills/brand-audit/SKILL.md +3 -0
  50. package/dist/agent-src/skills/brand-identity/SKILL.md +3 -0
  51. package/dist/agent-src/skills/brand-strategy/SKILL.md +3 -0
  52. package/dist/agent-src/skills/brand-to-tokens/SKILL.md +3 -0
  53. package/dist/agent-src/skills/check-refs/SKILL.md +3 -0
  54. package/dist/agent-src/skills/code-refactoring/SKILL.md +1 -1
  55. package/dist/agent-src/skills/code-review/SKILL.md +11 -0
  56. package/dist/agent-src/skills/command-routing/SKILL.md +6 -0
  57. package/dist/agent-src/skills/condense-memory/SKILL.md +4 -1
  58. package/dist/agent-src/skills/context-authoring/SKILL.md +1 -1
  59. package/dist/agent-src/skills/conventional-commits-writing/SKILL.md +4 -1
  60. package/dist/agent-src/skills/copilot-agents-optimization/SKILL.md +10 -0
  61. package/dist/agent-src/skills/corpus-grounding/SKILL.md +3 -0
  62. package/dist/agent-src/skills/design-intelligence/SKILL.md +3 -0
  63. package/dist/agent-src/skills/design-intelligence/data/landing.csv +36 -35
  64. package/dist/agent-src/skills/design-intelligence/data/stacks/threejs.csv +1 -0
  65. package/dist/agent-src/skills/design-review/SKILL.md +80 -2
  66. package/dist/agent-src/skills/design-review/references/verification-automation.md +41 -0
  67. package/dist/agent-src/skills/design-system-capture/SKILL.md +35 -40
  68. package/dist/agent-src/skills/design-system-capture/references/import-procedure.md +50 -0
  69. package/dist/agent-src/skills/design-tokens/SKILL.md +3 -0
  70. package/dist/agent-src/skills/design-variations/SKILL.md +3 -0
  71. package/dist/agent-src/skills/developer-like-execution/SKILL.md +3 -0
  72. package/dist/agent-src/skills/discovery-interview/SKILL.md +1 -1
  73. package/dist/agent-src/skills/doc-coauthoring/SKILL.md +1 -1
  74. package/dist/agent-src/skills/docx-authoring/SKILL.md +1 -1
  75. package/dist/agent-src/skills/editorial-calendar/SKILL.md +1 -1
  76. package/dist/agent-src/skills/estimate-ticket/SKILL.md +3 -0
  77. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +20 -3
  78. package/dist/agent-src/skills/fe-design/SKILL.md +74 -41
  79. package/dist/agent-src/skills/fe-design/evals/triggers.json +65 -53
  80. package/dist/agent-src/skills/fe-design/references/craft-floor.md +52 -0
  81. package/dist/agent-src/skills/fe-design/references/source-led-port.md +36 -0
  82. package/dist/agent-src/skills/file-editor/SKILL.md +5 -0
  83. package/dist/agent-src/skills/finishing-a-development-branch/SKILL.md +2 -2
  84. package/dist/agent-src/skills/git-workflow/SKILL.md +3 -0
  85. package/dist/agent-src/skills/github-ci/SKILL.md +1 -1
  86. package/dist/agent-src/skills/html-deck/SKILL.md +3 -0
  87. package/dist/agent-src/skills/iconography/SKILL.md +3 -0
  88. package/dist/agent-src/skills/image-editing/SKILL.md +3 -0
  89. package/dist/agent-src/skills/image-generation/SKILL.md +3 -0
  90. package/dist/agent-src/skills/image-provider-routing/SKILL.md +3 -0
  91. package/dist/agent-src/skills/incident-commander/SKILL.md +10 -0
  92. package/dist/agent-src/skills/js-library-packaging/SKILL.md +173 -0
  93. package/dist/agent-src/skills/js-library-packaging/evals/triggers.json +17 -0
  94. package/dist/agent-src/skills/js-library-packaging/scripts/check_package_surface.ts +270 -0
  95. package/dist/agent-src/skills/judge-artifact-completeness/SKILL.md +1 -1
  96. package/dist/agent-src/skills/judge-injection-defense/SKILL.md +1 -1
  97. package/dist/agent-src/skills/laravel-migration/SKILL.md +3 -0
  98. package/dist/agent-src/skills/laravel-validation/SKILL.md +1 -1
  99. package/dist/agent-src/skills/learning-to-rule-or-skill/SKILL.md +3 -0
  100. package/dist/agent-src/skills/legal-intake-triage/SKILL.md +1 -1
  101. package/dist/agent-src/skills/legal-practice-profile/SKILL.md +1 -1
  102. package/dist/agent-src/skills/lint-skills/SKILL.md +3 -0
  103. package/dist/agent-src/skills/llm-provider-knowledge/SKILL.md +3 -0
  104. package/dist/agent-src/skills/logging-monitoring/SKILL.md +234 -51
  105. package/dist/agent-src/skills/logo-generation/SKILL.md +3 -0
  106. package/dist/agent-src/skills/md-language-check/SKILL.md +3 -0
  107. package/dist/agent-src/skills/messaging-architecture/SKILL.md +1 -1
  108. package/dist/agent-src/skills/migration-architect/SKILL.md +1 -1
  109. package/dist/agent-src/skills/module-detect-on-the-fly/SKILL.md +1 -1
  110. package/dist/agent-src/skills/module-management/SKILL.md +1 -1
  111. package/dist/agent-src/skills/monorepo-workspace/SKILL.md +149 -0
  112. package/dist/agent-src/skills/monorepo-workspace/evals/triggers.json +17 -0
  113. package/dist/agent-src/skills/motion-choreographer/SKILL.md +7 -1
  114. package/dist/agent-src/skills/multi-tenancy/SKILL.md +4 -0
  115. package/dist/agent-src/skills/onboarding-program/SKILL.md +1 -1
  116. package/dist/agent-src/skills/operational-readiness/SKILL.md +184 -0
  117. package/dist/agent-src/skills/operational-readiness/evals/triggers.json +17 -0
  118. package/dist/agent-src/skills/override-management/SKILL.md +3 -0
  119. package/dist/agent-src/skills/perf-feedback-craft/SKILL.md +1 -1
  120. package/dist/agent-src/skills/persona-improvement/SKILL.md +3 -0
  121. package/dist/agent-src/skills/pest-testing/SKILL.md +3 -0
  122. package/dist/agent-src/skills/playbook-authoring/SKILL.md +162 -0
  123. package/dist/agent-src/skills/playwright-testing/SKILL.md +3 -0
  124. package/dist/agent-src/skills/privacy-review/SKILL.md +3 -0
  125. package/dist/agent-src/skills/prompt-engineering-image/SKILL.md +3 -0
  126. package/dist/agent-src/skills/prompt-optimizer/SKILL.md +1 -1
  127. package/dist/agent-src/skills/quality-tools/SKILL.md +5 -0
  128. package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +52 -21
  129. package/dist/agent-src/skills/readme-reviewer/SKILL.md +3 -0
  130. package/dist/agent-src/skills/readme-writing/SKILL.md +3 -0
  131. package/dist/agent-src/skills/readme-writing-package/SKILL.md +3 -0
  132. package/dist/agent-src/skills/receiving-code-review/SKILL.md +3 -3
  133. package/dist/agent-src/skills/refine-prompt/SKILL.md +3 -0
  134. package/dist/agent-src/skills/refine-ticket/SKILL.md +3 -0
  135. package/dist/agent-src/skills/requesting-code-review/SKILL.md +2 -2
  136. package/dist/agent-src/skills/review-routing/SKILL.md +2 -2
  137. package/dist/agent-src/skills/roadmap-writing/SKILL.md +23 -41
  138. package/dist/agent-src/skills/roadmap-writing/references/follow-up-roadmap-shape.md +47 -0
  139. package/dist/agent-src/skills/rtk-output-filtering/SKILL.md +22 -6
  140. package/dist/agent-src/skills/runway-cognition/SKILL.md +3 -0
  141. package/dist/agent-src/skills/scene-expander/SKILL.md +1 -1
  142. package/dist/agent-src/skills/scene-expander/scene-blueprint.schema.yaml +26 -0
  143. package/dist/agent-src/skills/secrets-management/SKILL.md +5 -1
  144. package/dist/agent-src/skills/server-hardening/SKILL.md +159 -0
  145. package/dist/agent-src/skills/server-hardening/evals/triggers.json +17 -0
  146. package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +3 -0
  147. package/dist/agent-src/skills/skill-management/SKILL.md +3 -0
  148. package/dist/agent-src/skills/skill-reviewer/SKILL.md +3 -0
  149. package/dist/agent-src/skills/skill-writing/SKILL.md +6 -0
  150. package/dist/agent-src/skills/spreadsheet-authoring/SKILL.md +3 -0
  151. package/dist/agent-src/skills/storybook-workshop/SKILL.md +194 -0
  152. package/dist/agent-src/skills/storybook-workshop/evals/triggers.json +17 -0
  153. package/dist/agent-src/skills/storybook-workshop/scripts/story_contrast_floor.ts +134 -0
  154. package/dist/agent-src/skills/subagent-orchestration/SKILL.md +1 -1
  155. package/dist/agent-src/skills/subagent-orchestration/prompts/README.md +25 -0
  156. package/dist/agent-src/skills/supply-chain-intake/SKILL.md +12 -0
  157. package/dist/agent-src/skills/systematic-debugging/SKILL.md +1 -1
  158. package/dist/agent-src/skills/terraform/SKILL.md +3 -0
  159. package/dist/agent-src/skills/test-driven-development/SKILL.md +1 -1
  160. package/dist/agent-src/skills/threat-modeling/SKILL.md +0 -3
  161. package/dist/agent-src/skills/token-optimizer/SKILL.md +3 -0
  162. package/dist/agent-src/skills/typography-system/SKILL.md +3 -0
  163. package/dist/agent-src/skills/ui-component-architect/SKILL.md +9 -1
  164. package/dist/agent-src/skills/upstream-contribute/SKILL.md +3 -0
  165. package/dist/agent-src/skills/using-git-worktrees/SKILL.md +1 -1
  166. package/dist/agent-src/skills/validate-feature-fit/SKILL.md +3 -0
  167. package/dist/agent-src/skills/verify-completion-evidence/SKILL.md +2 -2
  168. package/dist/agent-src/skills/wireframe/SKILL.md +3 -0
  169. package/dist/agent-src/skills/wireframe/references/story-beat-ledger.schema.json +115 -0
  170. package/dist/agent-src/skills/workspace-link/SKILL.md +131 -0
  171. package/dist/agent-src/skills/workspace-link/evals/triggers.json +17 -0
  172. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  173. package/dist/agent-src/templates/playbook.md +75 -0
  174. package/dist/agent-src/templates/roadmaps.md +37 -0
  175. package/dist/agent-src/templates/scripts/check_playbook_invokes.ts +231 -0
  176. package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +24 -0
  177. package/dist/agent-src/templates/scripts/work_engine/directives/ui/polish.ts +2 -0
  178. package/dist/agent-src/templates/scripts/work_engine/directives/ui/review.ts +216 -0
  179. package/dist/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +159 -0
  180. package/dist/agent-src/templates/scripts/work_engine/directives/ui/stack_bundles.ts +73 -4
  181. package/dist/agent-src/templates/scripts/work_engine/stack/detect.ts +325 -40
  182. package/dist/cli/commands/uiAudit.js +221 -0
  183. package/dist/cli/commands/uiAudit.js.map +1 -0
  184. package/dist/cli/commands/uiRender.js +206 -0
  185. package/dist/cli/commands/uiRender.js.map +1 -0
  186. package/dist/cli/main.js +22 -1
  187. package/dist/cli/main.js.map +1 -1
  188. package/dist/cli/mcp/content.js +64 -1
  189. package/dist/cli/mcp/content.js.map +1 -1
  190. package/dist/cli/mcp/dispatch.js +175 -10
  191. package/dist/cli/mcp/dispatch.js.map +1 -1
  192. package/dist/cli/registry.js +3 -0
  193. package/dist/cli/registry.js.map +1 -1
  194. package/dist/cli-delegate/{chunk-H4VIVD4C.js → chunk-236PNQZK.js} +413 -391
  195. package/dist/cli-delegate/{chunk-G23AT6GD.js → chunk-DMSZHXZS.js} +57 -1
  196. package/dist/cli-delegate/{chunk-77GR3VD4.js → chunk-FF7WAFMR.js} +58 -3
  197. package/dist/cli-delegate/{chunk-PLSG6UL5.js → chunk-K4FFODTS.js} +341 -204
  198. package/dist/cli-delegate/{chunk-YM4GZDJG.js → chunk-L43NCYPB.js} +5 -0
  199. package/dist/cli-delegate/{chunk-B2ERU2VA.js → chunk-WOKWX5PL.js} +1 -1
  200. package/dist/cli-delegate/cmd_brand_status.js +8 -6
  201. package/dist/cli-delegate/cmd_conformance.js +3 -3
  202. package/dist/cli-delegate/cmd_doctor.js +3 -3
  203. package/dist/cli-delegate/cmd_export.js +4 -4
  204. package/dist/cli-delegate/cmd_handoff.js +1 -1
  205. package/dist/cli-delegate/cmd_preflight.js +2 -2
  206. package/dist/cli-delegate/cmd_prune.js +4 -4
  207. package/dist/cli-delegate/cmd_session_recycle.js +70 -9
  208. package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
  209. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  210. package/dist/cli-delegate/cmd_sync.js +4 -4
  211. package/dist/cli-delegate/cmd_uninstall.js +4 -4
  212. package/dist/cli-delegate/cmd_validate.js +4 -4
  213. package/dist/discovery/deprecation-report.md +1 -1
  214. package/dist/discovery/discovery-manifest.json +380 -145
  215. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  216. package/dist/discovery/discovery-manifest.summary.md +4 -4
  217. package/dist/discovery/dormancy-report.md +1 -1
  218. package/dist/discovery/orphan-report.md +1 -1
  219. package/dist/discovery/packs.json +18 -8
  220. package/dist/discovery/trust-report.md +3 -3
  221. package/dist/discovery/workspaces.json +16 -6
  222. package/dist/hooks/dispatch.js +2337 -1436
  223. package/dist/install/install.mjs +795 -708
  224. package/dist/mcp/registry-manifest.json +3 -3
  225. package/dist/mcp/server.json +2 -2
  226. package/dist/mcp/server.mjs +1685 -1250
  227. package/dist/router.json +1 -1
  228. package/dist/scripts/_lib/lean_projection_mode.js +40 -0
  229. package/dist/scripts/_lib/lean_projection_mode.js.map +1 -0
  230. package/dist/scripts/_lib/scoped_projection.js +84 -0
  231. package/dist/scripts/_lib/scoped_projection.js.map +1 -1
  232. package/dist/scripts/_lib/ui_surface.js +89 -0
  233. package/dist/scripts/_lib/ui_surface.js.map +1 -0
  234. package/dist/scripts/condense.js +6 -10
  235. package/dist/scripts/condense.js.map +1 -1
  236. package/dist/scripts/project_thin_rules.js +40 -2
  237. package/dist/scripts/project_thin_rules.js.map +1 -1
  238. package/dist/server/schemas/settings.js +3 -0
  239. package/dist/server/schemas/settings.js.map +1 -1
  240. package/dist/shared/skillRanking.js +159 -0
  241. package/dist/shared/skillRanking.js.map +1 -0
  242. package/docs/contracts/provider-lifecycle.md +47 -18
  243. package/docs/contracts/settings-classes.md +14 -4
  244. package/docs/guidelines/agent-infra/context-hygiene-mechanics.md +49 -0
  245. package/docs/guidelines/agent-infra/evaluator-independence-mechanics.md +92 -0
  246. package/docs/guidelines/agent-infra/minimal-safe-diff-mechanics.md +45 -1
  247. package/docs/guidelines/agent-infra/roadmap-deferred-resolution-provenance.md +54 -0
  248. package/docs/guidelines/design-antipatterns.md +4 -2
  249. package/docs/guidelines/design-asset-discipline.md +38 -0
  250. package/docs/guidelines/design-fidelity-mechanics.md +45 -29
  251. package/docs/guidelines/design-handover-extraction.md +35 -5
  252. package/docs/guidelines/design-modes.md +41 -1
  253. package/docs/guidelines/monorepo-antipatterns.md +141 -0
  254. package/llms.txt +50 -39
  255. package/package.json +1 -1
  256. package/src/agent-src/templates/scripts/check_playbook_invokes.ts +231 -0
  257. package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +24 -0
  258. package/src/agent-src/templates/scripts/work_engine/directives/ui/polish.ts +2 -0
  259. package/src/agent-src/templates/scripts/work_engine/directives/ui/review.ts +216 -0
  260. package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +159 -0
  261. package/src/agent-src/templates/scripts/work_engine/directives/ui/stack_bundles.ts +73 -4
  262. package/src/agent-src/templates/scripts/work_engine/stack/detect.ts +325 -40
  263. package/src/config/agent-settings.template.yml +28 -0
  264. package/src/config/assurance-capability-registry.json +223 -0
  265. package/src/config/assurance-threshold-budget.json +65 -0
  266. package/src/config/ci-local-parity.yml +14 -0
  267. package/src/config/discovery/unassigned-artefacts.yml +2 -0
  268. package/src/config/dispatch-economy-metrics.json +4 -2
  269. package/src/config/evaluator-budgets.json +14 -14
  270. package/src/config/gate-coverage.yml +227 -8
  271. package/src/config/gate-violation-baselines.json +76 -72
  272. package/src/config/hook-latency-budget.json +1 -0
  273. package/src/config/hook-token-budget.json +137 -125
  274. package/src/config/pack-size-budget.json +4 -2
  275. package/src/config/preamble-payload-budget.json +71 -67
  276. package/src/config/quorum-attendance-budget.json +12 -4
  277. package/src/config/rule-activation-census.json +7 -3
  278. package/src/config/rule-enforcement-baseline.json +0 -1
  279. package/src/config/rule-stub-ceilings.json +112 -91
  280. package/src/scripts/_cli/cmd_doctor.ts +15 -16
  281. package/src/scripts/_cli/doctor_install_mode.ts +49 -0
  282. package/src/scripts/_cli/doctor_overrides.ts +154 -0
  283. package/src/scripts/_dispatch.bash +17 -0
  284. package/src/scripts/_lib/as_of.ts +412 -0
  285. package/src/scripts/_lib/exec_evidence.ts +7 -0
  286. package/src/scripts/_lib/hook_settings.ts +33 -0
  287. package/src/scripts/_lib/host_listing_model.ts +192 -0
  288. package/src/scripts/_lib/lean_projection_mode.ts +44 -0
  289. package/src/scripts/_lib/mcp_bridge.ts +199 -0
  290. package/src/scripts/_lib/review_independence.ts +62 -10
  291. package/src/scripts/_lib/rule_injection.ts +239 -0
  292. package/src/scripts/_lib/run_checkpoint.ts +54 -1
  293. package/src/scripts/_lib/scoped_projection.ts +96 -0
  294. package/src/scripts/_lib/session_register.ts +44 -2
  295. package/src/scripts/_lib/skill_catalogue.ts +38 -2
  296. package/src/scripts/_lib/transcript_reads.ts +207 -0
  297. package/src/scripts/_lib/ui_authority.test.ts +251 -0
  298. package/src/scripts/_lib/ui_authority.ts +343 -0
  299. package/src/scripts/ai-video/adapters/fal.sh +8 -3
  300. package/src/scripts/ai-video/adapters/gemini-veo.sh +10 -0
  301. package/src/scripts/ai-video/adapters/higgsfield.sh +10 -2
  302. package/src/scripts/ai-video/adapters/kling.sh +10 -0
  303. package/src/scripts/ai-video/adapters/sora.sh +10 -0
  304. package/src/scripts/ai-video/lib/calibrate-cost.sh +145 -0
  305. package/src/scripts/ai-video/lib/model-capabilities/README.md +53 -7
  306. package/src/scripts/ai-video/lib/model-capabilities/comfyui.json +15 -3
  307. package/src/scripts/ai-video/lib/model-capabilities/fal.json +36 -6
  308. package/src/scripts/ai-video/lib/model-capabilities/gemini-veo.json +23 -0
  309. package/src/scripts/ai-video/lib/model-capabilities/higgsfield.json +23 -0
  310. package/src/scripts/ai-video/lib/model-capabilities/kling.json +23 -0
  311. package/src/scripts/ai-video/lib/model-capabilities/musetalk.json +8 -2
  312. package/src/scripts/ai-video/lib/model-capabilities/replicate.json +36 -6
  313. package/src/scripts/ai-video/lib/model-capabilities/sora.json +45 -0
  314. package/src/scripts/ai-video/lib/model-capabilities/syncso.json +8 -2
  315. package/src/scripts/ai-video/lib/parse-blueprint.sh +52 -5
  316. package/src/scripts/ai-video/lib/resume-scan.sh +92 -3
  317. package/src/scripts/ai-video/lib/seam-plan.sh +212 -0
  318. package/src/scripts/ai-video/smoke-trace.sh +450 -2
  319. package/src/scripts/ai-video/stitch.sh +191 -10
  320. package/src/scripts/ai-video/test-pipeline.sh +84 -6
  321. package/src/scripts/ai_council/cli_agency_bounds.ts +139 -0
  322. package/src/scripts/ai_council/cli_least_agency_canary.ts +347 -0
  323. package/src/scripts/ai_council/clients.ts +3 -2
  324. package/src/scripts/ai_council/consensus.ts +63 -6
  325. package/src/scripts/ai_council/events_log.ts +46 -1
  326. package/src/scripts/ai_council/orchestrator.ts +103 -108
  327. package/src/scripts/ai_council/orchestrator_results.ts +166 -0
  328. package/src/scripts/ai_council/prompts.ts +12 -5
  329. package/src/scripts/ai_council/quorum.ts +82 -1
  330. package/src/scripts/ai_council/quorum_wiring.ts +82 -5
  331. package/src/scripts/ai_council/seating.ts +211 -0
  332. package/src/scripts/ai_council/stance_tally.ts +22 -0
  333. package/src/scripts/bench_hook_latency.ts +62 -0
  334. package/src/scripts/build_proof.ts +12 -1
  335. package/src/scripts/capture_skill_catalogue.ts +21 -0
  336. package/src/scripts/check_always_budget.ts +2 -1
  337. package/src/scripts/check_augmentignore.ts +2 -1
  338. package/src/scripts/check_beta_review_markers.ts +2 -1
  339. package/src/scripts/check_composite_arming.ts +364 -0
  340. package/src/scripts/check_corpus_staleness.ts +2 -1
  341. package/src/scripts/check_council_pin_staleness.ts +3 -1
  342. package/src/scripts/check_enforcement_coverage.ts +116 -3
  343. package/src/scripts/check_enforcement_denominator.ts +365 -0
  344. package/src/scripts/check_gate_coverage.ts +2 -1
  345. package/src/scripts/check_hook_bundle_freshness.ts +13 -1
  346. package/src/scripts/check_knowledge_cards.ts +2 -1
  347. package/src/scripts/check_knowledge_pages.ts +3 -2
  348. package/src/scripts/check_memory.ts +2 -1
  349. package/src/scripts/check_no_automerge_key.ts +287 -0
  350. package/src/scripts/check_pack_conformance_fixture.ts +565 -0
  351. package/src/scripts/check_pack_passport_reconciliation.ts +274 -0
  352. package/src/scripts/check_proposal.ts +2 -1
  353. package/src/scripts/check_reach_staleness.ts +2 -1
  354. package/src/scripts/check_release_adjacent_health.ts +2 -1
  355. package/src/scripts/check_release_pr_shape.ts +5 -0
  356. package/src/scripts/check_review_schema.ts +34 -1
  357. package/src/scripts/check_roadmap_trackable.ts +48 -3
  358. package/src/scripts/check_source_size_budget.ts +2 -1
  359. package/src/scripts/check_standing_payload_delta.ts +505 -0
  360. package/src/scripts/check_trigger_evals.ts +2 -1
  361. package/src/scripts/classify_change_risk.ts +281 -0
  362. package/src/scripts/compute_skill_tiers.ts +186 -0
  363. package/src/scripts/condense.ts +8 -13
  364. package/src/scripts/council_attendance_metrics.ts +183 -0
  365. package/src/scripts/council_cli.ts +10 -8
  366. package/src/scripts/council_parse_rate.ts +116 -0
  367. package/src/scripts/derive_playbooks.ts +406 -0
  368. package/src/scripts/design_slop_rules.test.ts +7 -0
  369. package/src/scripts/design_slop_rules.ts +56 -2
  370. package/src/scripts/dispatch_r2_reviewer.ts +16 -0
  371. package/src/scripts/frontend_corpus_hash.ts +116 -0
  372. package/src/scripts/generate_pack_manifests.ts +78 -0
  373. package/src/scripts/grade_target_readiness.ts +478 -0
  374. package/src/scripts/hook_manifest.json +1 -1
  375. package/src/scripts/hook_manifest.yaml +97 -16
  376. package/src/scripts/hooks/concern_registry.ts +4 -0
  377. package/src/scripts/hooks/design_pass_hook.ts +356 -0
  378. package/src/scripts/hooks/rtk_wrap_hook.ts +30 -7
  379. package/src/scripts/hooks/rule_inject_hook.ts +318 -0
  380. package/src/scripts/hot_context_hook.ts +74 -7
  381. package/src/scripts/install.ts +40 -89
  382. package/src/scripts/lint_adapter_tier.ts +663 -0
  383. package/src/scripts/lint_archived_skills.ts +93 -0
  384. package/src/scripts/lint_budget_ownership.ts +2 -1
  385. package/src/scripts/lint_deterministic_time.ts +470 -0
  386. package/src/scripts/lint_framework_leakage.ts +5 -0
  387. package/src/scripts/lint_handoffs.ts +525 -1
  388. package/src/scripts/lint_judge_prompt_expectation.ts +155 -0
  389. package/src/scripts/lint_marketplace.ts +88 -0
  390. package/src/scripts/lint_media_policy_linkage.ts +140 -1
  391. package/src/scripts/lint_one_off_age.ts +6 -1
  392. package/src/scripts/lint_override_kernel_guard.ts +87 -5
  393. package/src/scripts/lint_roadmap_complexity.ts +56 -0
  394. package/src/scripts/lint_rule_enforcement_declaration.ts +24 -7
  395. package/src/scripts/lint_rule_norm_pin.ts +321 -0
  396. package/src/scripts/lint_spawn_payload.ts +42 -5
  397. package/src/scripts/lint_symptom_intake.ts +2 -1
  398. package/src/scripts/mcp_parity_smoke.ts +30 -4
  399. package/src/scripts/mcp_render.ts +66 -1
  400. package/src/scripts/mcp_tool_fingerprint.ts +241 -0
  401. package/src/scripts/measure_risk_class_drift.ts +140 -0
  402. package/src/scripts/measure_skill_ranker_baseline.ts +212 -0
  403. package/src/scripts/media/lib/adapter-common.sh +211 -1
  404. package/src/scripts/media/lib/adapter-contract.md +74 -6
  405. package/src/scripts/model_rule_injection.ts +907 -0
  406. package/src/scripts/project_thin_rules.ts +45 -2
  407. package/src/scripts/release.ts +66 -860
  408. package/src/scripts/release_drill.ts +59 -3
  409. package/src/scripts/release_env.ts +298 -0
  410. package/src/scripts/release_publication.ts +720 -0
  411. package/src/scripts/roadmap_context.ts +778 -0
  412. package/src/scripts/schemas/fidelity-measurement-sheet.schema.json +169 -0
  413. package/src/scripts/schemas/pack.schema.json +62 -4
  414. package/src/scripts/schemas/rule.schema.json +24 -2
  415. package/src/scripts/schemas/skill.schema.json +41 -2
  416. package/src/scripts/schemas/ui-authority.schema.json +114 -0
  417. package/src/scripts/session_register_hook.ts +29 -3
  418. package/src/scripts/sessions_cli.ts +67 -3
  419. package/src/scripts/skill_tools/score_skill_relevance.ts +36 -53
  420. package/src/scripts/tighten_skill_descriptions.ts +264 -0
  421. package/src/scripts/token_sink_report.ts +239 -0
  422. package/src/scripts/trigger_coverage.ts +122 -3
  423. package/src/scripts/update_counts.ts +7 -3
  424. package/src/scripts/validate_frontmatter.ts +66 -0
  425. package/src/server/schemas/settings.ts +5 -0
  426. package/src/shared/skillRanking.ts +183 -0
@@ -0,0 +1,907 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Rule-injection recall + cost model — the offline half of
4
+ * `road-to-trigger-delivered-rule-bodies` (steps 0.3, 0.4, 0.5 and the scorer
5
+ * step 2.2 falsifies).
6
+ *
7
+ * ── The measured verdict of step 0.3, stated in the header as that step asks ──
8
+ *
9
+ * **NO NEW MATCHER.** The pre-registered comparison required by 0.3 was run
10
+ * against the shipping lexical/BM25 core (`_lib/lexical_index.ts`) over the
11
+ * frozen `tests/eval/routing-matrix/` corpus, on the same labelled positives
12
+ * and near-misses. Exact-trigger matching (`_lib/router_match.ts`) wins on both
13
+ * axes at every retrieval depth tried, so no third matcher is written and the
14
+ * runtime concern imports the shipping semantics directly. Run
15
+ * `--baseline-comparison` to reproduce the table; it is not restated here,
16
+ * because a number copied into a comment is a number nobody re-derives.
17
+ *
18
+ * That outcome was a permitted result of 0.3 rather than a foregone one. The
19
+ * council lock it honours (2026-07-28, recorded at the head of
20
+ * `agents/roadmaps/later/road-to-deferred-rule-retriever.md`) is that rule
21
+ * retrieval is a retrieval problem and the shipping lexical core is the cheaper
22
+ * baseline any new retriever must beat on a PRE-registered measurement, because
23
+ * build-then-measure already cost this repository a whole engine — the code
24
+ * graph, recall 0.365 against disciplined grep's 0.797.
25
+ *
26
+ * ── What this script is, and what it deliberately is not ──
27
+ *
28
+ * It is a pure function of files already in the tree: `dist/router.json`, the
29
+ * projected bodies under `dist/agent-src/rules/`, the labelled corpus, and
30
+ * `internal/bench/pricing.yaml`. It makes **no metered call on any path**,
31
+ * `--selftest` included, so it can run in CI and be re-run for free.
32
+ *
33
+ * It measures **delivery** and **cost**. It does not measure whether a session
34
+ * that receives a body behaves like a session that had it standing — that
35
+ * instrument is closed by ADR-202 (paired judging inadmissible; inter-evaluator
36
+ * Cohen's kappa 0.472 against a registered 0.800 floor) and this script does not
37
+ * reopen it. Nothing printed below is evidence about quality.
38
+ *
39
+ * ── Determinism is a pinned property, not an aspiration ──
40
+ *
41
+ * Corpus files are read in sorted order, matches come back in router
42
+ * declaration order, and every quantile is computed on an explicitly sorted
43
+ * array. Two consecutive runs are byte-identical; `tests/scripts/model_rule_injection.test.ts`
44
+ * asserts it, because a price table that moves between runs cannot license
45
+ * anything.
46
+ */
47
+ import * as fs from 'node:fs';
48
+ import * as path from 'node:path';
49
+ import process from 'node:process';
50
+ import { fileURLToPath } from 'node:url';
51
+ import { parse as parseYaml } from 'yaml';
52
+
53
+ import { LexicalIndex } from './_lib/lexical_index.js';
54
+ import { gpt_tokens } from './_lib/token_count.js';
55
+ import {
56
+ allTierRules,
57
+ kernelIds,
58
+ loadRouter,
59
+ loadRuleBody,
60
+ matchTierRules,
61
+ pathCapableRuleIds,
62
+ selectForInjection,
63
+ triggerlessRuleIds,
64
+ type Router,
65
+ } from './_lib/rule_injection.js';
66
+ import { thin_entry } from './project_thin_rules.js';
67
+ import { CAP_BYTES, buildInjection } from './hooks/rule_inject_hook.js';
68
+
69
+ /**
70
+ * Exact-BPE tokens, used ONLY for the price table and the distribution report.
71
+ *
72
+ * Selection is capped in bytes by `selectForInjection`, shared with the concern
73
+ * — see `_lib/rule_injection.ts`'s header for why the runtime path carries no
74
+ * tokenizer. Here the tokenizer IS the point and nothing is on a hot path.
75
+ */
76
+ function tokensOf(text: string): number {
77
+ return gpt_tokens(text).tokens;
78
+ }
79
+
80
+ const _HERE = fileURLToPath(import.meta.url);
81
+ export const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
82
+ export const DEFAULT_CORPUS = path.join(REPO_ROOT, 'tests', 'eval', 'routing-matrix');
83
+ export const PRICING = path.join(REPO_ROOT, 'internal', 'bench', 'pricing.yaml');
84
+
85
+ /** Turn counts and spawn counts the price table is computed over (0.4). */
86
+ export const TURN_GRID = [10, 50, 200] as const;
87
+ export const SPAWN_GRID = [0, 5, 20] as const;
88
+ /** Retrieval depths the BM25 baseline is scored at (0.3). */
89
+ export const BM25_DEPTHS = [1, 4, 8] as const;
90
+
91
+ // ── corpus ───────────────────────────────────────────────────────────────
92
+
93
+ export interface CorpusCase {
94
+ rule: string;
95
+ prompt: string;
96
+ openFiles: string[] | null;
97
+ label: 'positive' | 'near_miss';
98
+ file: string;
99
+ }
100
+
101
+ function _asCases(
102
+ rule: string,
103
+ file: string,
104
+ raw: unknown,
105
+ label: CorpusCase['label'],
106
+ ): CorpusCase[] {
107
+ if (!Array.isArray(raw)) return [];
108
+ const out: CorpusCase[] = [];
109
+ for (const e of raw) {
110
+ if (e === null || typeof e !== 'object') continue;
111
+ const o = e as Record<string, unknown>;
112
+ const prompt = typeof o['prompt'] === 'string' ? o['prompt'] : null;
113
+ if (prompt === null) continue;
114
+ const of = o['open_files'];
115
+ out.push({
116
+ rule,
117
+ prompt,
118
+ openFiles: Array.isArray(of) ? of.map((x) => String(x)) : null,
119
+ label,
120
+ file,
121
+ });
122
+ }
123
+ return out;
124
+ }
125
+
126
+ /** Load the labelled corpus in sorted file order — the determinism anchor. */
127
+ export function loadCorpus(dir: string): CorpusCase[] {
128
+ const files = fs
129
+ .readdirSync(dir)
130
+ .filter((f) => f.endsWith('.yaml'))
131
+ .sort();
132
+ const out: CorpusCase[] = [];
133
+ for (const f of files) {
134
+ const doc = parseYaml(fs.readFileSync(path.join(dir, f), 'utf8')) as Record<string, unknown>;
135
+ if (doc === null || typeof doc !== 'object') continue;
136
+ const rule = typeof doc['rule'] === 'string' ? doc['rule'] : path.basename(f, '.yaml');
137
+ out.push(..._asCases(rule, f, doc['positives'], 'positive'));
138
+ out.push(..._asCases(rule, f, doc['near_misses'], 'near_miss'));
139
+ }
140
+ return out;
141
+ }
142
+
143
+ // ── quantiles ────────────────────────────────────────────────────────────
144
+
145
+ /** Nearest-rank quantile over an already-copied array. Deterministic. */
146
+ export function quantile(values: number[], q: number): number {
147
+ if (values.length === 0) return 0;
148
+ const s = [...values].sort((a, b) => a - b);
149
+ const idx = Math.min(s.length - 1, Math.max(0, Math.ceil(q * s.length) - 1));
150
+ return s[idx] as number;
151
+ }
152
+
153
+ function mean(values: number[]): number {
154
+ if (values.length === 0) return 0;
155
+ return values.reduce((a, b) => a + b, 0) / values.length;
156
+ }
157
+
158
+ // ── scoring ──────────────────────────────────────────────────────────────
159
+
160
+ export interface Score {
161
+ positives: number;
162
+ hits: number;
163
+ nearMisses: number;
164
+ falseFires: number;
165
+ }
166
+
167
+ export function ratio(hits: number, total: number): string {
168
+ return total === 0 ? '—' : (hits / total).toFixed(3);
169
+ }
170
+
171
+ /** Score the exact-trigger matcher over the corpus. */
172
+ export function scoreExact(
173
+ router: Router,
174
+ cases: CorpusCase[],
175
+ honourOpenFiles: boolean,
176
+ ): Score {
177
+ const s: Score = { positives: 0, hits: 0, nearMisses: 0, falseFires: 0 };
178
+ for (const c of cases) {
179
+ const of = honourOpenFiles ? c.openFiles : null;
180
+ const fired = new Set(matchTierRules(router, c.prompt, of).map((m) => m.id));
181
+ if (c.label === 'positive') {
182
+ s.positives += 1;
183
+ if (fired.has(c.rule)) s.hits += 1;
184
+ } else {
185
+ s.nearMisses += 1;
186
+ if (fired.has(c.rule)) s.falseFires += 1;
187
+ }
188
+ }
189
+ return s;
190
+ }
191
+
192
+ /**
193
+ * Score the shipping BM25 core at a fixed retrieval depth.
194
+ *
195
+ * The index is built over the PROJECTED BODIES of the tier rules — the same
196
+ * text the delivery mode would inject — because that is the only document set a
197
+ * body-delivering retriever could plausibly search. Kernel rules are excluded
198
+ * from the index for the same reason `matchTierRules` excludes them: they are
199
+ * already standing, so retrieving one is never the win.
200
+ */
201
+ export function scoreLexical(router: Router, cases: CorpusCase[], depth: number): Score {
202
+ const kernel = kernelIds(router);
203
+ const docs = allTierRules(router)
204
+ .filter((r) => !kernel.has(r.id))
205
+ .map((r) => ({ id: r.id, text: loadRuleBody(REPO_ROOT, r.id) ?? '' }))
206
+ .filter((d) => d.text !== '');
207
+ const index = new LexicalIndex(docs);
208
+ const s: Score = { positives: 0, hits: 0, nearMisses: 0, falseFires: 0 };
209
+ for (const c of cases) {
210
+ const ranked = index.rank([c.prompt]).filter((r) => r.score > 0).slice(0, depth);
211
+ const fired = new Set(ranked.map((r) => r.id));
212
+ if (c.label === 'positive') {
213
+ s.positives += 1;
214
+ if (fired.has(c.rule)) s.hits += 1;
215
+ } else {
216
+ s.nearMisses += 1;
217
+ if (fired.has(c.rule)) s.falseFires += 1;
218
+ }
219
+ }
220
+ return s;
221
+ }
222
+
223
+ /**
224
+ * Positives whose labelled rule declares a path trigger, carries `open_files`,
225
+ * and is STILL missed with those open files honoured.
226
+ *
227
+ * This is the residual column step 1.2's verify reads: a non-zero value means
228
+ * the file-trigger binding cannot reach a rule the corpus says it should, which
229
+ * is a delivery defect rather than a corpus one.
230
+ */
231
+ export function pathRuleMisses(router: Router, cases: CorpusCase[]): CorpusCase[] {
232
+ const pathCapable = pathCapableRuleIds(router);
233
+ const out: CorpusCase[] = [];
234
+ for (const c of cases) {
235
+ if (c.label !== 'positive') continue;
236
+ if (!pathCapable.has(c.rule)) continue;
237
+ if (c.openFiles === null || c.openFiles.length === 0) continue;
238
+ const fired = new Set(matchTierRules(router, c.prompt, c.openFiles).map((m) => m.id));
239
+ if (!fired.has(c.rule)) out.push(c);
240
+ }
241
+ return out;
242
+ }
243
+
244
+ // ── standing corpora + pricing ───────────────────────────────────────────
245
+
246
+ export interface StandingCorpora {
247
+ eagerTokens: number;
248
+ thinTokens: number;
249
+ triggerlessTokens: number;
250
+ triggerless: string[];
251
+ }
252
+
253
+ /**
254
+ * Token sizes of the two standing shapes, computed from the projected tree.
255
+ *
256
+ * `thin_entry` is imported from the projector rather than re-derived, so the
257
+ * pointer cost priced here is the pointer the projector actually writes.
258
+ */
259
+ export function standingCorpora(router: Router): StandingCorpora {
260
+ const kernel = kernelIds(router);
261
+ const triggerless = new Set(triggerlessRuleIds(router));
262
+ let eager = 0;
263
+ let thin = 0;
264
+ let triggerlessTokens = 0;
265
+ const dir = path.join(REPO_ROOT, 'dist', 'agent-src', 'rules');
266
+ for (const f of fs.readdirSync(dir).filter((x) => x.endsWith('.md')).sort()) {
267
+ const id = path.basename(f, '.md');
268
+ const text = fs.readFileSync(path.join(dir, f), 'utf8');
269
+ const body = tokensOf(text);
270
+ eager += body;
271
+ if (kernel.has(id)) {
272
+ thin += body;
273
+ } else if (triggerless.has(id)) {
274
+ // Cannot be delivered — 1.3 keeps it eager under the thin shapes too.
275
+ thin += body;
276
+ triggerlessTokens += body;
277
+ } else {
278
+ thin += tokensOf(thin_entry(id, text));
279
+ }
280
+ }
281
+ return {
282
+ eagerTokens: eager,
283
+ thinTokens: thin,
284
+ triggerlessTokens,
285
+ triggerless: [...triggerless].sort(),
286
+ };
287
+ }
288
+
289
+ export interface Rates {
290
+ tier: string;
291
+ input: number;
292
+ cacheRead: number;
293
+ cacheWrite: number;
294
+ }
295
+
296
+ /** Read one tier's USD-per-1M rates out of the committed pricing file. */
297
+ export function loadRates(tier = 'sonnet'): Rates {
298
+ const doc = parseYaml(fs.readFileSync(PRICING, 'utf8')) as Record<string, unknown>;
299
+ const models = Array.isArray(doc['models']) ? doc['models'] : [];
300
+ for (const m of models as Array<Record<string, unknown>>) {
301
+ if (String(m['tier']) === tier) {
302
+ return {
303
+ tier,
304
+ input: Number(m['input']),
305
+ cacheRead: Number(m['cache_read']),
306
+ cacheWrite: Number(m['cache_write']),
307
+ };
308
+ }
309
+ }
310
+ throw new Error(`pricing.yaml carries no tier '${tier}'`);
311
+ }
312
+
313
+ /**
314
+ * USD for one session under one delivery shape.
315
+ *
316
+ * THE MODEL, STATED SO IT CAN BE DISAGREED WITH. Standing context is written to
317
+ * cache once and read on every later turn; a spawn re-writes the whole preamble
318
+ * as UNCACHED input, which is the asymmetry that makes the standing corpus cost
319
+ * scale with spawns rather than with turns. Injected bodies are uncached input
320
+ * paid once per rule per session, because step 1.4's seen-set injects a given
321
+ * rule's body at most once. Output tokens are identical across shapes by
322
+ * construction and are therefore excluded rather than modelled — including them
323
+ * would add the same constant to every row and change no comparison.
324
+ */
325
+ export function sessionCostUsd(
326
+ standingTokens: number,
327
+ injectedTokens: number,
328
+ turns: number,
329
+ spawns: number,
330
+ rates: Rates,
331
+ ): number {
332
+ const perM = 1_000_000;
333
+ const cacheWrite = (standingTokens / perM) * rates.cacheWrite;
334
+ const cacheReads = ((standingTokens * Math.max(0, turns - 1)) / perM) * rates.cacheRead;
335
+ const spawnPreambles = ((standingTokens * spawns) / perM) * rates.input;
336
+ const injected = (injectedTokens / perM) * rates.input;
337
+ return cacheWrite + cacheReads + spawnPreambles + injected;
338
+ }
339
+
340
+ /**
341
+ * Injected tokens accumulated over the first `turns` positives, deduplicated by
342
+ * rule — the seen-set of step 1.4, modelled directly rather than assumed away.
343
+ */
344
+ export function injectedOverSession(
345
+ router: Router,
346
+ positives: CorpusCase[],
347
+ turns: number,
348
+ capBytes: number,
349
+ ): number {
350
+ const seen = new Set<string>();
351
+ let total = 0;
352
+ for (const c of positives.slice(0, turns)) {
353
+ const matches = matchTierRules(router, c.prompt, c.openFiles);
354
+ const sel = selectForInjection(REPO_ROOT, matches, capBytes);
355
+ for (const m of sel.selected) {
356
+ if (seen.has(m.id)) continue;
357
+ seen.add(m.id);
358
+ const body = loadRuleBody(REPO_ROOT, m.id);
359
+ total += body === null ? 0 : tokensOf(body);
360
+ }
361
+ }
362
+ return total;
363
+ }
364
+
365
+ // ── reports ──────────────────────────────────────────────────────────────
366
+
367
+ function fmtUsd(n: number): string {
368
+ return n.toFixed(4);
369
+ }
370
+
371
+ export interface ModelReport {
372
+ lines: string[];
373
+ capTokens: number;
374
+ exactHonoured: Score;
375
+ exactIgnored: Score;
376
+ pathMisses: CorpusCase[];
377
+ priceOk: boolean;
378
+ }
379
+
380
+ export function runModel(corpusDir: string, honourOnly: boolean): ModelReport {
381
+ const router = loadRouter(REPO_ROOT);
382
+ const cases = loadCorpus(corpusDir);
383
+ const positives = cases.filter((c) => c.label === 'positive');
384
+ const exactHonoured = scoreExact(router, cases, true);
385
+ const exactIgnored = scoreExact(router, cases, false);
386
+ const pathMisses = pathRuleMisses(router, cases);
387
+
388
+ // Uncapped matched-token distribution → the cap (p90, rounded UP to 500).
389
+ const uncapped: number[] = [];
390
+ for (const c of positives) {
391
+ const matches = matchTierRules(router, c.prompt, c.openFiles);
392
+ let sum = 0;
393
+ for (const m of matches) {
394
+ const body = loadRuleBody(REPO_ROOT, m.id);
395
+ sum += body === null ? 0 : tokensOf(body);
396
+ }
397
+ uncapped.push(sum);
398
+ }
399
+ const p90 = quantile(uncapped, 0.9);
400
+ const capTokens = Math.max(500, Math.ceil(p90 / 500) * 500);
401
+ // The SELECTION cap is the concern's, in bytes — one number, both arms.
402
+ const capBytes = CAP_BYTES;
403
+
404
+ const matchedCounts = positives.map(
405
+ (c) => matchTierRules(router, c.prompt, c.openFiles).length,
406
+ );
407
+ const standing = standingCorpora(router);
408
+ const rates = loadRates('sonnet');
409
+
410
+ const lines: string[] = [];
411
+ const push = (s: string): void => {
412
+ lines.push(s);
413
+ };
414
+
415
+ push(`model_rule_injection · corpus ${path.relative(REPO_ROOT, corpusDir)} · router dist/router.json`);
416
+ push(
417
+ `router: ${kernelIds(router).size} kernel · ${allTierRules(router).length} tier rules · ` +
418
+ `${allTierRules(router).reduce((a, r) => a + r.triggers.length, 0)} triggers`,
419
+ );
420
+ push(
421
+ `corpus: ${new Set(cases.map((c) => c.rule)).size} labelled rules · ` +
422
+ `${exactHonoured.positives} positives · ${exactHonoured.nearMisses} near-misses · ` +
423
+ `${positives.filter((c) => c.openFiles !== null).length} positives carry open_files`,
424
+ );
425
+ push('');
426
+ push('── matched rules per positive prompt (exact-trigger matcher) ──');
427
+ push(
428
+ ` count p50=${quantile(matchedCounts, 0.5)} p90=${quantile(matchedCounts, 0.9)} ` +
429
+ `max=${quantile(matchedCounts, 1)} mean=${mean(matchedCounts).toFixed(2)}`,
430
+ );
431
+ push(
432
+ ` tokens p50=${quantile(uncapped, 0.5)} p90=${p90} p99=${quantile(uncapped, 0.99)} ` +
433
+ `max=${quantile(uncapped, 1)} mean=${Math.round(mean(uncapped))}`,
434
+ );
435
+ push(
436
+ ` per-prompt cap = p90 rounded up to 500 = ${capTokens} tok, applied as ` +
437
+ `${capBytes} B (the concern's CAP_BYTES — one selection, both arms)`,
438
+ );
439
+ push('');
440
+ push('── standing corpora (exact BPE over dist/agent-src/rules) ──');
441
+ push(` eager-all ${standing.eagerTokens} tok`);
442
+ push(
443
+ ` thin/delivery ${standing.thinTokens} tok ` +
444
+ `(kernel bodies + ${standing.triggerless.length} no-trigger residue bodies + pointers)`,
445
+ );
446
+ push(` no-trigger residue: ${standing.triggerless.join(', ')} = ${standing.triggerlessTokens} tok`);
447
+ if (pathMisses.length > 0) {
448
+ push('');
449
+ push('── path-rule misses, listed by rule (residual column of 1.2) ──');
450
+ for (const c of pathMisses) push(` ${c.rule} ${JSON.stringify(c.openFiles)}`);
451
+ }
452
+ push('');
453
+
454
+ // Price grid.
455
+ const shapes: Array<{ name: string; standing: number; injectedFor: (t: number) => number }> = [
456
+ { name: 'eager-all', standing: standing.eagerTokens, injectedFor: () => 0 },
457
+ { name: 'thin-pointers', standing: standing.thinTokens, injectedFor: () => 0 },
458
+ {
459
+ name: 'delivery',
460
+ standing: standing.thinTokens,
461
+ injectedFor: (t) => injectedOverSession(router, positives, t, capBytes),
462
+ },
463
+ ];
464
+ const price = (shapeIdx: number, turns: number, spawns: number): number => {
465
+ const sh = shapes[shapeIdx] as (typeof shapes)[number];
466
+ return sessionCostUsd(sh.standing, sh.injectedFor(turns), turns, spawns, rates);
467
+ };
468
+ const eagerRef = price(0, 50, 5);
469
+ const deliveryRef = price(2, 50, 5);
470
+ const priceOk = deliveryRef < eagerRef;
471
+
472
+ if (honourOnly) {
473
+ push(`path-rule misses: ${pathMisses.length}`);
474
+ return { lines, capTokens, exactHonoured, exactIgnored, pathMisses, priceOk };
475
+ }
476
+
477
+ push('SUMMARY');
478
+ push(
479
+ `corpus: ${exactHonoured.positives} positives · ${exactHonoured.nearMisses} near-misses · ` +
480
+ `cap ${capTokens} tok/prompt`,
481
+ );
482
+ push(
483
+ `recall, open_files ignored: ${ratio(exactIgnored.hits, exactIgnored.positives)} ` +
484
+ `(${exactIgnored.hits}/${exactIgnored.positives})`,
485
+ );
486
+ push(
487
+ `recall, open_files honoured: ${ratio(exactHonoured.hits, exactHonoured.positives)} ` +
488
+ `(${exactHonoured.hits}/${exactHonoured.positives})`,
489
+ );
490
+ push(`path-rule misses: ${pathMisses.length}`);
491
+ push(`false fires: ${exactHonoured.falseFires} of ${exactHonoured.nearMisses} near-miss prompts`);
492
+ push(
493
+ `matched body tokens (exact BPE): p50=${quantile(uncapped, 0.5)} p90=${p90} ` +
494
+ `p99=${quantile(uncapped, 0.99)} max=${quantile(uncapped, 1)}`,
495
+ );
496
+ push(
497
+ `price USD/session · ${rates.tier} in ${rates.input}/cr ${rates.cacheRead}/cw ` +
498
+ `${rates.cacheWrite} per 1M · cells = turns 10/50/200 at 0|5|20 spawns`,
499
+ );
500
+ for (let i = 0; i < shapes.length; i += 1) {
501
+ const cells = TURN_GRID.map((t) =>
502
+ SPAWN_GRID.map((s) => fmtUsd(price(i, t, s))).join('|'),
503
+ ).join(' ');
504
+ push(` ${(shapes[i] as (typeof shapes)[number]).name.padEnd(14)}${cells}`);
505
+ }
506
+ push(
507
+ `endpoint (d) price · delivery ${fmtUsd(deliveryRef)} < eager ${fmtUsd(eagerRef)} ` +
508
+ `at 50 turns x 5 spawns: ${priceOk ? 'PASS' : 'FAIL'}`,
509
+ );
510
+ return { lines, capTokens, exactHonoured, exactIgnored, pathMisses, priceOk };
511
+ }
512
+
513
+ export function runBaselineComparison(corpusDir: string): string[] {
514
+ const router = loadRouter(REPO_ROOT);
515
+ const cases = loadCorpus(corpusDir);
516
+ const exact = scoreExact(router, cases, true);
517
+ const rows: Array<[string, Score, string]> = [
518
+ ['router_match (exact triggers)', exact, 'the shipping matcher, _lib/router_match.ts'],
519
+ ];
520
+ for (const d of BM25_DEPTHS) {
521
+ rows.push([
522
+ `lexical_index BM25 top-${d}`,
523
+ scoreLexical(router, cases, d),
524
+ 'the shipping BM25 core, _lib/lexical_index.ts',
525
+ ]);
526
+ }
527
+ const lines: string[] = [];
528
+ lines.push(
529
+ `model_rule_injection --baseline-comparison · corpus ${path.relative(REPO_ROOT, corpusDir)} · open_files honoured`,
530
+ );
531
+ lines.push(
532
+ 'Pre-registered per the 2026-07-28 council lock: the shipping lexical core is scored',
533
+ );
534
+ lines.push('BEFORE any new matcher is written, on the same labelled positives and near-misses.');
535
+ lines.push('');
536
+ lines.push('MATCHER COMPARISON');
537
+ lines.push(' matcher recall false-fires source');
538
+ for (const [name, s, src] of rows) {
539
+ lines.push(
540
+ ` ${name.padEnd(32)}${ratio(s.hits, s.positives).padEnd(10)}` +
541
+ `${`${s.falseFires}/${s.nearMisses}`.padEnd(14)}${src}`,
542
+ );
543
+ }
544
+ const best = rows.reduce((a, b) =>
545
+ b[1].hits / Math.max(1, b[1].positives) > a[1].hits / Math.max(1, a[1].positives) ? b : a,
546
+ );
547
+ const winner = best[0];
548
+ lines.push('');
549
+ if (winner.startsWith('router_match')) {
550
+ lines.push('WINNER: router_match (exact triggers) — no new matcher.');
551
+ lines.push(
552
+ ' It wins recall AND false fires at every depth tried, so a third matcher would be',
553
+ );
554
+ lines.push(
555
+ ' strictly worse on both axes at strictly higher cost. The runtime concern imports',
556
+ );
557
+ lines.push(' _lib/router_match.ts through _lib/rule_injection.ts; nothing is re-implemented.');
558
+ } else {
559
+ lines.push(`WINNER: ${winner} — a new matcher IS warranted, and the reason is measured:`);
560
+ lines.push(' it beats exact-trigger matching on recall over the frozen corpus. Step 0.3');
561
+ lines.push(' therefore does NOT conclude "no new matcher"; Phase 1 must pin this result');
562
+ lines.push(' into the concern before any body is delivered on the exact matcher alone.');
563
+ }
564
+ return lines;
565
+ }
566
+
567
+ export interface EndpointResult {
568
+ id: 'a-delivery' | 'b-recall' | 'c-false-fire' | 'd-price';
569
+ name: string;
570
+ reading: string;
571
+ bar: string;
572
+ passed: boolean;
573
+ }
574
+
575
+ /**
576
+ * Score the four PRE-REGISTERED endpoints of `internal/bench/thin-inject-PREREG.md`.
577
+ *
578
+ * Each bar is a property, not a number lifted off a prior run — see the PREREG
579
+ * for the derivation of each and for the honest limit on how "pre-registered"
580
+ * this run's registration is.
581
+ */
582
+ export function runEndpoints(corpusDir: string): EndpointResult[] {
583
+ const router = loadRouter(REPO_ROOT);
584
+ const cases = loadCorpus(corpusDir);
585
+ const positives = cases.filter((c) => c.label === 'positive');
586
+
587
+ // (a) Delivery census — the CONCERN's own output against the projection.
588
+ //
589
+ // This calls `buildInjection` from the shipped hook rather than re-reading
590
+ // the projected file twice: comparing a file to itself is a tautology that
591
+ // would pass whatever the concern did to the bytes on the way out. What is
592
+ // asserted is that the text the concern puts in front of the model contains
593
+ // the projected body verbatim, trimmed and nothing else changed.
594
+ let deliveries = 0;
595
+ let unequal = 0;
596
+ for (const c of positives) {
597
+ const injection = buildInjection(REPO_ROOT, c.prompt, c.openFiles, null, new Set());
598
+ if (injection === null) continue;
599
+ for (const id of injection.rules) {
600
+ const projected = loadRuleBody(REPO_ROOT, id);
601
+ if (projected === null) {
602
+ unequal += 1;
603
+ continue;
604
+ }
605
+ if (injection.body.includes(projected.trim())) {
606
+ deliveries += 1;
607
+ } else {
608
+ unequal += 1;
609
+ }
610
+ }
611
+ }
612
+
613
+ // (b) No labelled rule may end with ZERO matched positives.
614
+ const byRule = new Map<string, { hit: number; total: number }>();
615
+ for (const c of positives) {
616
+ const e = byRule.get(c.rule) ?? { hit: 0, total: 0 };
617
+ e.total += 1;
618
+ if (matchTierRules(router, c.prompt, c.openFiles).some((m) => m.id === c.rule)) e.hit += 1;
619
+ byRule.set(c.rule, e);
620
+ }
621
+ const unreachable = [...byRule.entries()].filter(([, v]) => v.hit === 0).map(([k]) => k);
622
+ const partial = [...byRule.entries()]
623
+ .filter(([, v]) => v.hit > 0 && v.hit < v.total)
624
+ .map(([k, v]) => `${k} ${v.hit}/${v.total}`);
625
+
626
+ // (c) A near-miss must never deliver its labelled rule.
627
+ const exact = scoreExact(router, cases, true);
628
+
629
+ // (d) Delivery below eager at 50 turns x 5 spawns.
630
+ const standing = standingCorpora(router);
631
+ const rates = loadRates('sonnet');
632
+ const eagerUsd = sessionCostUsd(standing.eagerTokens, 0, 50, 5, rates);
633
+ const deliveryUsd = sessionCostUsd(
634
+ standing.thinTokens,
635
+ injectedOverSession(router, positives, 50, CAP_BYTES),
636
+ 50,
637
+ 5,
638
+ rates,
639
+ );
640
+
641
+ return [
642
+ {
643
+ id: 'a-delivery',
644
+ name: 'delivery census — injected body byte-equal to the eager projection',
645
+ reading: `${deliveries} deliveries byte-equal, ${unequal} not`,
646
+ bar: 'zero tolerance: unequal == 0',
647
+ passed: unequal === 0 && deliveries > 0,
648
+ },
649
+ {
650
+ id: 'b-recall',
651
+ name: 'per-rule recall floor — no labelled rule left unreachable',
652
+ reading:
653
+ `${byRule.size - unreachable.length}/${byRule.size} rules reachable; ` +
654
+ `unreachable: ${unreachable.length === 0 ? 'none' : unreachable.join(', ')}; ` +
655
+ `partial: ${partial.length === 0 ? 'none' : partial.join(', ')}`,
656
+ bar: 'unreachable == 0 (a rule with zero matched positives is a rule the mode removed)',
657
+ passed: unreachable.length === 0,
658
+ },
659
+ {
660
+ id: 'c-false-fire',
661
+ name: 'false-fire ceiling — a near-miss never delivers its labelled rule',
662
+ reading: `${exact.falseFires} of ${exact.nearMisses} near-miss prompts fired`,
663
+ bar: 'falseFires == 0',
664
+ passed: exact.falseFires === 0,
665
+ },
666
+ {
667
+ id: 'd-price',
668
+ name: 'price — delivery below eager at 50 turns x 5 spawns',
669
+ reading: `delivery ${deliveryUsd.toFixed(4)} USD vs eager ${eagerUsd.toFixed(4)} USD`,
670
+ bar: 'delivery < eager',
671
+ passed: deliveryUsd < eagerUsd,
672
+ },
673
+ ];
674
+ }
675
+
676
+ // ── selftest (step 2.2) ──────────────────────────────────────────────────
677
+
678
+ export interface SelftestCase {
679
+ endpoint: 'a-delivery' | 'b-recall' | 'c-false-fire' | 'matcher-mutation';
680
+ name: string;
681
+ passed: boolean;
682
+ detail: string;
683
+ }
684
+
685
+ /**
686
+ * Falsify the scorer before trusting it.
687
+ *
688
+ * Each case mutates ONE input in a way that MUST turn the endpoint red. A case
689
+ * that stays green means the endpoint is insensitive to the defect it exists to
690
+ * catch, and the endpoint is then not evidence for anything. This is the
691
+ * "a scorer never seen red has unknown sensitivity" discipline applied to the
692
+ * four Phase-2 endpoints.
693
+ */
694
+ export function runSelftest(corpusDir: string): SelftestCase[] {
695
+ const router = loadRouter(REPO_ROOT);
696
+ const cases = loadCorpus(corpusDir);
697
+ const out: SelftestCase[] = [];
698
+
699
+ // (a) delivery census — the endpoint must reject a one-byte mutation of the
700
+ // body it is comparing against. Driven through the concern's real output, so
701
+ // a change that mangled the payload would turn this red.
702
+ const probe = cases.find(
703
+ (c) =>
704
+ c.label === 'positive' &&
705
+ buildInjection(REPO_ROOT, c.prompt, c.openFiles, null, new Set()) !== null,
706
+ );
707
+ const injection =
708
+ probe === undefined
709
+ ? null
710
+ : buildInjection(REPO_ROOT, probe.prompt, probe.openFiles, null, new Set());
711
+ if (injection === null || injection.rules.length === 0) {
712
+ out.push({
713
+ endpoint: 'a-delivery',
714
+ name: 'one-byte mutation rejected',
715
+ passed: false,
716
+ detail: 'no positive in the corpus produces an injection to check',
717
+ });
718
+ } else {
719
+ const id = injection.rules[0] as string;
720
+ const projected = loadRuleBody(REPO_ROOT, id) ?? '';
721
+ const mutated = `${projected.trim().slice(0, -1)}~MUTATED~`;
722
+ out.push({
723
+ endpoint: 'a-delivery',
724
+ name: 'one-byte mutation rejected',
725
+ passed: injection.body.includes(projected.trim()) && !injection.body.includes(mutated),
726
+ detail: `${id}: the concern's payload carries the projected body verbatim and rejects a mutation of it`,
727
+ });
728
+ }
729
+
730
+ // (b) recall — a rule stripped of its triggers must stop firing.
731
+ const withTriggers = allTierRules(router).find((r) => r.triggers.length > 0);
732
+ if (withTriggers === undefined) {
733
+ out.push({
734
+ endpoint: 'b-recall',
735
+ name: 'trigger removal drops recall',
736
+ passed: false,
737
+ detail: 'router carries no triggered tier rule',
738
+ });
739
+ } else {
740
+ const pos = cases.find((c) => c.label === 'positive' && c.rule === withTriggers.id);
741
+ const stripped = JSON.parse(JSON.stringify(router)) as Router;
742
+ for (const tier of ['tier_1', 'tier_2'] as const) {
743
+ const arr = stripped[tier];
744
+ if (!Array.isArray(arr)) continue;
745
+ for (const r of arr as unknown as Array<Record<string, unknown>>) {
746
+ if (String(r['id']) === withTriggers.id) r['triggers'] = [];
747
+ }
748
+ }
749
+ const probe = pos ?? { prompt: withTriggers.triggers.map((t) => Object.values(t)[0]).join(' ') };
750
+ const before = matchTierRules(router, String(probe.prompt), null).some(
751
+ (m) => m.id === withTriggers.id,
752
+ );
753
+ const after = matchTierRules(stripped, String(probe.prompt), null).some(
754
+ (m) => m.id === withTriggers.id,
755
+ );
756
+ out.push({
757
+ endpoint: 'b-recall',
758
+ name: 'trigger removal drops recall',
759
+ passed: before && !after,
760
+ detail: `${withTriggers.id}: fired=${before} before, fired=${after} with triggers removed`,
761
+ });
762
+ }
763
+
764
+ // (c) false-fire ceiling — an injected near-miss that fires must be counted.
765
+ const nm = cases.find((c) => c.label === 'near_miss');
766
+ if (nm === undefined) {
767
+ out.push({
768
+ endpoint: 'c-false-fire',
769
+ name: 'firing near-miss is counted',
770
+ passed: false,
771
+ detail: 'corpus carries no near-miss',
772
+ });
773
+ } else {
774
+ const planted: CorpusCase = {
775
+ ...nm,
776
+ rule: nm.rule,
777
+ prompt: `${nm.prompt} ${allTierRules(router)
778
+ .filter((r) => r.id === nm.rule)
779
+ .flatMap((r) => r.triggers)
780
+ .map((t) => String(Object.values(t)[0]))
781
+ .join(' ')}`,
782
+ };
783
+ const scored = scoreExact(router, [planted], true);
784
+ out.push({
785
+ endpoint: 'c-false-fire',
786
+ name: 'firing near-miss is counted',
787
+ passed: scored.falseFires === 1,
788
+ detail: `planted near-miss for ${nm.rule}: falseFires=${scored.falseFires} (expected 1)`,
789
+ });
790
+ }
791
+
792
+ // matcher mutation — an empty router must score zero recall, never a default pass.
793
+ const empty: Router = { kernel: [], tier_1: [], tier_2: [] };
794
+ const emptyScore = scoreExact(empty, cases, true);
795
+ out.push({
796
+ endpoint: 'matcher-mutation',
797
+ name: 'empty router scores zero recall',
798
+ passed: emptyScore.hits === 0 && emptyScore.positives > 0,
799
+ detail: `hits=${emptyScore.hits} over ${emptyScore.positives} positives`,
800
+ });
801
+
802
+ return out;
803
+ }
804
+
805
+ // ── CLI ──────────────────────────────────────────────────────────────────
806
+
807
+ const USAGE = `usage: model_rule_injection [--corpus DIR] [--baseline-comparison]
808
+ [--honour-open-files]
809
+ [--selftest] [--endpoints] [--json]
810
+
811
+ Offline recall + cost model for trigger-delivered rule bodies. No metered call
812
+ on any path.
813
+ `;
814
+
815
+ export function main(argv: string[] | null = null): number {
816
+ const args = argv ?? process.argv.slice(2);
817
+ let corpus = DEFAULT_CORPUS;
818
+ let baseline = false;
819
+ let honour = false;
820
+ let selftest = false;
821
+ let endpoints = false;
822
+ let json = false;
823
+ for (let i = 0; i < args.length; i += 1) {
824
+ const a = args[i] as string;
825
+ if (a === '--corpus') {
826
+ i += 1;
827
+ corpus = path.resolve(args[i] as string);
828
+ } else if (a === '--baseline-comparison') {
829
+ baseline = true;
830
+ } else if (a === '--honour-open-files') {
831
+ honour = true;
832
+ } else if (a === '--selftest') {
833
+ selftest = true;
834
+ } else if (a === '--endpoints') {
835
+ endpoints = true;
836
+ } else if (a === '--json') {
837
+ json = true;
838
+ } else if (a === '-h' || a === '--help') {
839
+ process.stdout.write(USAGE);
840
+ return 0;
841
+ } else {
842
+ process.stderr.write(`model_rule_injection: unknown argument '${a}'\n${USAGE}`);
843
+ return 2;
844
+ }
845
+ }
846
+
847
+ if (selftest) {
848
+ const results = runSelftest(corpus);
849
+ const failed = results.filter((r) => !r.passed);
850
+ if (json) {
851
+ process.stdout.write(`${JSON.stringify(results, null, 2)}\n`);
852
+ } else {
853
+ process.stdout.write('model_rule_injection --selftest · no metered call on any path\n');
854
+ for (const r of results) {
855
+ process.stdout.write(
856
+ ` ${r.passed ? 'ok ' : 'FAIL'} ${r.endpoint.padEnd(18)}${r.name} — ${r.detail}\n`,
857
+ );
858
+ }
859
+ process.stdout.write(
860
+ `selftest: ${results.length - failed.length}/${results.length} rejecting cases green ` +
861
+ `(one per endpoint a/b/c + a matcher mutation)\n`,
862
+ );
863
+ process.stdout.write(`selftest: ${failed.length === 0 ? 'PASS' : 'FAIL'}\n`);
864
+ }
865
+ return failed.length === 0 ? 0 : 1;
866
+ }
867
+
868
+ if (endpoints) {
869
+ const results = runEndpoints(corpus);
870
+ if (json) {
871
+ process.stdout.write(`${JSON.stringify(results, null, 2)}\n`);
872
+ } else {
873
+ process.stdout.write(
874
+ 'model_rule_injection --endpoints · the four PRE-REGISTERED endpoints of\n' +
875
+ 'internal/bench/thin-inject-PREREG.md · offline, no metered call\n\n',
876
+ );
877
+ for (const r of results) {
878
+ process.stdout.write(` ${r.passed ? 'PASS' : 'FAIL'} (${r.id}) ${r.name}\n`);
879
+ process.stdout.write(` reading: ${r.reading}\n`);
880
+ process.stdout.write(` bar: ${r.bar}\n`);
881
+ }
882
+ const failed = results.filter((r) => !r.passed);
883
+ process.stdout.write(
884
+ `\nendpoints: ${results.length - failed.length}/${results.length} hold\n`,
885
+ );
886
+ process.stdout.write(
887
+ 'This licenses delivery equivalence and cost. It does not measure behavioural\n' +
888
+ 'equivalence; that instrument is closed (ADR-202) and this run does not reopen it.\n',
889
+ );
890
+ }
891
+ return results.every((r) => r.passed) ? 0 : 1;
892
+ }
893
+
894
+ if (baseline) {
895
+ const lines = runBaselineComparison(corpus);
896
+ process.stdout.write(`${lines.join('\n')}\n`);
897
+ return 0;
898
+ }
899
+
900
+ const rep = runModel(corpus, honour);
901
+ process.stdout.write(`${rep.lines.join('\n')}\n`);
902
+ return 0;
903
+ }
904
+
905
+ if (path.resolve(process.argv[1] ?? '') === _HERE) {
906
+ process.exit(main());
907
+ }