@event4u/agent-config 14.12.0 → 14.13.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 (466) 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 +234 -0
  5. package/CONTRIBUTING.md +38 -0
  6. package/CREDITS.md +135 -0
  7. package/NOTICE +13 -0
  8. package/README.md +8 -6
  9. package/dist/agent-src/commands/analyze/inbox.md +109 -2
  10. package/dist/agent-src/commands/feature/dev.md +7 -2
  11. package/dist/agent-src/commands/ghostwriter/write.md +5 -1
  12. package/dist/agent-src/commands/implement-ticket.md +1 -1
  13. package/dist/agent-src/commands/mission/upgrade.md +1 -1
  14. package/dist/agent-src/commands/orchestrate.md +2 -2
  15. package/dist/agent-src/commands/post-as/me.md +5 -1
  16. package/dist/agent-src/commands/pr/merge.md +19 -6
  17. package/dist/agent-src/commands/roadmap/process-full.md +19 -5
  18. package/dist/agent-src/commands/tdd/red.md +12 -2
  19. package/dist/agent-src/commands/work.md +1 -1
  20. package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +40 -0
  21. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +5 -2
  22. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +21 -0
  23. package/dist/agent-src/contexts/execution/orchestration-telemetry.md +1 -0
  24. package/dist/agent-src/contexts/execution/subagent-response-contract.md +40 -0
  25. package/dist/agent-src/contexts/execution/subagent-steering.md +13 -1
  26. package/dist/agent-src/contexts/execution/terminal-states.md +96 -0
  27. package/dist/agent-src/rules/artifact-drafting-protocol.md +1 -1
  28. package/dist/agent-src/rules/broken-access-control.md +4 -0
  29. package/dist/agent-src/rules/code-provenance.md +4 -0
  30. package/dist/agent-src/rules/design-fidelity.md +1 -1
  31. package/dist/agent-src/rules/domain-safety-pii.md +4 -0
  32. package/dist/agent-src/rules/domain-safety-retention.md +4 -0
  33. package/dist/agent-src/rules/engineering-safety-floor.md +2 -0
  34. package/dist/agent-src/rules/external-code-graph-interop.md +14 -12
  35. package/dist/agent-src/rules/improve-before-implement.md +6 -8
  36. package/dist/agent-src/rules/laravel-routing.md +1 -1
  37. package/dist/agent-src/rules/lethal-trifecta-guard.md +4 -0
  38. package/dist/agent-src/rules/secret-vcs-guard.md +2 -0
  39. package/dist/agent-src/rules/security-sensitive-stop.md +2 -0
  40. package/dist/agent-src/rules/source-confidentiality.md +12 -34
  41. package/dist/agent-src/rules/symfony-routing.md +1 -1
  42. package/dist/agent-src/rules/token-efficiency.md +14 -0
  43. package/dist/agent-src/rules/tool-safety.md +4 -0
  44. package/dist/agent-src/rules/untrusted-input-defense.md +4 -0
  45. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +247 -3
  46. package/dist/agent-src/scripts/guarded_baseline.ts +328 -0
  47. package/dist/agent-src/scripts/roadmap_set_step.ts +253 -0
  48. package/dist/agent-src/scripts/stub_queue.ts +82 -0
  49. package/dist/agent-src/scripts/stubs_due.ts +400 -0
  50. package/dist/agent-src/scripts/ticket_bundles.ts +84 -0
  51. package/dist/agent-src/scripts/update_roadmap_progress.ts +8 -4
  52. package/dist/agent-src/skills/brand-asset-generation/SKILL.md +17 -4
  53. package/dist/agent-src/skills/brand-audit/SKILL.md +33 -0
  54. package/dist/agent-src/skills/code-intelligence/SKILL.md +57 -0
  55. package/dist/agent-src/skills/database/SKILL.md +8 -0
  56. package/dist/agent-src/skills/dependency-upgrade/SKILL.md +27 -0
  57. package/dist/agent-src/skills/design-review/SKILL.md +1 -1
  58. package/dist/agent-src/skills/doc-coauthoring/SKILL.md +1 -1
  59. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +17 -1
  60. package/dist/agent-src/skills/feature-planning/SKILL.md +9 -2
  61. package/dist/agent-src/skills/frontend-render-security/SKILL.md +44 -0
  62. package/dist/agent-src/skills/git-workflow/SKILL.md +1 -1
  63. package/dist/agent-src/skills/js-library-packaging/scripts/check_package_surface.ts +38 -4
  64. package/dist/agent-src/skills/judge-artifact-completeness/rubrics/ticket-quality-score.json +2 -2
  65. package/dist/agent-src/skills/laravel/SKILL.md +35 -0
  66. package/dist/agent-src/skills/laravel-migration/SKILL.md +57 -3
  67. package/dist/agent-src/skills/monorepo-workspace/SKILL.md +26 -0
  68. package/dist/agent-src/skills/playbook-authoring/SKILL.md +28 -1
  69. package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +1 -1
  70. package/dist/agent-src/skills/skill-writing/references/procedure.md +5 -5
  71. package/dist/agent-src/skills/sql-writing/SKILL.md +7 -1
  72. package/dist/agent-src/skills/standards-from-config/SKILL.md +27 -4
  73. package/dist/agent-src/skills/supply-chain-intake/SKILL.md +11 -4
  74. package/dist/agent-src/skills/symfony-workflow/SKILL.md +35 -0
  75. package/dist/agent-src/skills/test-case-discovery/SKILL.md +85 -0
  76. package/dist/agent-src/skills/test-driven-development/SKILL.md +33 -9
  77. package/dist/agent-src/skills/testing-anti-patterns/SKILL.md +1 -1
  78. package/dist/agent-src/skills/testing-anti-patterns/process-anti-patterns.md +37 -0
  79. package/dist/agent-src/skills/ui-component-architect/SKILL.md +45 -2
  80. package/dist/agent-src/skills/upstream-contribute/SKILL.md +38 -1
  81. package/dist/agent-src/skills/verify-repair-loop/SKILL.md +9 -4
  82. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  83. package/dist/agent-src/templates/roadmaps.md +97 -3
  84. package/dist/agent-src/templates/scripts/check_memory.ts +258 -17
  85. package/dist/agent-src/templates/scripts/check_memory_proposal.ts +9 -3
  86. package/dist/agent-src/templates/scripts/memory_lookup.ts +86 -0
  87. package/dist/agent-src/templates/scripts/memory_signal.ts +44 -0
  88. package/dist/agent-src/templates/scripts/work_engine/directives/backend/implement.ts +90 -0
  89. package/dist/agent-src/templates/scripts/work_engine/directives/backend/index.ts +11 -0
  90. package/dist/agent-src/templates/scripts/work_engine/directives/backend/test.ts +23 -1
  91. package/dist/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +10 -1
  92. package/dist/agent-src/templates/scripts/work_engine/directives/mixed/stitch.ts +5 -1
  93. package/dist/agent-src/templates/scripts/work_engine/stack/runner.ts +102 -14
  94. package/dist/cli/commands/uiAudit.js +124 -5
  95. package/dist/cli/registry.js +2 -0
  96. package/dist/cli-delegate/{chunk-HYWSRNUX.js → chunk-2DK2VJWG.js} +3 -3
  97. package/dist/cli-delegate/{chunk-JCW7ZLHK.js → chunk-4FS4WRXP.js} +5 -0
  98. package/dist/cli-delegate/{chunk-OMIVVCG2.js → chunk-A4QJ6ENT.js} +24 -0
  99. package/dist/cli-delegate/{chunk-JJA2QEWA.js → chunk-AKJGUQNO.js} +466 -285
  100. package/dist/cli-delegate/{chunk-E46KPN6K.js → chunk-BTWUYDGN.js} +424 -363
  101. package/dist/cli-delegate/{chunk-K4FFODTS.js → chunk-ILMUPLGW.js} +1227 -316
  102. package/dist/cli-delegate/{chunk-FF7WAFMR.js → chunk-KPBWFOPZ.js} +5 -1
  103. package/dist/cli-delegate/cmd_adr_effective.js +102 -0
  104. package/dist/cli-delegate/cmd_conformance.js +42 -10
  105. package/dist/cli-delegate/cmd_doctor.js +3 -3
  106. package/dist/cli-delegate/cmd_export.js +3 -2
  107. package/dist/cli-delegate/cmd_fleet.js +2 -2
  108. package/dist/cli-delegate/cmd_handoff.js +1 -1
  109. package/dist/cli-delegate/cmd_preflight.js +2 -2
  110. package/dist/cli-delegate/cmd_prune.js +3 -2
  111. package/dist/cli-delegate/cmd_settings_get.js +1 -1
  112. package/dist/cli-delegate/cmd_settings_migrate.js +3 -2
  113. package/dist/cli-delegate/cmd_settings_set.js +2 -2
  114. package/dist/cli-delegate/cmd_sync.js +3 -2
  115. package/dist/cli-delegate/cmd_uninstall.js +3 -2
  116. package/dist/cli-delegate/cmd_validate.js +3 -2
  117. package/dist/discovery/deprecation-report.md +1 -1
  118. package/dist/discovery/discovery-manifest.json +57 -57
  119. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  120. package/dist/discovery/discovery-manifest.summary.md +1 -1
  121. package/dist/discovery/dormancy-report.md +1 -1
  122. package/dist/discovery/orphan-report.md +1 -1
  123. package/dist/discovery/packs.json +2 -2
  124. package/dist/discovery/trust-report.md +1 -1
  125. package/dist/discovery/workspaces.json +2 -2
  126. package/dist/hooks/dispatch.js +1836 -712
  127. package/dist/install/claudeRuleRewrite.js +254 -0
  128. package/dist/install/detect.js +29 -16
  129. package/dist/install/detect_php_shape.js +159 -0
  130. package/dist/install/install.mjs +634 -428
  131. package/dist/mcp/awesome-mcp-servers.row.md +1 -1
  132. package/dist/mcp/mcp-cloudflare-catalogue.json +1 -1
  133. package/dist/mcp/registry-manifest.json +3 -3
  134. package/dist/mcp/server.json +3 -3
  135. package/dist/mcp/server.mjs +4818 -2645
  136. package/dist/server/schemas/settings.js +3 -0
  137. package/dist/ui/assets/index-D0jaCRPM.js +29 -0
  138. package/dist/ui/index.html +1 -1
  139. package/docs/MIGRATION.md +1 -1
  140. package/docs/THIRD-PARTY-NOTICES.md +25 -0
  141. package/docs/contracts/provider-lifecycle.md +1 -1
  142. package/docs/contracts/settings-classes.md +10 -4
  143. package/docs/guidelines/agent-infra/5w2h-analysis.md +3 -3
  144. package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +1 -0
  145. package/docs/guidelines/agent-infra/artifact-drafting-protocol-mechanics.md +58 -3
  146. package/docs/guidelines/agent-infra/ask-when-uncertain-demos.md +2 -2
  147. package/docs/guidelines/agent-infra/asking-and-brevity-examples.md +3 -3
  148. package/docs/guidelines/agent-infra/break-glass-usage.md +7 -7
  149. package/docs/guidelines/agent-infra/carve-out-predicates.md +3 -3
  150. package/docs/guidelines/agent-infra/comparison-matrix.md +1 -1
  151. package/docs/guidelines/agent-infra/critical-thinking.md +4 -4
  152. package/docs/guidelines/agent-infra/direct-answers-demos.md +2 -2
  153. package/docs/guidelines/agent-infra/domain-eval-anti-pattern.md +1 -1
  154. package/docs/guidelines/agent-infra/engineering-memory-data-format.md +3 -3
  155. package/docs/guidelines/agent-infra/existence-question-verdicts.md +80 -0
  156. package/docs/guidelines/agent-infra/first-principles.md +2 -2
  157. package/docs/guidelines/agent-infra/framework-neutrality-patterns.md +2 -2
  158. package/docs/guidelines/agent-infra/guarded-baseline.md +39 -0
  159. package/docs/guidelines/agent-infra/inversion-thinking.md +5 -5
  160. package/docs/guidelines/agent-infra/language-and-tone-examples.md +1 -1
  161. package/docs/guidelines/agent-infra/layered-settings.md +10 -10
  162. package/docs/guidelines/agent-infra/memory-access.md +1 -1
  163. package/docs/guidelines/agent-infra/mental-models.md +3 -3
  164. package/docs/guidelines/agent-infra/reuse-verdict-mechanics.md +71 -0
  165. package/docs/guidelines/agent-infra/review-routing-data-format.md +7 -7
  166. package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +13 -7
  167. package/docs/guidelines/agent-infra/role-contracts.md +2 -2
  168. package/docs/guidelines/agent-infra/role-mode-router.md +3 -3
  169. package/docs/guidelines/agent-infra/rule-type-governance.md +2 -2
  170. package/docs/guidelines/agent-infra/scqa-framework.md +5 -5
  171. package/docs/guidelines/agent-infra/self-improvement-pipeline.md +6 -6
  172. package/docs/guidelines/agent-infra/simplicity-and-goal-demos.md +2 -2
  173. package/docs/guidelines/agent-infra/six-hats.md +3 -3
  174. package/docs/guidelines/agent-infra/skill-quality-checklist.md +6 -6
  175. package/docs/guidelines/agent-infra/source-confidentiality-mechanics.md +109 -0
  176. package/docs/guidelines/agent-infra/systems-thinking.md +1 -1
  177. package/docs/guidelines/agent-infra/verify-before-complete-demos.md +2 -2
  178. package/docs/guidelines/augment-portability-patterns.md +6 -4
  179. package/docs/guidelines/component-oriented-and-oop-development.md +3 -2
  180. package/docs/guidelines/cross-role-handoff.md +2 -2
  181. package/docs/guidelines/design-fidelity-mechanics.md +1 -1
  182. package/docs/guidelines/docs/readme-size-and-splitting.md +3 -3
  183. package/docs/guidelines/php/database.md +90 -7
  184. package/docs/guidelines/prompt-templates.md +7 -7
  185. package/llms.txt +2 -2
  186. package/package.json +5 -2
  187. package/src/agent-src/scripts/archive_completed_roadmaps.ts +247 -3
  188. package/src/agent-src/scripts/guarded_baseline.ts +328 -0
  189. package/src/agent-src/scripts/roadmap_set_step.ts +253 -0
  190. package/src/agent-src/scripts/stub_queue.ts +82 -0
  191. package/src/agent-src/scripts/stubs_due.ts +400 -0
  192. package/src/agent-src/scripts/ticket_bundles.ts +84 -0
  193. package/src/agent-src/scripts/update_roadmap_progress.ts +8 -4
  194. package/src/agent-src/templates/scripts/check_memory.ts +258 -17
  195. package/src/agent-src/templates/scripts/check_memory_proposal.ts +9 -3
  196. package/src/agent-src/templates/scripts/memory_lookup.ts +86 -0
  197. package/src/agent-src/templates/scripts/memory_signal.ts +44 -0
  198. package/src/agent-src/templates/scripts/work_engine/directives/backend/implement.ts +90 -0
  199. package/src/agent-src/templates/scripts/work_engine/directives/backend/index.ts +11 -0
  200. package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +23 -1
  201. package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +10 -1
  202. package/src/agent-src/templates/scripts/work_engine/directives/mixed/stitch.ts +5 -1
  203. package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +102 -14
  204. package/src/config/agent-settings.template.yml +32 -3
  205. package/src/config/assurance-capability-registry.json +21 -9
  206. package/src/config/canonical-terms.yml +119 -0
  207. package/src/config/capability-native-outcome-bars.json +81 -0
  208. package/src/config/ci-local-parity.yml +80 -0
  209. package/src/config/drafting-channels.yml +43 -0
  210. package/src/config/estate-count-budget.json +38 -36
  211. package/src/config/evaluator-budgets.json +14 -14
  212. package/src/config/gate-coverage.yml +579 -63
  213. package/src/config/gate-reachability-exemptions.json +21 -0
  214. package/src/config/gate-violation-baselines.json +37 -19
  215. package/src/config/harness-evolution-budget.json +25 -0
  216. package/src/config/lapsed-beta-baseline.json +96 -0
  217. package/src/config/memory-twin-verdicts.yml +129 -0
  218. package/src/config/metric-registry.yml +124 -0
  219. package/src/config/pack-size-budget.json +16 -15
  220. package/src/config/preamble-payload-budget.json +8 -8
  221. package/src/config/profiles/balanced.ini +27 -0
  222. package/src/config/profiles/full.ini +27 -0
  223. package/src/config/profiles/minimal.ini +27 -0
  224. package/src/config/publish-surface.json +9 -2
  225. package/src/config/routing-coverage-seed.json +16 -2
  226. package/src/config/rule-stub-ceilings.json +126 -112
  227. package/src/config/turnaround-budget.json +66 -0
  228. package/src/install/claudeRuleRewrite.ts +288 -0
  229. package/src/install/detect.ts +60 -17
  230. package/src/install/detect_php_shape.ts +191 -0
  231. package/src/scripts/_cli/cmd_doctor.ts +27 -112
  232. package/src/scripts/_dispatch.bash +27 -0
  233. package/src/scripts/_lib/activation_ladder.ts +211 -0
  234. package/src/scripts/_lib/activation_states.ts +88 -0
  235. package/src/scripts/_lib/adherence_detectors.ts +143 -0
  236. package/src/scripts/_lib/arith_claims.ts +181 -0
  237. package/src/scripts/_lib/arm_ranking.ts +62 -0
  238. package/src/scripts/_lib/asset_delivery_ledger.ts +303 -0
  239. package/src/scripts/_lib/audit_field_provenance.ts +54 -0
  240. package/src/scripts/_lib/auto_dispatch.ts +32 -3
  241. package/src/scripts/_lib/bench_ab_size_claim.ts +98 -4
  242. package/src/scripts/_lib/benchmark_cache_fields.ts +158 -0
  243. package/src/scripts/_lib/candidate_proposer.ts +402 -0
  244. package/src/scripts/_lib/candidate_record.ts +601 -0
  245. package/src/scripts/_lib/capture_rate.ts +319 -0
  246. package/src/scripts/_lib/collector_denominator.ts +771 -0
  247. package/src/scripts/_lib/collector_record.ts +340 -0
  248. package/src/scripts/_lib/collector_store.ts +664 -0
  249. package/src/scripts/_lib/collector_supervision.ts +827 -0
  250. package/src/scripts/_lib/concern_estate.ts +77 -0
  251. package/src/scripts/_lib/config_chain.ts +477 -0
  252. package/src/scripts/_lib/config_cost.ts +209 -0
  253. package/src/scripts/_lib/council_settings_block.ts +6 -0
  254. package/src/scripts/_lib/curator_ops.ts +172 -0
  255. package/src/scripts/_lib/delivery_arm_experiment.ts +266 -0
  256. package/src/scripts/_lib/delivery_criticality.ts +126 -0
  257. package/src/scripts/_lib/doctor_runtime_checks.ts +70 -0
  258. package/src/scripts/_lib/empty_cycles.ts +88 -0
  259. package/src/scripts/_lib/eval_publication.ts +354 -0
  260. package/src/scripts/_lib/evaluation_vector.ts +283 -0
  261. package/src/scripts/_lib/evaluator_promotion.ts +225 -0
  262. package/src/scripts/_lib/evidence_basis.ts +118 -0
  263. package/src/scripts/_lib/exec_evidence.ts +8 -0
  264. package/src/scripts/_lib/experience_card.ts +272 -0
  265. package/src/scripts/_lib/experience_report.ts +151 -0
  266. package/src/scripts/_lib/experiment_freeze.ts +112 -0
  267. package/src/scripts/_lib/generated_by.ts +66 -0
  268. package/src/scripts/_lib/harness_evolution_guards.ts +229 -0
  269. package/src/scripts/_lib/hook_effect_probe.ts +141 -0
  270. package/src/scripts/_lib/host_denominator.ts +687 -0
  271. package/src/scripts/_lib/host_projection_reach.ts +157 -0
  272. package/src/scripts/_lib/ignored_blocker.ts +148 -0
  273. package/src/scripts/_lib/install_reach_checks.ts +495 -0
  274. package/src/scripts/_lib/json_duplicate_keys.ts +150 -0
  275. package/src/scripts/_lib/judge_hygiene.ts +228 -0
  276. package/src/scripts/_lib/legacy_boundary_map.ts +228 -0
  277. package/src/scripts/_lib/loop_guards.ts +299 -0
  278. package/src/scripts/_lib/loss_class.ts +129 -0
  279. package/src/scripts/_lib/machine_wake.ts +51 -0
  280. package/src/scripts/_lib/md_prose_lines.ts +177 -0
  281. package/src/scripts/_lib/minimality_tiebreak.ts +168 -0
  282. package/src/scripts/_lib/one_resolver_invariant.ts +449 -0
  283. package/src/scripts/_lib/orchestration_record.ts +253 -5
  284. package/src/scripts/_lib/outcome_envelope.ts +227 -0
  285. package/src/scripts/_lib/outcome_vocabularies.ts +125 -0
  286. package/src/scripts/_lib/paired_stats.ts +245 -0
  287. package/src/scripts/_lib/paired_verdict.ts +226 -0
  288. package/src/scripts/_lib/prefix_stable_surfaces.ts +123 -0
  289. package/src/scripts/_lib/privacy_class.ts +47 -0
  290. package/src/scripts/_lib/regression_neighbourhood.ts +259 -0
  291. package/src/scripts/_lib/repeated_failure.ts +105 -0
  292. package/src/scripts/_lib/repo_root.ts +111 -0
  293. package/src/scripts/_lib/review_skipped_record.ts +37 -0
  294. package/src/scripts/_lib/role_split.ts +338 -0
  295. package/src/scripts/_lib/runtime_journal.ts +1499 -0
  296. package/src/scripts/_lib/runtime_wiring_checks.ts +430 -0
  297. package/src/scripts/_lib/skill_catalogue.ts +11 -1
  298. package/src/scripts/_lib/source_digest.ts +200 -0
  299. package/src/scripts/_lib/source_redact.ts +260 -0
  300. package/src/scripts/_lib/source_shape.ts +502 -0
  301. package/src/scripts/_lib/source_snapshot_dedup.ts +199 -0
  302. package/src/scripts/_lib/strip_comments.d.mts +8 -0
  303. package/src/scripts/_lib/strip_comments.mjs +65 -0
  304. package/src/scripts/_lib/test_red_state.ts +154 -0
  305. package/src/scripts/_lib/trigger_shift.ts +144 -0
  306. package/src/scripts/_lib/untrusted_content.ts +82 -7
  307. package/src/scripts/adoption_report.ts +1 -1
  308. package/src/scripts/adr/evidence_census.ts +1 -1
  309. package/src/scripts/adr/regenerate_index.ts +26 -2
  310. package/src/scripts/adr_cite_check.ts +164 -1
  311. package/src/scripts/ai_council/blind_review.ts +11 -1
  312. package/src/scripts/ai_council/clients.ts +4 -0
  313. package/src/scripts/ai_council/config.ts +3 -0
  314. package/src/scripts/ai_council/consensus.ts +28 -8
  315. package/src/scripts/ai_council/consensus_round.ts +226 -0
  316. package/src/scripts/ai_council/disagreement_signal.ts +338 -0
  317. package/src/scripts/ai_council/inline_findings.ts +340 -0
  318. package/src/scripts/ai_council/judge_position_bias.ts +197 -0
  319. package/src/scripts/ai_council/orchestrator.ts +16 -203
  320. package/src/scripts/ai_council/orchestrator_results.ts +23 -3
  321. package/src/scripts/ai_council/pricing.ts +106 -0
  322. package/src/scripts/ai_council/prompts.ts +74 -7
  323. package/src/scripts/ai_council/provider_leakage_bench.ts +327 -0
  324. package/src/scripts/ai_council/py_format.ts +14 -3
  325. package/src/scripts/ai_council/recouncil_guard.ts +293 -0
  326. package/src/scripts/ai_council/response_record.ts +50 -0
  327. package/src/scripts/ai_council/topology_bench_manifest.ts +860 -0
  328. package/src/scripts/ai_council/topology_vocabulary.ts +175 -0
  329. package/src/scripts/asset_delivery_ledger.ts +157 -0
  330. package/src/scripts/attest_artifact.ts +193 -0
  331. package/src/scripts/audit_auto_rules.ts +2 -1
  332. package/src/scripts/audit_command_surface.ts +1 -1
  333. package/src/scripts/audit_mcp_tools.ts +2 -2
  334. package/src/scripts/audit_skill_overlap_allowlist.json +6 -1
  335. package/src/scripts/audit_user_type_axis.ts +2 -1
  336. package/src/scripts/bench_ab_clone.ts +215 -10
  337. package/src/scripts/bench_ab_integrity.ts +119 -5
  338. package/src/scripts/bench_ab_v2_stats.ts +40 -213
  339. package/src/scripts/build_archive_index.ts +1 -1
  340. package/src/scripts/build_linear_digest.ts +1 -1
  341. package/src/scripts/build_proof.ts +11 -4
  342. package/src/scripts/build_rule_trigger_matrix.ts +1 -1
  343. package/src/scripts/build_source_digests.ts +157 -0
  344. package/src/scripts/cache_realization_report.ts +103 -0
  345. package/src/scripts/check_adr_frontmatter.ts +126 -0
  346. package/src/scripts/check_benchmark_report_fields.ts +207 -0
  347. package/src/scripts/check_beta_review_markers.ts +235 -6
  348. package/src/scripts/check_bundle_path_leakage.ts +34 -1
  349. package/src/scripts/check_claims.ts +576 -6
  350. package/src/scripts/check_composite_arming.ts +35 -0
  351. package/src/scripts/check_concern_admissions.ts +365 -0
  352. package/src/scripts/check_enforcement_coverage.ts +28 -0
  353. package/src/scripts/check_estate_count.ts +142 -1
  354. package/src/scripts/check_gate_reachability.ts +383 -0
  355. package/src/scripts/check_host_loadability.ts +104 -0
  356. package/src/scripts/check_loss_class_declared.ts +308 -0
  357. package/src/scripts/check_md_language.ts +15 -98
  358. package/src/scripts/check_memory_proposal.ts +18 -11
  359. package/src/scripts/check_no_currency_in_cost_surfaces.ts +225 -0
  360. package/src/scripts/check_no_external_sources.ts +281 -6
  361. package/src/scripts/check_pack_conformance_fixture.ts +11 -1
  362. package/src/scripts/check_pack_size.ts +59 -2
  363. package/src/scripts/check_preamble_payload_budget.ts +114 -3
  364. package/src/scripts/check_prefix_stable_mutation.ts +372 -0
  365. package/src/scripts/check_publish_surface.ts +10 -1
  366. package/src/scripts/check_references.ts +24 -1
  367. package/src/scripts/check_rule_evidence_declaration.ts +316 -0
  368. package/src/scripts/check_rule_projection_integrity.ts +14 -4
  369. package/src/scripts/check_score_contract.ts +26 -3
  370. package/src/scripts/check_static_parity.ts +285 -0
  371. package/src/scripts/check_supervision_claim_atomicity.ts +498 -0
  372. package/src/scripts/check_suppression_hygiene.ts +15 -0
  373. package/src/scripts/ci_settle.ts +39 -2
  374. package/src/scripts/code_graph/build.ts +60 -3
  375. package/src/scripts/code_graph/extract.ts +44 -0
  376. package/src/scripts/code_graph/types.ts +24 -0
  377. package/src/scripts/collector_daemon.ts +714 -0
  378. package/src/scripts/config_cost_report.ts +302 -0
  379. package/src/scripts/consumer_matrix.ts +74 -1
  380. package/src/scripts/cost/budget.mjs +10 -3
  381. package/src/scripts/cost/track.mjs +10 -3
  382. package/src/scripts/council_cli.ts +37 -54
  383. package/src/scripts/dispatch_r2_reviewer.ts +46 -5
  384. package/src/scripts/evolution_lab.ts +980 -0
  385. package/src/scripts/external_sources_denylist.json +5 -18
  386. package/src/scripts/extract_audit_patterns.ts +21 -0
  387. package/src/scripts/fold_intake.ts +12 -1
  388. package/src/scripts/gen_discovery_baseline.ts +1 -1
  389. package/src/scripts/generate_capabilities_index.ts +12 -5
  390. package/src/scripts/generate_capability_matrix.ts +3 -3
  391. package/src/scripts/generate_command_flows.ts +2 -2
  392. package/src/scripts/generate_cookbook.ts +2 -2
  393. package/src/scripts/generate_index.ts +8 -1
  394. package/src/scripts/generate_knowledge_index.ts +2 -1
  395. package/src/scripts/generate_ownership_matrix.ts +2 -1
  396. package/src/scripts/generate_pack_manifests.ts +10 -2
  397. package/src/scripts/generate_role_experiences_catalog.ts +2 -2
  398. package/src/scripts/generate_settings_reference.ts +1 -1
  399. package/src/scripts/git_authorization_hook.ts +22 -1
  400. package/src/scripts/grade_target_readiness.ts +48 -5
  401. package/src/scripts/hook_effect_doctor.ts +261 -0
  402. package/src/scripts/hook_manifest.json +1 -1
  403. package/src/scripts/hook_manifest.yaml +98 -11
  404. package/src/scripts/hooks/block_speaking_inbox_dir.ts +271 -0
  405. package/src/scripts/hooks/block_unauthorized_git.ts +18 -0
  406. package/src/scripts/hooks/concern_registry.ts +4 -0
  407. package/src/scripts/hooks/dispatch_hook.ts +32 -0
  408. package/src/scripts/hooks/journal_record_hook.ts +345 -0
  409. package/src/scripts/hooks/prepush_metadata_sources.sh +110 -0
  410. package/src/scripts/hooks/run_continuation_hook.ts +44 -9
  411. package/src/scripts/hooks/shims/install.sh +61 -0
  412. package/src/scripts/hooks/shims/php +101 -0
  413. package/src/scripts/hooks/suggestion_capture_hook.ts +10 -0
  414. package/src/scripts/hot_context_hook.ts +16 -0
  415. package/src/scripts/install-hooks.sh +20 -5
  416. package/src/scripts/install.ts +17 -35
  417. package/src/scripts/inventory_frontmatter.ts +2 -1
  418. package/src/scripts/lint_adr_sweep_routing.ts +12 -0
  419. package/src/scripts/lint_canonical_terms.ts +604 -0
  420. package/src/scripts/lint_composition_review.ts +619 -0
  421. package/src/scripts/lint_consolidation_lineage.ts +666 -0
  422. package/src/scripts/lint_eval_specs.ts +593 -0
  423. package/src/scripts/lint_hook_manifest.ts +19 -0
  424. package/src/scripts/lint_host_portability.ts +373 -0
  425. package/src/scripts/lint_judge_prompt_expectation.ts +27 -1
  426. package/src/scripts/lint_memory_twin_parity.ts +335 -0
  427. package/src/scripts/lint_metric_consumers.ts +355 -0
  428. package/src/scripts/lint_skill_descriptions.ts +18 -1
  429. package/src/scripts/lint_skill_frontmatter_safety.ts +58 -0
  430. package/src/scripts/lint_skill_link_reach.ts +252 -0
  431. package/src/scripts/lint_skill_trigger_corpus.ts +426 -23
  432. package/src/scripts/lint_ticket_buildable.ts +17 -1
  433. package/src/scripts/mcp_server/consumer_tool_catalog.json +1 -1
  434. package/src/scripts/mcp_server/tool_catalog_source.ts +1 -1
  435. package/src/scripts/measure_docs_dead_links.ts +348 -0
  436. package/src/scripts/measure_host_capture.ts +461 -0
  437. package/src/scripts/migration_status.ts +3 -3
  438. package/src/scripts/model_rule_injection.ts +60 -0
  439. package/src/scripts/orchestration_record.ts +14 -0
  440. package/src/scripts/preamble_byte_census.ts +139 -10
  441. package/src/scripts/prepack-check.mjs +4 -1
  442. package/src/scripts/probe_turnaround.ts +384 -0
  443. package/src/scripts/probe_unblocked_ask.ts +279 -0
  444. package/src/scripts/render_benchmark_md.ts +1 -1
  445. package/src/scripts/rule_activation_census.ts +70 -9
  446. package/src/scripts/rule_backlinks.ts +1 -1
  447. package/src/scripts/run_lifecycle_suite.ts +208 -0
  448. package/src/scripts/run_skill_evals.ts +40 -0
  449. package/src/scripts/schemas/delivery-manifest.schema.json +43 -0
  450. package/src/scripts/schemas/drafting-channels.schema.json +43 -0
  451. package/src/scripts/schemas/evals.schema.json +24 -1
  452. package/src/scripts/schemas/rule.schema.json +52 -1
  453. package/src/scripts/schemas/skill.schema.json +17 -2
  454. package/src/scripts/skill_overlap.ts +7 -1
  455. package/src/scripts/skill_tools/run_block_d_eval.ts +8 -1
  456. package/src/scripts/skill_tools/score_skill_relevance.ts +84 -3
  457. package/src/scripts/skill_tools/suggest_skill_for_task.ts +8 -1
  458. package/src/scripts/skill_usage_report.ts +2 -1
  459. package/src/scripts/stubs_due.ts +211 -0
  460. package/src/scripts/sweep_source_surfaces.ts +678 -0
  461. package/src/scripts/sync_pr_branch.ts +3 -3
  462. package/src/scripts/trigger_eval_grandfather.json +202 -222
  463. package/src/scripts/utilization_report.ts +22 -2
  464. package/src/server/schemas/settings.ts +5 -0
  465. package/src/templates/marketing-copy.yml +1 -1
  466. package/dist/ui/assets/index-Cxgy63kz.js +0 -26
@@ -42,6 +42,7 @@ import * as path from 'node:path';
42
42
  import { fileURLToPath, pathToFileURL } from 'node:url';
43
43
 
44
44
  import { runGateCli, runSelfTest, type SelfTestCase } from './_lib/gate_self_test.js';
45
+ import { GateLedger } from './_lib/gate_ledger.js';
45
46
  import { DeadScopeError, reportScanned } from './_lib/scan_scope.js';
46
47
 
47
48
  const _HERE = fileURLToPath(import.meta.url);
@@ -292,6 +293,40 @@ function main(argv?: readonly string[]): number {
292
293
  return 1;
293
294
  }
294
295
  const { readings, malformed } = parseStore(text);
296
+
297
+ // Per-line accounting. `malformed` is a COUNT, and a count cannot say WHICH
298
+ // lines were dropped — a store whose last 40 appends were malformed reads
299
+ // the same as one with 40 fewer readings, which is the "looked at almost
300
+ // nothing" case. The ledger names each dropped line.
301
+ const ledger = new GateLedger('check_composite_arming');
302
+ const storeLines = text.split('\n').map((l, i) => ({ n: i + 1, t: l.trim() })).filter((l) => l.t !== '');
303
+ ledger.plan(storeLines.map((l) => `line:${String(l.n)}`));
304
+ let parsedSeen = 0;
305
+ for (const l of storeLines) {
306
+ let ok = false;
307
+ try {
308
+ const o = JSON.parse(l.t) as { session?: unknown };
309
+ ok = typeof o.session === 'string' && 'composite_ms' in (o as object);
310
+ } catch {
311
+ ok = false;
312
+ }
313
+ if (ok) {
314
+ parsedSeen += 1;
315
+ ledger.complete(`line:${String(l.n)}`);
316
+ } else {
317
+ ledger.skip(`line:${String(l.n)}`, 'precondition_unmet');
318
+ }
319
+ }
320
+ ledger.report();
321
+ /* c8 ignore next 6 */
322
+ if (parsedSeen !== readings.length) {
323
+ process.stderr.write(
324
+ `❌ check_composite_arming: ledger counted ${String(parsedSeen)} parseable line(s) but ` +
325
+ `parseStore returned ${String(readings.length)} reading(s) — the accounting and the ` +
326
+ 'parser disagree, which means one of them is wrong about what was read.\n',
327
+ );
328
+ return 2;
329
+ }
295
330
  if (malformed > 0 && readings.length === 0) {
296
331
  process.stderr.write(
297
332
  `❌ ${path.relative(REPO_ROOT, store)}: ${String(malformed)} malformed line(s) and no usable ` +
@@ -0,0 +1,365 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Every hook concern added from here on carries a recorded answer — or fails.
4
+ *
5
+ * The sibling of `check_skill_admissions.ts`, same shape and same reasons, with
6
+ * one structural difference that changes the mechanism rather than the policy.
7
+ *
8
+ * ## Forward-only, and why the scope is computed differently
9
+ *
10
+ * A skill is a FILE, so its gate reads `git diff --diff-filter=A` and the 299
11
+ * already in the tree are grandfathered by construction. A concern is a KEY
12
+ * INSIDE ONE FILE — `src/scripts/hook_manifest.yaml` — so a file-level diff
13
+ * says only "the manifest changed", which is true of every concern edit and
14
+ * discriminates nothing.
15
+ *
16
+ * The scope here is therefore a SET DIFFERENCE over the parsed ids: the
17
+ * concerns at HEAD minus the concerns at the base ref. Both sides go through
18
+ * the same scoped parser (`_lib/concern_estate.ts`), for the reason that module
19
+ * exists — two readings of one corpus is how a gate comes to compare a number
20
+ * against a differently-derived number.
21
+ *
22
+ * The grandfathering is identical in effect: the 55 already in the tree need no
23
+ * ledger row and cannot be forgotten off one.
24
+ *
25
+ * ## The questions are NOT the skill questions
26
+ *
27
+ * They are derived from `docs/contracts/hook-architecture-v1.md`, because what
28
+ * makes a concern risky is different from what makes a skill risky. A concern
29
+ * binds to a slot, on hosts that may or may not honour a deny, inside a
30
+ * per-event budget — and none of that has a skill analogue.
31
+ *
32
+ * ## A rejected row is first-class
33
+ *
34
+ * `decision: rejected` records a visible "no", and a rejected row naming a
35
+ * concern that EXISTS is itself a failure: it means the refusal was recorded
36
+ * and then overridden without the row being updated, which is worse than no
37
+ * record at all.
38
+ */
39
+
40
+ import * as fs from 'node:fs';
41
+ import * as path from 'node:path';
42
+ import { spawnSync } from 'node:child_process';
43
+
44
+ import { CONCERN_MANIFEST_POSIX, concernIds } from './_lib/concern_estate.js';
45
+
46
+ const REPO_ROOT = process.cwd();
47
+
48
+ export const LEDGER_REL = 'agents/decisions/concern-admissions.jsonl';
49
+
50
+ /**
51
+ * The five answers, each a non-empty sentence.
52
+ *
53
+ * Every one is a question `hook-architecture-v1.md` makes answerable and a
54
+ * reviewer would otherwise have to reconstruct from the manifest by hand.
55
+ */
56
+ export const REQUIRED_ANSWERS = [
57
+ /** Which lifecycle slot it binds — `pre_tool_use`, `stop`, and so on. */
58
+ 'slot',
59
+ /** Which hosts actually BIND it, as opposed to which declare the slot. */
60
+ 'binding_hosts',
61
+ /** What it does where the host ignores a deny — the honest-coverage answer. */
62
+ 'behaviour_where_deny_ignored',
63
+ /** Why an existing concern cannot carry this. */
64
+ 'why_not_extend',
65
+ /** What it costs against the per-(platform,event) budget. */
66
+ 'per_event_budget_impact',
67
+ ] as const;
68
+
69
+ export const DECISIONS = ['admitted', 'rejected'] as const;
70
+ export type Decision = (typeof DECISIONS)[number];
71
+
72
+ export interface AdmissionRow {
73
+ concern: string;
74
+ decision: Decision;
75
+ date: string;
76
+ slot?: string;
77
+ binding_hosts?: string;
78
+ behaviour_where_deny_ignored?: string;
79
+ why_not_extend?: string;
80
+ per_event_budget_impact?: string;
81
+ /** Free-form; only meaningful on a `rejected` row. */
82
+ instead?: string;
83
+ }
84
+
85
+ export interface Finding {
86
+ concern: string;
87
+ kind: 'missing_row' | 'incomplete_row' | 'rejected_but_present' | 'bad_decision' | 'duplicate_row';
88
+ message: string;
89
+ }
90
+
91
+ function isRow(v: unknown): v is AdmissionRow {
92
+ return typeof v === 'object' && v !== null && typeof (v as AdmissionRow).concern === 'string';
93
+ }
94
+
95
+ export function readLedger(root = REPO_ROOT): AdmissionRow[] {
96
+ const p = path.join(root, LEDGER_REL);
97
+ if (!fs.existsSync(p)) {
98
+ // An absent ledger is not an empty corpus: it is a gate with nothing to
99
+ // read, and the forward-only scope means an absent ledger only matters
100
+ // once something has been added.
101
+ return [];
102
+ }
103
+ const out: AdmissionRow[] = [];
104
+ let n = 0;
105
+ for (const line of fs.readFileSync(p, 'utf-8').split('\n')) {
106
+ n += 1;
107
+ const t = line.trim();
108
+ if (t === '') continue;
109
+ let parsed: unknown;
110
+ try {
111
+ parsed = JSON.parse(t);
112
+ } catch {
113
+ throw new Error(
114
+ `${LEDGER_REL}:${String(n)} is not valid JSON. A malformed ledger is a hard ` +
115
+ 'failure, never a skipped line — a gate that silently drops rows admits by accident.',
116
+ );
117
+ }
118
+ if (isRow(parsed)) out.push(parsed);
119
+ }
120
+ return out;
121
+ }
122
+
123
+ /** Concern ids declared at a git ref, or `null` when the manifest is unreadable there. */
124
+ export function concernsAt(root: string, ref: string): Set<string> | null {
125
+ const res = spawnSync('git', ['show', `${ref}:${CONCERN_MANIFEST_POSIX}`], {
126
+ cwd: root,
127
+ encoding: 'utf-8',
128
+ maxBuffer: 32 * 1024 * 1024,
129
+ });
130
+ if (res.status !== 0) return null;
131
+ return new Set(concernIds(res.stdout ?? ''));
132
+ }
133
+
134
+ /** Concern ids in the working tree. */
135
+ export function liveConcerns(root = REPO_ROOT): Set<string> {
136
+ const p = path.join(root, CONCERN_MANIFEST_POSIX);
137
+ if (!fs.existsSync(p)) return new Set();
138
+ return new Set(concernIds(fs.readFileSync(p, 'utf-8')));
139
+ }
140
+
141
+ /**
142
+ * Concerns ADDED since `baseRef` — the forward-only scope.
143
+ *
144
+ * A set difference rather than a file diff, because a concern is a key inside
145
+ * one file. `null` from `concernsAt` means the manifest could not be read at
146
+ * the base, in which case NOTHING is treated as added: a ref that predates the
147
+ * file must not retroactively demand a row for all 55.
148
+ */
149
+ export function addedConcerns(root: string, baseRef: string): string[] {
150
+ const base = concernsAt(root, baseRef);
151
+ if (base === null) return [];
152
+ return [...liveConcerns(root)].filter((id) => !base.has(id)).sort();
153
+ }
154
+
155
+ export function evaluate(root = REPO_ROOT, baseRef: string | null): Finding[] {
156
+ const findings: Finding[] = [];
157
+ const rows = readLedger(root);
158
+ const live = liveConcerns(root);
159
+
160
+ const seen = new Map<string, number>();
161
+ for (const r of rows) {
162
+ seen.set(r.concern, (seen.get(r.concern) ?? 0) + 1);
163
+ if (!(DECISIONS as readonly string[]).includes(r.decision)) {
164
+ findings.push({
165
+ concern: r.concern,
166
+ kind: 'bad_decision',
167
+ message: `decision must be one of ${DECISIONS.join(' | ')}, got ${JSON.stringify(r.decision)}`,
168
+ });
169
+ continue;
170
+ }
171
+ if (r.decision === 'rejected' && live.has(r.concern)) {
172
+ findings.push({
173
+ concern: r.concern,
174
+ kind: 'rejected_but_present',
175
+ message:
176
+ 'the ledger records a REJECTION and the concern exists in the manifest. A refusal that ' +
177
+ 'was later overridden without updating its row is worse than no record at all.',
178
+ });
179
+ }
180
+ }
181
+ for (const [id, n] of seen) {
182
+ if (n > 1) {
183
+ findings.push({
184
+ concern: id,
185
+ kind: 'duplicate_row',
186
+ message: `${String(n)} rows name this concern; one decision per concern, amended in place`,
187
+ });
188
+ }
189
+ }
190
+
191
+ if (baseRef === null) return findings;
192
+
193
+ const admitted = new Map(rows.filter((r) => r.decision === 'admitted').map((r) => [r.concern, r]));
194
+ for (const id of addedConcerns(root, baseRef)) {
195
+ const row = admitted.get(id);
196
+ if (row === undefined) {
197
+ findings.push({
198
+ concern: id,
199
+ kind: 'missing_row',
200
+ message: `added since ${baseRef} with no \`admitted\` row in ${LEDGER_REL}`,
201
+ });
202
+ continue;
203
+ }
204
+ const blank = REQUIRED_ANSWERS.filter((k) => {
205
+ const v = row[k];
206
+ return typeof v !== 'string' || v.trim() === '';
207
+ });
208
+ if (blank.length > 0) {
209
+ findings.push({
210
+ concern: id,
211
+ kind: 'incomplete_row',
212
+ message: `row is missing a real answer for: ${blank.join(', ')}`,
213
+ });
214
+ }
215
+ }
216
+ return findings;
217
+ }
218
+
219
+ // ---------------------------------------------------------------------------
220
+ // CLI
221
+ // ---------------------------------------------------------------------------
222
+
223
+ import { reportScanned } from './_lib/scan_scope.js';
224
+ import { runGateCli, runSelfTest, type SelfTestCase } from './_lib/gate_self_test.js';
225
+ import * as os from 'node:os';
226
+
227
+ function resolveBase(root: string): string | null {
228
+ for (const ref of ['origin/main', 'main']) {
229
+ const r = spawnSync('git', ['rev-parse', '--verify', '--quiet', ref], { cwd: root, encoding: 'utf-8' });
230
+ if (r.status === 0) return ref;
231
+ }
232
+ return null;
233
+ }
234
+
235
+ const MANIFEST = (ids: readonly string[]): string =>
236
+ ['concerns:', ...ids.map((i) => ` ${i}:\n severity: advisory`), 'roles:', ' dev:', ''].join('\n');
237
+
238
+ const ROW = (concern: string, decision: Decision, blank?: string): string =>
239
+ JSON.stringify({
240
+ concern,
241
+ decision,
242
+ date: '2026-08-30',
243
+ ...Object.fromEntries(REQUIRED_ANSWERS.map((k) => [k, k === blank ? '' : `a real answer for ${k}`])),
244
+ });
245
+
246
+ function selfTest(): number {
247
+ const roots: string[] = [];
248
+ const script = path.join('src', 'scripts', 'check_concern_admissions.ts');
249
+
250
+ /** A throwaway repo with a base commit and a working-tree change on top. */
251
+ const fixture = (opts: { base: readonly string[]; head: readonly string[]; ledger?: string }): number => {
252
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ca-selftest-'));
253
+ roots.push(dir);
254
+ const g = (...a: string[]): void => {
255
+ spawnSync('git', a, { cwd: dir, encoding: 'utf-8' });
256
+ };
257
+ const put = (rel: string, body: string): void => {
258
+ fs.mkdirSync(path.join(dir, path.dirname(rel)), { recursive: true });
259
+ fs.writeFileSync(path.join(dir, rel), body, 'utf-8');
260
+ };
261
+ g('init', '-q', '-b', 'main');
262
+ g('config', 'user.email', 'selftest@local');
263
+ g('config', 'user.name', 'selftest');
264
+ put(CONCERN_MANIFEST_POSIX, MANIFEST(opts.base));
265
+ put(LEDGER_REL, '');
266
+ g('add', '-A');
267
+ g('commit', '-q', '-m', 'base');
268
+ put(CONCERN_MANIFEST_POSIX, MANIFEST(opts.head));
269
+ if (opts.ledger !== undefined) put(LEDGER_REL, `${opts.ledger}\n`);
270
+ // repoRoot locates tsx AND resolves the relative script path, so it is
271
+ // THIS checkout; cwd and --root are the fixture.
272
+ return runGateCli(REPO_ROOT, script, ['--base', 'main', '--root', dir], dir);
273
+ };
274
+
275
+ const cases: SelfTestCase[] = [
276
+ {
277
+ name: 'no concern added → accept',
278
+ expect: 'accept',
279
+ run: () => fixture({ base: ['one'], head: ['one'] }),
280
+ },
281
+ {
282
+ name: 'a concern added with no ledger row → reject',
283
+ expect: 'reject',
284
+ run: () => fixture({ base: ['one'], head: ['one', 'two'] }),
285
+ },
286
+ {
287
+ name: 'a concern added WITH a complete admitted row → accept',
288
+ expect: 'accept',
289
+ run: () => fixture({ base: ['one'], head: ['one', 'two'], ledger: ROW('two', 'admitted') }),
290
+ },
291
+ {
292
+ // A row that exists but answers nothing is the shape a gate is
293
+ // easiest to satisfy dishonestly, so it is tested explicitly.
294
+ name: 'an admitted row with a blank answer → reject',
295
+ expect: 'reject',
296
+ run: () =>
297
+ fixture({ base: ['one'], head: ['one', 'two'], ledger: ROW('two', 'admitted', 'why_not_extend') }),
298
+ },
299
+ {
300
+ name: 'a rejected row naming a LIVE concern → reject',
301
+ expect: 'reject',
302
+ run: () => fixture({ base: ['one', 'two'], head: ['one', 'two'], ledger: ROW('two', 'rejected') }),
303
+ },
304
+ {
305
+ // The grandfathering, asserted rather than assumed: pre-existing
306
+ // concerns must never demand a row.
307
+ name: 'the pre-existing set needs no rows → accept',
308
+ expect: 'accept',
309
+ run: () => fixture({ base: ['a', 'b', 'c'], head: ['a', 'b', 'c'] }),
310
+ },
311
+ ];
312
+
313
+ try {
314
+ return runSelfTest({ gate: 'check_concern_admissions', cases, minCases: 5, minRejectCases: 3 });
315
+ } finally {
316
+ for (const d of roots) fs.rmSync(d, { recursive: true, force: true });
317
+ }
318
+ }
319
+
320
+ export function main(argv: string[] = process.argv.slice(2), rootIn = REPO_ROOT): number {
321
+ if (argv.includes('--self-test')) return selfTest();
322
+ const i = argv.indexOf('--base');
323
+ const j = argv.indexOf('--root');
324
+ const root = j >= 0 && argv[j + 1] !== undefined ? path.resolve(argv[j + 1] as string) : rootIn;
325
+ const baseRef = i >= 0 ? (argv[i + 1] as string) : resolveBase(root);
326
+
327
+ const rows = readLedger(root);
328
+ const added = baseRef === null ? [] : addedConcerns(root, baseRef);
329
+ reportScanned({
330
+ gate: 'check_concern_admissions',
331
+ scanned: rows.length + added.length,
332
+ units: 'admission row(s) + added concern(s)',
333
+ roots: [LEDGER_REL, CONCERN_MANIFEST_POSIX],
334
+ // The state this ships in: 55 concerns are grandfathered by the
335
+ // forward-only set-difference scope, so the first row arrives with the
336
+ // first new concern. `readLedger` throws on a MALFORMED ledger, so
337
+ // "moved" and "empty" stay different verdicts.
338
+ allowEmpty:
339
+ 'EMPTY_VALID: no concern has been admitted or refused through this ledger yet — the ' +
340
+ 'pre-existing 55 are grandfathered by the forward-only set-difference scope, and a ' +
341
+ 'malformed ledger is a separate hard error in readLedger()',
342
+ });
343
+
344
+ const findings = evaluate(root, baseRef);
345
+ if (baseRef === null) {
346
+ process.stdout.write(
347
+ ' ⏭️ no base ref resolved — the added-concern scope could not be computed; ' +
348
+ 'ledger consistency was still checked\n',
349
+ );
350
+ }
351
+ if (findings.length === 0) {
352
+ process.stdout.write(
353
+ `✅ check_concern_admissions: ${String(added.length)} concern(s) added since ` +
354
+ `${baseRef ?? '<no base>'}, each carrying a recorded answer.\n`,
355
+ );
356
+ return 0;
357
+ }
358
+ process.stderr.write(`❌ check_concern_admissions: ${String(findings.length)} finding(s):\n`);
359
+ for (const f of findings) process.stderr.write(` 🔴 ${f.concern} [${f.kind}] — ${f.message}\n`);
360
+ return 1;
361
+ }
362
+
363
+ if (process.argv[1] !== undefined && process.argv[1].includes('check_concern_admissions')) {
364
+ process.exit(main());
365
+ }
@@ -852,6 +852,34 @@ function main(argv: string[]): number {
852
852
  }
853
853
  const base = JSON.parse(fs.readFileSync(BASELINE, 'utf-8')).summary as Summary;
854
854
  const regressions: string[] = [];
855
+
856
+ // `undeclared` is the only counter here that is a TRUE shrink-only
857
+ // ratchet, and the asymmetry is worth stating because the two below it
858
+ // are not.
859
+ //
860
+ // A rule with no declaration has no carrier, so it can contribute to
861
+ // neither `frequency_gap` nor `local_only`. DECLARING one truthfully can
862
+ // therefore only RAISE both — the gap and the taskfile-only reach were
863
+ // always there, and the declaration is what made them computable. Both
864
+ // checks compare a number against a baseline taken when the rule was
865
+ // invisible, so an honest declaring change reds them by construction.
866
+ //
867
+ // Measured 2026-08-27, landing 14 declarations from
868
+ // `road-to-undeclared-obligation-disposition` Phase 1.3:
869
+ // `frequency gaps rose: 9 → 14` and `validators fell back to
870
+ // taskfile-only: 0 → 3`. Nothing fell back and no carrier fired less
871
+ // often. Both readings were new visibility.
872
+ //
873
+ // The sanctioned response is the one the failure message already names —
874
+ // regenerate the baseline — and the obligation this comment adds is that
875
+ // a change doing so SAYS WHY, because a regenerated baseline and a
876
+ // hidden regression look identical in a diff.
877
+ if (summary.undeclared > (base.undeclared ?? Number.POSITIVE_INFINITY)) {
878
+ regressions.push(
879
+ `undeclared rules rose: ${base.undeclared} → ${summary.undeclared} ` +
880
+ `(a rule lost its enforced_by, or a new rule landed without one)`,
881
+ );
882
+ }
855
883
  if (summary.blocking < base.blocking) {
856
884
  regressions.push(`blocking coverage fell: ${base.blocking} → ${summary.blocking}`);
857
885
  }
@@ -153,6 +153,7 @@ import { GateLedger } from './_lib/gate_ledger.js';
153
153
  import { resolveBaseRef } from './_lib/ratchet_base_ref.js';
154
154
  import { materialiseSubtree } from './_lib/base_tree.js';
155
155
  import { SKILLS_POSIX, measureSkillEstate } from './_lib/skill_estate.js';
156
+ import { CONCERN_MANIFEST_POSIX, countConcerns } from './_lib/concern_estate.js';
156
157
  import { runGateCli, runSelfTest, type SelfTestCase } from './_lib/gate_self_test.js';
157
158
 
158
159
  const GATE = 'check_estate_count';
@@ -190,6 +191,23 @@ export interface EstateCounts {
190
191
  * "no tokens" — see `tokensExact`.
191
192
  */
192
193
  skill_description_tokens: number;
194
+ /**
195
+ * Hook concerns declared in `src/scripts/hook_manifest.yaml`.
196
+ *
197
+ * A FIFTH corpus on the same machinery, and the same argument that admitted
198
+ * skills: the floor-from-the-base-ref work is the expensive part and should
199
+ * not be written a third time, and a separate ratchet is a separate place to
200
+ * forget.
201
+ *
202
+ * Read with the SCOPED parser in `_lib/concern_estate.ts`, not the
203
+ * whole-file grep the originating roadmap reproduces its series with. That
204
+ * grep over-counts by exactly 16 at every one of its six pins, because
205
+ * `roles:`, `platforms:` and `native_event_aliases:` sit at the same indent.
206
+ * The growth finding survives the correction — the delta is constant — but
207
+ * the absolute figure does not: the axis stands at 55, not 71, and a floor
208
+ * of 71 is one this gate's own parser could never reproduce.
209
+ */
210
+ concern_count: number;
193
211
  }
194
212
 
195
213
  export type MetricName = keyof EstateCounts;
@@ -200,6 +218,7 @@ export const METRICS: readonly MetricName[] = [
200
218
  'open_blockers',
201
219
  'skill_count',
202
220
  'skill_description_tokens',
221
+ 'concern_count',
203
222
  ];
204
223
 
205
224
  export interface EstateBudget {
@@ -303,6 +322,29 @@ function repoRootFrom(start: string): string {
303
322
  }
304
323
  }
305
324
 
325
+ /** File contents, or `null` when it is absent. Absent is a real state here. */
326
+ function readIfPresent(file: string): string | null {
327
+ try {
328
+ return fs.readFileSync(file, 'utf-8');
329
+ } catch {
330
+ return null;
331
+ }
332
+ }
333
+
334
+ /**
335
+ * Concern count at a git ref, or `null` when the manifest is unreadable there.
336
+ *
337
+ * `null` is NOT zero, and the distinction is the whole reason this returns an
338
+ * option. A missing manifest read as 0 makes the floor 0, which makes the
339
+ * ratchet inert on that path while still printing a green line — "a gate that
340
+ * read nothing has not passed", in this repository's own words. The caller
341
+ * turns `null` into a floor-skip, which this gate already treats as a failure.
342
+ */
343
+ function concernCountAt(repoRoot: string, ref: string): number | null {
344
+ const res = git(['show', `${ref}:${CONCERN_MANIFEST_POSIX}`], repoRoot);
345
+ return res.ok ? countConcerns(res.stdout) : null;
346
+ }
347
+
306
348
  function git(args: readonly string[], cwd: string): { ok: boolean; stdout: string } {
307
349
  const res = spawnSync('git', [...args], { cwd, encoding: 'utf-8', maxBuffer: 32 * 1024 * 1024 });
308
350
  return { ok: res.status === 0, stdout: res.stdout ?? '' };
@@ -389,6 +431,7 @@ export function countEstate(repoRoot: string): EstateCounts {
389
431
  // against a proxy one — they differ by more than the growth a ratchet
390
432
  // is trying to catch.
391
433
  skill_description_tokens: skills.skill_description_tokens ?? 0,
434
+ concern_count: countConcerns(readIfPresent(path.join(repoRoot, CONCERN_MANIFEST_POSIX)) ?? ''),
392
435
  };
393
436
  }
394
437
 
@@ -554,6 +597,15 @@ export function evaluate(
554
597
  // property, and unlike a number nobody has to remember to update it.
555
598
  let floor: EstateCounts | null = null;
556
599
  let floorSkipReason: string | null = null;
600
+ /**
601
+ * Does the WORKING TREE carry a manifest at all?
602
+ *
603
+ * The base-ref read only matters when it does. A repository with no manifest
604
+ * on either side has no concern axis to ratchet, and failing there would
605
+ * turn every unrelated fixture red — which is how the first version of this
606
+ * clause was caught.
607
+ */
608
+ const headHasManifest = fs.existsSync(path.join(repoRoot, CONCERN_MANIFEST_POSIX));
557
609
  /** Set when the base ref carries no `src/skills` — the skill metrics drop. */
558
610
  let skillFloorSkipReason: string | null = null;
559
611
  /** Set when either side's tokeniser did not resolve — that ONE metric drops. */
@@ -584,7 +636,14 @@ export function evaluate(
584
636
  if (baseSkills.error !== null || baseSkills.files === 0) {
585
637
  // Roadmap floor stands; the skill floor is stated as
586
638
  // unavailable and its metrics are dropped from the compare.
587
- floor = roadmapFloor;
639
+ const cf = concernCountAt(repoRoot, baseRef);
640
+ floor = { ...roadmapFloor, concern_count: cf ?? 0 };
641
+ if (cf === null && headHasManifest) {
642
+ floorSkipReason =
643
+ `could not read ${CONCERN_MANIFEST_POSIX} at ${baseRef} while HEAD ` +
644
+ 'carries one. A floor of 0 would make the concern ratchet inert while ' +
645
+ 'still printing green, so this fails rather than skips.';
646
+ }
588
647
  skillFloorSkipReason =
589
648
  `could not read ${SKILLS_POSIX} at ${baseRef} — ` +
590
649
  `${baseSkills.error ?? 'no files'}`;
@@ -594,7 +653,23 @@ export function evaluate(
594
653
  ...roadmapFloor,
595
654
  skill_count: s.skill_count,
596
655
  skill_description_tokens: s.skill_description_tokens ?? 0,
656
+ // A SINGLE FILE, so `git show` rather than a third
657
+ // `materialiseSubtree` — stated because the roadmap asks
658
+ // which of the two this uses. Materialising a subtree to
659
+ // reach one known path would spend a temp tree and a
660
+ // recursive ls-tree on a `git cat-file` in disguise.
661
+ //
662
+ // `?? 0` never stands for "unreadable": the branch below
663
+ // turns that into a floor-skip, which fails. This
664
+ // coalesce only satisfies the type.
665
+ concern_count: concernCountAt(repoRoot, baseRef) ?? 0,
597
666
  };
667
+ if (concernCountAt(repoRoot, baseRef) === null && headHasManifest) {
668
+ floorSkipReason =
669
+ `could not read ${CONCERN_MANIFEST_POSIX} at ${baseRef} while HEAD ` +
670
+ 'carries one. A floor of 0 would make the concern ratchet inert while ' +
671
+ 'still printing green, so this fails rather than skips.';
672
+ }
598
673
  if (s.skill_description_tokens === null) {
599
674
  skillTokenSkipReason =
600
675
  `the tokeniser did not resolve at ${baseRef}; an exact reading may ` +
@@ -663,6 +738,14 @@ export function evaluate(
663
738
  // claim path — a recorded reason in the diff — or it fails.
664
739
  skill_count: 0,
665
740
  skill_description_tokens: 0,
741
+ // Step 1.2: NO new allowance key. The concern axis joins `skill_count`
742
+ // in taking the claim path or nothing, for the reason the budget file
743
+ // already records for skills — an allowance reopens the gaming path the
744
+ // dimension exists to close. Eight concerns spread across eight events
745
+ // violate the existing per-event cap zero times, which is exactly why a
746
+ // total-growth ratchet was needed and exactly why it must not carry a
747
+ // per-change freebie.
748
+ concern_count: 0,
666
749
  };
667
750
 
668
751
  /** Metrics dropped from the compare, with the reason, never silently. */
@@ -808,6 +891,64 @@ function selfTest(): number {
808
891
  expect: 'accept',
809
892
  run: () => fixture({ roadmaps: 3, base: 'main', after: () => undefined }),
810
893
  },
894
+ {
895
+ // The concern axis, both directions. Seeded into the BASE tree so
896
+ // the floor is a real reading of the fixture's own manifest rather
897
+ // than this repository's -- the whole point of measuring the floor
898
+ // from the base ref.
899
+ name: 'concern count grows above the base-tree floor → reject',
900
+ expect: 'reject',
901
+ run: () =>
902
+ fixture({
903
+ roadmaps: 3,
904
+ base: 'main',
905
+ before: (dir) =>
906
+ write(dir, CONCERN_MANIFEST_POSIX, 'concerns:\n one:\n severity: advisory\n'),
907
+ after: (dir) =>
908
+ write(
909
+ dir,
910
+ CONCERN_MANIFEST_POSIX,
911
+ 'concerns:\n one:\n severity: advisory\n two:\n severity: advisory\n',
912
+ ),
913
+ }),
914
+ },
915
+ {
916
+ // AC-3's explicit clause, and it corrects the first implementation
917
+ // of this metric: an unreadable manifest at the base ref must FAIL,
918
+ // never read as 0. A floor of 0 makes the ratchet inert on that
919
+ // path while still printing a green line, which is the shape this
920
+ // repository calls "a gate that read nothing has not passed".
921
+ name: 'base ref carries no manifest → reject (a 0 floor is not a floor)',
922
+ expect: 'reject',
923
+ run: () =>
924
+ fixture({
925
+ roadmaps: 3,
926
+ base: 'main',
927
+ after: (dir) =>
928
+ write(dir, CONCERN_MANIFEST_POSIX, 'concerns:\n one:\n severity: advisory\n'),
929
+ }),
930
+ },
931
+ {
932
+ // The half that proves the parser is SCOPED rather than a grep: a
933
+ // top-level map gaining members at the same two-space indent is not
934
+ // concern growth, and the roadmap's own reproduce command would
935
+ // have counted it as such.
936
+ name: 'a non-concern top-level map growing is not concern growth → accept',
937
+ expect: 'accept',
938
+ run: () =>
939
+ fixture({
940
+ roadmaps: 3,
941
+ base: 'main',
942
+ before: (dir) =>
943
+ write(dir, CONCERN_MANIFEST_POSIX, 'concerns:\n one:\n severity: advisory\nroles:\n dev:\n'),
944
+ after: (dir) =>
945
+ write(
946
+ dir,
947
+ CONCERN_MANIFEST_POSIX,
948
+ 'concerns:\n one:\n severity: advisory\nroles:\n dev:\n ops:\n sre:\n',
949
+ ),
950
+ }),
951
+ },
811
952
  {
812
953
  // The floor is now exact, so this case no longer depends on anyone
813
954
  // having typed the right number at the base commit.