@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
@@ -32,17 +32,47 @@
32
32
  * German would be a heuristic in a file called a schema check: it would pass a
33
33
  * German-looking English sentence and fail a short German one, and its
34
34
  * failures would be unactionable. So German is declared — `"language": "de"`
35
- * on the case — and declaration is checked. Forward-only, because zero of the
36
- * 76 existing files carry the field: requiring it everywhere today would red
37
- * 76 files for a field that did not exist when they were written.
35
+ * on the case — and declaration is checked. Forward-only, because the field
36
+ * postdates almost every corpus: 25 of the 100 files in the shipped tree carry
37
+ * it today, so requiring it everywhere would red 75 files for a field that did
38
+ * not exist when they were written. (Both numbers move as corpora land; they
39
+ * are stated as of 2026-08-30 and are illustration, not a threshold — nothing
40
+ * reads them.)
38
41
  *
39
- * ## Forward-only, in two different senses
42
+ * ## Forward-only, in three senses now
40
43
  *
41
44
  * The COUNT discipline (>=3 positives, >=2 near-misses) applies to every file
42
45
  * today: exactly two files fail it, and both are listed as grandfathered by
43
46
  * name rather than by a numeric baseline, so a third failure cannot hide inside
44
47
  * a count. The LANGUAGE discipline applies only to files the diff adds or
45
- * changes.
48
+ * changes. So, since 2026-08-30, does the CASE-CLASS discipline.
49
+ *
50
+ * ## The case class — three semantic classes, declared, never inferred
51
+ *
52
+ * `road-to-governed-harness-evolution` step 2.3 asked for a four-class corpus
53
+ * where this gate had two: the boolean. AI council 2026-08-30, anthropic +
54
+ * openai, 2/2 convergent, re-scoped it to THREE semantic classes on this
55
+ * surface and moved the fourth elsewhere:
56
+ *
57
+ * - `exemplar` — a success case; requires `trigger: true`.
58
+ * - `near-miss` — vocabulary genuinely overlaps a neighbour, and the
59
+ * answer is still no; requires `trigger: false`.
60
+ * - `counterexample` — unrelated, guarding over-triggering rather than a
61
+ * neighbour; requires `trigger: false`.
62
+ *
63
+ * The fourth class the step named — `failure`, a case the routing gets wrong
64
+ * TODAY — is deliberately NOT here. Both seats reached the same reason
65
+ * independently: it is an ORTHOGONAL axis. `exemplar` / `near-miss` /
66
+ * `counterexample` describe intended routing; `failure` describes observed
67
+ * behaviour, and one case can be both. A corpus file is a regression LOCK, so
68
+ * a known-wrong case placed in it is red by construction, and a rule requiring
69
+ * one per file would reward deliberately broken routing.
70
+ *
71
+ * This does not contradict `:20-26` above. That paragraph says the near-miss /
72
+ * unrelated-negative distinction is not machine-DECIDABLE, and it still is not:
73
+ * nothing here detects it. The class is DECLARED by the author and the
74
+ * declaration is validated — precisely the mechanism the German requirement
75
+ * already uses two paragraphs up, for the same reason.
46
76
  *
47
77
  * Exit codes: 0 = clean · 1 = a discipline violation · 2 = dead scope.
48
78
  */
@@ -54,12 +84,33 @@ import { fileURLToPath } from 'node:url';
54
84
 
55
85
  import { GateLedger } from './_lib/gate_ledger.js';
56
86
  import { type SelfTestCase, runGateCli, runSelfTest } from './_lib/gate_self_test.js';
87
+ import { resolveBaseRef } from './_lib/ratchet_base_ref.js';
57
88
  import { DeadScopeError, reportScanned } from './_lib/scan_scope.js';
58
89
 
59
90
  const HERE = fileURLToPath(import.meta.url);
60
91
  const REAL_REPO_ROOT = path.resolve(path.dirname(HERE), '..', '..');
61
92
  const REPO_ROOT = process.env['LINT_SKILL_TRIGGER_CORPUS_ROOT'] ?? REAL_REPO_ROOT;
62
93
 
94
+ /**
95
+ * Treat every scanned unit as diff-touched, so the forward-only disciplines
96
+ * apply to all of them.
97
+ *
98
+ * This can only ever WIDEN the checked set — there is no direction in which it
99
+ * suppresses a finding — which is what makes it safe to honour from the
100
+ * environment. `--self-test` needs it because its fixtures live in a temporary
101
+ * directory that is not a git repository, so `changedUnits` finds nothing
102
+ * there and the class rules would be silently unreachable: a self-test that
103
+ * cannot reach the rule it claims to prove is the exact no-op this harness
104
+ * exists to catch.
105
+ */
106
+ function forwardAll(): boolean {
107
+ // Read per CALL, not at module load. `selfTest` spawns a subprocess so a
108
+ // module-level const worked there by accident; an in-process caller — a
109
+ // unit test, or any future embedder — could not reach the flag at all,
110
+ // which made the widening path itself untestable without a spawn.
111
+ return process.env['LINT_SKILL_TRIGGER_CORPUS_FORWARD_ALL'] === '1';
112
+ }
113
+
63
114
  export const MIN_POSITIVES = 3;
64
115
  export const MIN_NEAR_MISSES = 2;
65
116
 
@@ -71,6 +122,22 @@ export const MIN_NEAR_MISSES = 2;
71
122
  */
72
123
  export const GRANDFATHERED = new Set(['brand-asset-generation', 'estimate-ticket']);
73
124
 
125
+ /**
126
+ * The closed case-class vocabulary, and the polarity each class requires.
127
+ *
128
+ * Polarity is part of the vocabulary rather than a separate convention: a
129
+ * `near-miss` carrying `trigger: true` is not a near miss, it is a positive
130
+ * that was mislabelled, and a class system that cannot say so buys nothing
131
+ * over a free-text tag.
132
+ */
133
+ export const CASE_CLASS_POLARITY: Readonly<Record<string, boolean>> = {
134
+ exemplar: true,
135
+ 'near-miss': false,
136
+ counterexample: false,
137
+ };
138
+
139
+ export const CASE_CLASSES: readonly string[] = Object.keys(CASE_CLASS_POLARITY);
140
+
74
141
  export interface CorpusStats {
75
142
  unit: string;
76
143
  positives: number;
@@ -78,11 +145,33 @@ export interface CorpusStats {
78
145
  germanPositives: number;
79
146
  /** True when at least one case declares a language at all. */
80
147
  declaresLanguage: boolean;
148
+ /** Cases in the `queries` shape that declare no `class` at all. */
149
+ unclassified: number;
150
+ /** Declared class values outside the closed vocabulary, as written. */
151
+ badClassValues: string[];
152
+ /** Cases whose declared class contradicts their `trigger` boolean. */
153
+ classPolarityErrors: string[];
154
+ /** Which vocabulary members appear at least once. */
155
+ classesPresent: string[];
156
+ /**
157
+ * True when the file uses the legacy `should_trigger` / `should_not_trigger`
158
+ * arrays, whose entries are bare strings and cannot carry a class.
159
+ */
160
+ legacyShape: boolean;
81
161
  }
82
162
 
83
163
  export interface Violation {
84
164
  unit: string;
85
- rule: 'positives' | 'near-misses' | 'german' | 'malformed';
165
+ rule:
166
+ | 'positives'
167
+ | 'near-misses'
168
+ | 'german'
169
+ | 'malformed'
170
+ | 'class-missing'
171
+ | 'class-vocab'
172
+ | 'class-polarity'
173
+ | 'class-coverage'
174
+ | 'class-shape';
86
175
  message: string;
87
176
  }
88
177
 
@@ -90,6 +179,7 @@ interface RawCase {
90
179
  q?: string;
91
180
  trigger?: boolean;
92
181
  language?: string;
182
+ class?: unknown;
93
183
  }
94
184
 
95
185
  /** Read one corpus file into counts, across both shapes that exist. */
@@ -104,16 +194,50 @@ export function statsFor(unit: string, file: string): CorpusStats {
104
194
  let nearMisses = queries.filter((q) => q.trigger === false).length;
105
195
  positives += (doc.should_trigger ?? []).length;
106
196
  nearMisses += (doc.should_not_trigger ?? []).length;
197
+
198
+ const badClassValues: string[] = [];
199
+ const classPolarityErrors: string[] = [];
200
+ const present = new Set<string>();
201
+ let unclassified = 0;
202
+ for (const q of queries) {
203
+ const raw = q.class;
204
+ if (raw === undefined || raw === null || raw === '') {
205
+ unclassified += 1;
206
+ continue;
207
+ }
208
+ if (typeof raw !== 'string' || !(raw in CASE_CLASS_POLARITY)) {
209
+ badClassValues.push(String(raw));
210
+ continue;
211
+ }
212
+ present.add(raw);
213
+ if (q.trigger !== CASE_CLASS_POLARITY[raw]) {
214
+ classPolarityErrors.push(`${raw} on \`trigger: ${String(q.trigger)}\``);
215
+ }
216
+ }
217
+
107
218
  return {
108
219
  unit,
109
220
  positives,
110
221
  nearMisses,
111
222
  germanPositives: queries.filter((q) => q.trigger === true && q.language === 'de').length,
112
223
  declaresLanguage: queries.some((q) => typeof q.language === 'string'),
224
+ unclassified,
225
+ badClassValues,
226
+ classPolarityErrors,
227
+ classesPresent: [...present].sort(),
228
+ legacyShape:
229
+ (doc.should_trigger ?? []).length > 0 || (doc.should_not_trigger ?? []).length > 0,
113
230
  };
114
231
  }
115
232
 
116
- export function judge(s: CorpusStats, requireLanguage: boolean): Violation[] {
233
+ /**
234
+ * `forward` is the diff-touched flag: true when this corpus file was added or
235
+ * changed against the base ref. The COUNT discipline ignores it; the LANGUAGE
236
+ * and CASE-CLASS disciplines are gated on it, so an untouched file authored
237
+ * before either existed is not red for a field that did not exist when it was
238
+ * written. Migration is by touch: the file that changes migrates.
239
+ */
240
+ export function judge(s: CorpusStats, forward: boolean): Violation[] {
117
241
  const out: Violation[] = [];
118
242
  if (GRANDFATHERED.has(s.unit)) return out;
119
243
  if (s.positives < MIN_POSITIVES) {
@@ -130,7 +254,7 @@ export function judge(s: CorpusStats, requireLanguage: boolean): Violation[] {
130
254
  message: `${String(s.nearMisses)} near-miss(es), the discipline asks for ${String(MIN_NEAR_MISSES)}`,
131
255
  });
132
256
  }
133
- if (requireLanguage && s.germanPositives < 1) {
257
+ if (forward && s.germanPositives < 1) {
134
258
  out.push({
135
259
  unit: s.unit,
136
260
  rule: 'german',
@@ -139,31 +263,136 @@ export function judge(s: CorpusStats, requireLanguage: boolean): Violation[] {
139
263
  'a detector would pass German-looking English and fail short German',
140
264
  });
141
265
  }
266
+ if (forward) out.push(...judgeClasses(s));
267
+ return out;
268
+ }
269
+
270
+ /**
271
+ * The case-class rules, all forward-only. Split out so the findings read as one
272
+ * discipline rather than as unrelated additions to `judge`.
273
+ *
274
+ * **`class-shape` cannot fire on the tree as it stands, and that is stated
275
+ * rather than left to be discovered** (completion review, 2026-08-30). It fires
276
+ * on a legacy-shaped corpus; the only two legacy-shaped files in the tree are
277
+ * exactly the two `GRANDFATHERED` units, and `judge` returns before reaching
278
+ * here for those. So it is a FORWARD guard — it catches a legacy-shaped file
279
+ * added from now on, or a grandfathered one promoted — not a rule with live
280
+ * coverage. It is exercised by synthetic fixtures in `--self-test`, so it is
281
+ * tested; what it is not is currently reachable from real data. Counting it
282
+ * among the rules this gate enforces TODAY would inflate the coverage claim,
283
+ * which is the failure this whole file exists to prevent one level down.
284
+ */
285
+ function judgeClasses(s: CorpusStats): Violation[] {
286
+ const out: Violation[] = [];
287
+ const vocab = CASE_CLASSES.join(' | ');
288
+ if (s.legacyShape) {
289
+ out.push({
290
+ unit: s.unit,
291
+ rule: 'class-shape',
292
+ message:
293
+ 'the `should_trigger` / `should_not_trigger` arrays hold bare strings and ' +
294
+ 'cannot carry a case class — migrate this file to the `queries` shape',
295
+ });
296
+ return out;
297
+ }
298
+ if (s.unclassified > 0) {
299
+ out.push({
300
+ unit: s.unit,
301
+ rule: 'class-missing',
302
+ message:
303
+ `${String(s.unclassified)} case(s) declare no \`class\` (${vocab}). ` +
304
+ 'Declared, never inferred — the near-miss / counterexample split is not ' +
305
+ 'machine-decidable, so the author states it and the gate checks the statement',
306
+ });
307
+ }
308
+ if (s.badClassValues.length > 0) {
309
+ out.push({
310
+ unit: s.unit,
311
+ rule: 'class-vocab',
312
+ message: `class outside the closed vocabulary (${vocab}): ${s.badClassValues.sort().join(', ')}`,
313
+ });
314
+ }
315
+ if (s.classPolarityErrors.length > 0) {
316
+ out.push({
317
+ unit: s.unit,
318
+ rule: 'class-polarity',
319
+ message:
320
+ 'class contradicts its own `trigger` boolean: ' +
321
+ `${s.classPolarityErrors.sort().join(', ')}. \`exemplar\` is a positive; ` +
322
+ '`near-miss` and `counterexample` are negatives',
323
+ });
324
+ }
325
+ const missing = CASE_CLASSES.filter((c) => !s.classesPresent.includes(c));
326
+ if (missing.length > 0) {
327
+ out.push({
328
+ unit: s.unit,
329
+ rule: 'class-coverage',
330
+ message:
331
+ `no case carries ${missing.join(' or ')}. A corpus of positives and ` +
332
+ 'near-misses alone cannot show whether the skill over-triggers on ' +
333
+ 'unrelated input, which is what a counterexample is for',
334
+ });
335
+ }
142
336
  return out;
143
337
  }
144
338
 
339
+ /**
340
+ * What the diff scope actually is — three states, deliberately not collapsed.
341
+ *
342
+ * The previous shape returned a bare `Set` and an empty one meant BOTH "the
343
+ * diff touched no corpus file" and "git could not answer". Those are opposite
344
+ * facts: the first is a clean tree, the second is a gate that cannot know what
345
+ * changed. Collapsing them made every forward-only rule a silent no-op wherever
346
+ * the base ref does not resolve — a shallow clone, a fork remote, a tarball, a
347
+ * worktree that never fetched — while `main()` still printed its success line
348
+ * asserting the discipline had run. A gate that reports green on a scope it
349
+ * could not read is the exact failure `DeadScopeError` exists for, applied one
350
+ * level up from the population to the DIFF.
351
+ */
352
+ export type ChangedScope =
353
+ | { kind: 'diff'; units: Set<string> }
354
+ | { kind: 'no-repo' }
355
+ | { kind: 'base-unresolvable'; base: string };
356
+
357
+ function gitOk(root: string, args: readonly string[]): boolean {
358
+ try {
359
+ execFileSync('git', [...args], {
360
+ cwd: root,
361
+ encoding: 'utf-8',
362
+ // Silenced deliberately: outside a repo git prints its whole usage
363
+ // text to stderr, and a gate that dumps a manual page on a path it
364
+ // already handles is noise a reader has to learn to ignore.
365
+ stdio: ['ignore', 'ignore', 'ignore'],
366
+ });
367
+ return true;
368
+ } catch {
369
+ return false;
370
+ }
371
+ }
372
+
145
373
  /** Corpus files the diff added or modified, relative to a base ref. */
146
- export function changedUnits(root: string, base: string): Set<string> {
374
+ export function changedUnits(root: string, base: string): ChangedScope {
375
+ if (!gitOk(root, ['rev-parse', '--git-dir'])) return { kind: 'no-repo' };
376
+ if (!gitOk(root, ['rev-parse', '--verify', '--quiet', base])) {
377
+ return { kind: 'base-unresolvable', base };
378
+ }
147
379
  let out = '';
148
380
  try {
149
381
  out = execFileSync('git', ['diff', '--name-only', `${base}...HEAD`], {
150
382
  cwd: root,
151
383
  encoding: 'utf-8',
152
- // Silenced deliberately: outside a repo (a self-test fixture, a
153
- // tarball install) git prints its whole usage text to stderr, and
154
- // a gate that dumps a manual page on a path it already handles is
155
- // noise a reader has to learn to ignore.
156
384
  stdio: ['ignore', 'pipe', 'ignore'],
157
385
  });
158
386
  } catch {
159
- return new Set();
387
+ // The ref resolved a line ago, so a failure here is not "no diff".
388
+ return { kind: 'base-unresolvable', base };
160
389
  }
161
390
  const units = new Set<string>();
162
391
  for (const line of out.split('\n')) {
163
392
  const m = /^src\/skills\/([^/]+)\/evals\/triggers\.json$/.exec(line.trim());
164
393
  if (m?.[1] !== undefined) units.add(m[1]);
165
394
  }
166
- return units;
395
+ return { kind: 'diff', units };
167
396
  }
168
397
 
169
398
  export interface Result {
@@ -172,7 +401,7 @@ export interface Result {
172
401
  ledger: GateLedger;
173
402
  }
174
403
 
175
- export function evaluate(root = REPO_ROOT, base = 'origin/main'): Result {
404
+ export function evaluate(root = REPO_ROOT, base?: string): Result {
176
405
  const dir = path.join(root, 'src', 'skills');
177
406
  let entries: fs.Dirent[];
178
407
  try {
@@ -183,7 +412,37 @@ export function evaluate(root = REPO_ROOT, base = 'origin/main'): Result {
183
412
  'src/skills is unreadable — a corpus gate with no corpus passes every tree.',
184
413
  );
185
414
  }
186
- const changed = changedUnits(root, base);
415
+ // `origin/main` is exactly the ref that is NOT available where this matters:
416
+ // actions/checkout does a shallow PR-merge fetch, so a PR build has no
417
+ // remote-tracking main at all. Hardcoding it made the first version of this
418
+ // guard a hard CI failure on every run — the opposite of the silent no-op it
419
+ // replaced, and no better. `resolveBaseRef` is this repository's existing
420
+ // answer to the same problem (RATCHET_BASE_REF, then GITHUB_BASE_REF, then
421
+ // the merge commit's first parent on Actions, then origin/main), so the gate
422
+ // uses it rather than inventing a second ladder. An explicit `base` still
423
+ // wins, which is what the tests pass.
424
+ const resolved = base ?? resolveBaseRef(root);
425
+ const scope: ChangedScope =
426
+ resolved === null ? { kind: 'base-unresolvable', base: '(none resolved)' } : changedUnits(root, resolved);
427
+ // FORWARD_ALL widens to every unit, so it needs no diff and is checked
428
+ // first — that is what keeps `--self-test`, which runs in a temporary
429
+ // non-repo directory, reachable.
430
+ const wideMode = forwardAll();
431
+ if (!wideMode && scope.kind !== 'diff') {
432
+ throw new DeadScopeError(
433
+ 'lint_skill_trigger_corpus',
434
+ scope.kind === 'no-repo'
435
+ ? 'not a git repository, so the diff scope cannot be read and every ' +
436
+ 'forward-only rule would be unreachable while this gate printed green. ' +
437
+ 'Run it inside the repo, or set LINT_SKILL_TRIGGER_CORPUS_FORWARD_ALL=1 ' +
438
+ 'to check every unit instead.'
439
+ : `base ref ${scope.base} does not resolve, so the diff scope cannot be ` +
440
+ 'read and every forward-only rule would be unreachable while this gate ' +
441
+ 'printed green. Fetch it (`git fetch origin main`) or pass a base that ' +
442
+ 'exists; LINT_SKILL_TRIGGER_CORPUS_FORWARD_ALL=1 checks every unit instead.',
443
+ );
444
+ }
445
+ const changed = scope.kind === 'diff' ? scope.units : new Set<string>();
187
446
  const ledger = new GateLedger('lint_skill_trigger_corpus');
188
447
  const violations: Violation[] = [];
189
448
  let scanned = 0;
@@ -195,7 +454,7 @@ export function evaluate(root = REPO_ROOT, base = 'origin/main'): Result {
195
454
  ledger.plan(e.name);
196
455
  let own: Violation[];
197
456
  try {
198
- own = judge(statsFor(e.name, file), changed.has(e.name));
457
+ own = judge(statsFor(e.name, file), wideMode || changed.has(e.name));
199
458
  } catch {
200
459
  own = [{ unit: e.name, rule: 'malformed', message: 'is not parseable JSON' }];
201
460
  }
@@ -206,7 +465,7 @@ export function evaluate(root = REPO_ROOT, base = 'origin/main'): Result {
206
465
  if (scanned === 0) {
207
466
  throw new DeadScopeError(
208
467
  'lint_skill_trigger_corpus',
209
- 'zero corpus files found under src/skills/*/evals/triggers.json — 76 exist ' +
468
+ 'zero corpus files found under src/skills/*/evals/triggers.json — 100 exist ' +
210
469
  'in the shipped tree, so an empty scan is a moved root, not a clean one.',
211
470
  );
212
471
  }
@@ -221,14 +480,43 @@ export function selfTest(): number {
221
480
  const d = path.join(root, 'src', 'skills', unit, 'evals');
222
481
  fs.mkdirSync(d, { recursive: true });
223
482
  fs.writeFileSync(path.join(d, 'triggers.json'), JSON.stringify(body));
483
+ // A REAL repo with a resolvable `origin/main`, not a bare directory.
484
+ //
485
+ // The non-forward arms below assert that a corpus is accepted when it
486
+ // is NOT diff-touched, and they used to get that for free: outside a
487
+ // repo `changedUnits` returned an empty set, which the gate could not
488
+ // distinguish from a clean diff. That conflation was the finding this
489
+ // branch fixed — an unreadable diff now REFUSES — so the fixtures have
490
+ // to supply the condition they were only ever simulating. Committing
491
+ // the file and pointing `origin/main` at that same commit makes the
492
+ // diff genuinely empty, which is what those arms mean.
493
+ const git = (...args: string[]): void => {
494
+ execFileSync('git', args, {
495
+ cwd: root,
496
+ stdio: ['ignore', 'ignore', 'ignore'],
497
+ env: {
498
+ ...process.env,
499
+ GIT_AUTHOR_NAME: 'selftest',
500
+ GIT_AUTHOR_EMAIL: 'selftest@invalid',
501
+ GIT_COMMITTER_NAME: 'selftest',
502
+ GIT_COMMITTER_EMAIL: 'selftest@invalid',
503
+ },
504
+ });
505
+ };
506
+ git('init', '-q');
507
+ git('add', '-A');
508
+ git('commit', '-q', '-m', 'fixture');
509
+ git('update-ref', 'refs/remotes/origin/main', 'HEAD');
224
510
  return root;
225
511
  };
226
- const run = (root: string): number => {
512
+ const run = (root: string, forwardAll = false): number => {
227
513
  process.env['LINT_SKILL_TRIGGER_CORPUS_ROOT'] = root;
514
+ if (forwardAll) process.env['LINT_SKILL_TRIGGER_CORPUS_FORWARD_ALL'] = '1';
228
515
  try {
229
516
  return runGateCli(REAL_REPO_ROOT, 'src/scripts/lint_skill_trigger_corpus.ts', [], root);
230
517
  } finally {
231
518
  delete process.env['LINT_SKILL_TRIGGER_CORPUS_ROOT'];
519
+ delete process.env['LINT_SKILL_TRIGGER_CORPUS_FORWARD_ALL'];
232
520
  }
233
521
  };
234
522
  const good = {
@@ -240,6 +528,19 @@ export function selfTest(): number {
240
528
  { q: 'e', trigger: false },
241
529
  ],
242
530
  };
531
+ /** The same corpus, fully classified and German-declared — forward-clean. */
532
+ const classified = {
533
+ queries: [
534
+ { q: 'a', trigger: true, class: 'exemplar' },
535
+ { q: 'b', trigger: true, class: 'exemplar' },
536
+ { q: 'c', trigger: true, class: 'exemplar', language: 'de' },
537
+ { q: 'd', trigger: false, class: 'near-miss' },
538
+ { q: 'e', trigger: false, class: 'counterexample' },
539
+ ],
540
+ };
541
+ const mutate = (fn: (qs: Record<string, unknown>[]) => Record<string, unknown>[]) => ({
542
+ queries: fn(JSON.parse(JSON.stringify(classified.queries)) as Record<string, unknown>[]),
543
+ });
243
544
  const cases: SelfTestCase[] = [
244
545
  {
245
546
  name: 'a compliant corpus is accepted',
@@ -293,13 +594,114 @@ export function selfTest(): number {
293
594
  return run(root);
294
595
  },
295
596
  },
597
+ // --- case-class discipline, forward-only. Every case below runs with
598
+ // the forward flag forced on; the pair at the end proves the flag is
599
+ // what turns the rule on, so a rule that fired unconditionally would
600
+ // be caught here rather than by a reviewer.
601
+ {
602
+ name: 'a fully classified corpus is accepted under the class discipline',
603
+ expect: 'accept',
604
+ run: () => run(mkRoot('probe', classified), true),
605
+ },
606
+ {
607
+ name: 'an unclassified case is rejected',
608
+ expect: 'reject',
609
+ run: () =>
610
+ run(
611
+ mkRoot(
612
+ 'probe',
613
+ mutate((qs) => {
614
+ delete qs[0]?.['class'];
615
+ return qs;
616
+ }),
617
+ ),
618
+ true,
619
+ ),
620
+ },
621
+ {
622
+ name: 'a class outside the closed vocabulary is rejected',
623
+ expect: 'reject',
624
+ run: () =>
625
+ run(
626
+ mkRoot(
627
+ 'probe',
628
+ mutate((qs) => {
629
+ if (qs[0]) qs[0]['class'] = 'positive';
630
+ return qs;
631
+ }),
632
+ ),
633
+ true,
634
+ ),
635
+ },
636
+ {
637
+ name: 'a near-miss on trigger:true is rejected — polarity is part of the vocabulary',
638
+ expect: 'reject',
639
+ run: () =>
640
+ run(
641
+ mkRoot(
642
+ 'probe',
643
+ mutate((qs) => {
644
+ if (qs[0]) qs[0]['class'] = 'near-miss';
645
+ return qs;
646
+ }),
647
+ ),
648
+ true,
649
+ ),
650
+ },
651
+ {
652
+ name: 'an exemplar on trigger:false is rejected — the other polarity direction',
653
+ expect: 'reject',
654
+ run: () =>
655
+ run(
656
+ mkRoot(
657
+ 'probe',
658
+ mutate((qs) => {
659
+ if (qs[4]) qs[4]['class'] = 'exemplar';
660
+ return qs;
661
+ }),
662
+ ),
663
+ true,
664
+ ),
665
+ },
666
+ {
667
+ name: 'a corpus with no counterexample at all is rejected',
668
+ expect: 'reject',
669
+ run: () =>
670
+ run(
671
+ mkRoot(
672
+ 'probe',
673
+ mutate((qs) => {
674
+ if (qs[4]) qs[4]['class'] = 'near-miss';
675
+ return qs;
676
+ }),
677
+ ),
678
+ true,
679
+ ),
680
+ },
681
+ {
682
+ name: 'the legacy string-array shape is rejected under the class discipline',
683
+ expect: 'reject',
684
+ run: () =>
685
+ run(
686
+ mkRoot('probe', {
687
+ should_trigger: ['a', 'b', 'c'],
688
+ should_not_trigger: ['d', 'e'],
689
+ }),
690
+ true,
691
+ ),
692
+ },
693
+ {
694
+ name: 'the SAME unclassified corpus is accepted when NOT diff-touched',
695
+ expect: 'accept',
696
+ run: () => run(mkRoot('probe', good), false),
697
+ },
296
698
  ];
297
699
  try {
298
700
  return runSelfTest({
299
701
  gate: 'lint_skill_trigger_corpus',
300
702
  cases,
301
- minCases: 7,
302
- minRejectCases: 4,
703
+ minCases: 15,
704
+ minRejectCases: 10,
303
705
  });
304
706
  } finally {
305
707
  fs.rmSync(tmp, { recursive: true, force: true });
@@ -330,7 +732,8 @@ export function main(argv: string[] = process.argv.slice(2), root = REPO_ROOT):
330
732
  process.stdout.write(
331
733
  `lint_skill_trigger_corpus: ${String(r.scanned)} corpus file(s) hold the discipline ` +
332
734
  `(>=${String(MIN_POSITIVES)} positives, >=${String(MIN_NEAR_MISSES)} near-misses; ` +
333
- `${String(GRANDFATHERED.size)} grandfathered by name).\n`,
735
+ `${String(GRANDFATHERED.size)} grandfathered by name). Diff-touched files also ` +
736
+ `carry a declared case class per query (${CASE_CLASSES.join(' | ')}).\n`,
334
737
  );
335
738
  return 0;
336
739
  }
@@ -609,6 +609,22 @@ function _cycle(graph: Map<string, string[]>): string[] | null {
609
609
  // iterdir / glob helpers (pathlib-faithful sort)
610
610
  // ---------------------------------------------------------------------------
611
611
 
612
+ /**
613
+ * Directories under `agents/tickets/` that are NOT ticket bundles.
614
+ *
615
+ * `archive` was the original member and was tested inline. `symptoms` joins it
616
+ * because it is a different artefact class that happens to live under the same
617
+ * root: one markdown file per operator symptom report, plus a README and a
618
+ * template, established so a production symptom had somewhere in the tree to
619
+ * land. It has no tickets, no dependency graph and no Definition-of-Ready, so
620
+ * every check below is meaningless there — and demanding a `manifest.yml` from
621
+ * it is the gate asserting a contract that directory never claimed.
622
+ *
623
+ * A named set rather than a second `||`: the next non-bundle directory should
624
+ * cost one line here instead of another inline special case.
625
+ */
626
+ const NON_BUNDLE_DIRS: ReadonlySet<string> = new Set(['archive', 'symptoms']);
627
+
612
628
  /** sorted(TICKETS_ROOT.iterdir()) — all entries (files + dirs), name-sorted. */
613
629
  function _iterdirSorted(root: string): string[] {
614
630
  let names: string[];
@@ -707,7 +723,7 @@ function lint(): number {
707
723
  const bundle_ids = new Set<string>();
708
724
 
709
725
  for (const bundle of _iterdirSorted(TICKETS_ROOT)) {
710
- if (!_isDir(bundle) || path.basename(bundle) === 'archive') {
726
+ if (!_isDir(bundle) || NON_BUNDLE_DIRS.has(path.basename(bundle))) {
711
727
  continue;
712
728
  }
713
729
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schema_version": 1,
3
- "description": "Source-of-truth catalog of consumer-relevant MCP tools. Read by the stdio server (scripts/mcp_server/) and packed into the Cloud Worker bundle (internal/workers/mcp/). The 'implemented_on' field lists transports where the real handler is wired. Entries marked '[stub — implemented on demand]' are DOCUMENTATION of the on-demand backlog: per ADR-132 they are NOT registered on the stdio tools/list (the wire carries implemented tools only); the 'not_implemented' envelope contract (docs/contracts/mcp-tool-stub-envelope.md) remains for Worker parity. Generated by `src/scripts/build_mcp_catalog.ts` — never hand-edit this file.",
3
+ "description": "Source-of-truth catalog of consumer-relevant MCP tools. Read by the stdio server (scripts/mcp_server/) and packed into the Cloud Worker bundle (internal/workers/mcp/). The 'implemented_on' field lists transports where the real handler is wired. Entries marked '[stub — implemented on demand]' are DOCUMENTATION of the on-demand backlog: per ADR-132 they are NOT registered on the stdio tools/list (the wire carries implemented tools only); the 'not_implemented' envelope contract (docs/contracts/mcp-tool-stub-envelope.md) remains for Worker parity. Generated by `build_mcp_catalog` — never hand-edit this file.",
4
4
  "install_hint_stdio": "npx -y @event4u/agent-config mcp-server",
5
5
  "tools": [
6
6
  {
@@ -33,7 +33,7 @@ export const CATALOG_DESCRIPTION =
33
33
  'backlog: per ADR-132 they are NOT registered on the stdio tools/list ' +
34
34
  "(the wire carries implemented tools only); the 'not_implemented' " +
35
35
  'envelope contract (docs/contracts/mcp-tool-stub-envelope.md) remains ' +
36
- 'for Worker parity. Generated by `src/scripts/build_mcp_catalog.ts` — ' +
36
+ 'for Worker parity. Generated by `build_mcp_catalog` — ' +
37
37
  'never hand-edit this file.';
38
38
 
39
39
  export interface StubToolSource {