@event4u/agent-config 14.11.0 → 14.12.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 (350) 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 +156 -538
  5. package/README.md +1 -1
  6. package/dist/agent-src/contexts/execution/evidence-discipline.md +26 -0
  7. package/dist/agent-src/skills/code-refactoring/SKILL.md +45 -1
  8. package/dist/agent-src/skills/code-review/SKILL.md +65 -6
  9. package/dist/agent-src/skills/command-writing/SKILL.md +35 -0
  10. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +8 -1
  11. package/dist/agent-src/skills/project-analysis-core/SKILL.md +23 -0
  12. package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +29 -12
  13. package/dist/agent-src/skills/skill-writing/references/procedure.md +24 -2
  14. package/dist/agent-src/skills/storybook-workshop/SKILL.md +9 -0
  15. package/dist/agent-src/skills/ui-component-architect/SKILL.md +8 -0
  16. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  17. package/dist/cli-delegate/{chunk-L43NCYPB.js → chunk-JCW7ZLHK.js} +5 -0
  18. package/dist/cli-delegate/{chunk-236PNQZK.js → chunk-JJA2QEWA.js} +1 -1
  19. package/dist/cli-delegate/cmd_export.js +2 -2
  20. package/dist/cli-delegate/cmd_prune.js +2 -2
  21. package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
  22. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  23. package/dist/cli-delegate/cmd_sync.js +2 -2
  24. package/dist/cli-delegate/cmd_uninstall.js +2 -2
  25. package/dist/cli-delegate/cmd_validate.js +2 -2
  26. package/dist/discovery/deprecation-report.md +1 -1
  27. package/dist/discovery/discovery-manifest.json +10 -10
  28. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  29. package/dist/discovery/discovery-manifest.summary.md +1 -1
  30. package/dist/discovery/dormancy-report.md +1 -1
  31. package/dist/discovery/orphan-report.md +1 -1
  32. package/dist/discovery/packs.json +2 -2
  33. package/dist/discovery/trust-report.md +1 -1
  34. package/dist/discovery/workspaces.json +2 -2
  35. package/dist/hooks/dispatch.js +1081 -879
  36. package/dist/install/install.mjs +5 -0
  37. package/dist/mcp/registry-manifest.json +2 -2
  38. package/dist/mcp/server.json +2 -2
  39. package/dist/server/schemas/settings.js +3 -0
  40. package/docs/contracts/settings-classes.md +24 -4
  41. package/docs/guidelines/agent-infra/downstream-changes-mechanics.md +91 -0
  42. package/docs/guidelines/code-clarity.md +21 -0
  43. package/docs/guidelines/redundancy-taxonomy.md +180 -0
  44. package/package.json +6 -2
  45. package/src/config/agent-settings.template.yml +16 -0
  46. package/src/config/budgets.yml +19 -0
  47. package/src/config/ci-local-parity.yml +15 -0
  48. package/src/config/discovery/prompt-patterns.yml +56 -0
  49. package/src/config/estate-count-budget.json +8 -3
  50. package/src/config/gate-coverage.yml +191 -6
  51. package/src/config/gate-violation-baselines.json +3 -3
  52. package/src/config/pack-size-budget.json +39 -12
  53. package/src/config/preamble-payload-budget.json +97 -71
  54. package/src/config/routing-coverage-seed.json +29 -0
  55. package/src/config/suggestion-capture.json +28 -0
  56. package/src/config/surface-matrix.yml +21 -2
  57. package/src/config/web-launch-readiness.json +188 -0
  58. package/src/scripts/_lib/gate_baseline.ts +150 -1
  59. package/src/scripts/_lib/skill_estate.ts +115 -0
  60. package/src/scripts/ai_council/argument_exhaustion.ts +130 -0
  61. package/src/scripts/check_bundle_path_leakage.ts +162 -7
  62. package/src/scripts/check_command_examples.ts +557 -0
  63. package/src/scripts/check_estate_count.ts +122 -3
  64. package/src/scripts/check_preamble_payload_budget.ts +30 -3
  65. package/src/scripts/check_routing_coverage.ts +352 -0
  66. package/src/scripts/check_score_contract.ts +505 -0
  67. package/src/scripts/check_skill_admissions.ts +514 -0
  68. package/src/scripts/check_web_launch_readiness.ts +705 -0
  69. package/src/scripts/description_route_check.ts +494 -0
  70. package/src/scripts/hook_manifest.json +1 -1
  71. package/src/scripts/hook_manifest.yaml +29 -2
  72. package/src/scripts/hooks/concern_registry.ts +2 -0
  73. package/src/scripts/hooks/suggestion_capture_hook.ts +368 -0
  74. package/src/scripts/lint_budget_ownership.ts +54 -4
  75. package/src/scripts/lint_skill_trigger_corpus.ts +349 -0
  76. package/src/scripts/pack_install_smoke.ts +363 -0
  77. package/src/scripts/pr_conflict_census.ts +187 -0
  78. package/src/scripts/report_pack_delta.ts +204 -0
  79. package/src/scripts/select_analysis_mode.ts +112 -0
  80. package/src/scripts/sync_pr_branch.ts +40 -1
  81. package/src/server/schemas/settings.ts +5 -0
  82. package/dist/agent-src/skills/accessibility-auditor/evals/evals.json +0 -15
  83. package/dist/agent-src/skills/agent-security-review/evals/triggers.json +0 -52
  84. package/dist/agent-src/skills/ai-code-blindspots/evals/triggers.json +0 -52
  85. package/dist/agent-src/skills/alerting-doctrine/evals/triggers.json +0 -17
  86. package/dist/agent-src/skills/analysis-skill-router/evals/evals.json +0 -13
  87. package/dist/agent-src/skills/brand/evals/triggers.json +0 -17
  88. package/dist/agent-src/skills/brand-asset-generation/evals/triggers.json +0 -17
  89. package/dist/agent-src/skills/brand-audit/evals/triggers.json +0 -17
  90. package/dist/agent-src/skills/brand-identity/evals/triggers.json +0 -17
  91. package/dist/agent-src/skills/brand-strategy/evals/triggers.json +0 -17
  92. package/dist/agent-src/skills/brand-to-tokens/evals/triggers.json +0 -17
  93. package/dist/agent-src/skills/bug-analyzer/evals/evals.json +0 -13
  94. package/dist/agent-src/skills/canvas-design/evals/triggers.json +0 -17
  95. package/dist/agent-src/skills/character-consistency/evals/evals.json +0 -16
  96. package/dist/agent-src/skills/code-intelligence/evals/triggers.json +0 -52
  97. package/dist/agent-src/skills/code-review/evals/evals.json +0 -100
  98. package/dist/agent-src/skills/command-routing/evals/evals.json +0 -14
  99. package/dist/agent-src/skills/complexity-first-planning/evals/triggers.json +0 -17
  100. package/dist/agent-src/skills/contract-review/evals/triggers.json +0 -17
  101. package/dist/agent-src/skills/conventional-commits-writing/evals/evals.json +0 -15
  102. package/dist/agent-src/skills/corpus-grounding/evals/triggers.json +0 -17
  103. package/dist/agent-src/skills/dashboard-design/evals/evals.json +0 -16
  104. package/dist/agent-src/skills/dcf-modeling/evals/domain-truth.json +0 -29
  105. package/dist/agent-src/skills/dcf-modeling/evals/evals.json +0 -20
  106. package/dist/agent-src/skills/decision-record/evals/evals.json +0 -20
  107. package/dist/agent-src/skills/decision-record/evals/triggers.json +0 -87
  108. package/dist/agent-src/skills/design-intelligence/evals/evals.json +0 -14
  109. package/dist/agent-src/skills/design-intelligence/evals/triggers.json +0 -17
  110. package/dist/agent-src/skills/design-system-capture/evals/evals.json +0 -15
  111. package/dist/agent-src/skills/design-tokens/evals/triggers.json +0 -17
  112. package/dist/agent-src/skills/design-variations/evals/triggers.json +0 -17
  113. package/dist/agent-src/skills/developer-like-execution/evals/evals.json +0 -16
  114. package/dist/agent-src/skills/doc-coauthoring/evals/evals.json +0 -16
  115. package/dist/agent-src/skills/doc-coauthoring/evals/triggers.json +0 -17
  116. package/dist/agent-src/skills/docx-authoring/evals/triggers.json +0 -52
  117. package/dist/agent-src/skills/dpa-review/evals/triggers.json +0 -17
  118. package/dist/agent-src/skills/editorial-calendar/evals/domain-truth.json +0 -16
  119. package/dist/agent-src/skills/editorial-calendar/evals/evals.json +0 -16
  120. package/dist/agent-src/skills/eloquent/evals/triggers.json +0 -17
  121. package/dist/agent-src/skills/estimate-ticket/evals/evals.json +0 -16
  122. package/dist/agent-src/skills/estimate-ticket/evals/output-schema.yml +0 -20
  123. package/dist/agent-src/skills/estimate-ticket/evals/triggers.json +0 -19
  124. package/dist/agent-src/skills/evaluate-llm-feature/evals/triggers.json +0 -52
  125. package/dist/agent-src/skills/existing-ui-audit/evals/triggers.json +0 -47
  126. package/dist/agent-src/skills/experiment-loop/evals/output-schema.yml +0 -27
  127. package/dist/agent-src/skills/experiment-loop/evals/triggers.json +0 -17
  128. package/dist/agent-src/skills/fe-design/evals/triggers.json +0 -67
  129. package/dist/agent-src/skills/forecasting/evals/domain-truth.json +0 -29
  130. package/dist/agent-src/skills/forecasting/evals/evals.json +0 -20
  131. package/dist/agent-src/skills/forensics-report/evals/triggers.json +0 -17
  132. package/dist/agent-src/skills/frontend-render-security/evals/triggers.json +0 -52
  133. package/dist/agent-src/skills/fundraising-narrative/evals/domain-truth.json +0 -16
  134. package/dist/agent-src/skills/fundraising-narrative/evals/evals.json +0 -16
  135. package/dist/agent-src/skills/gated-reach/evals/triggers.json +0 -72
  136. package/dist/agent-src/skills/github-ci/evals/evals.json +0 -25
  137. package/dist/agent-src/skills/history-design/evals/triggers.json +0 -17
  138. package/dist/agent-src/skills/html-deck/evals/triggers.json +0 -17
  139. package/dist/agent-src/skills/humanizer/evals/strip_fixtures.json +0 -57
  140. package/dist/agent-src/skills/humanizer/evals/triggers.json +0 -17
  141. package/dist/agent-src/skills/iconography/evals/triggers.json +0 -17
  142. package/dist/agent-src/skills/image-analyser/evals/triggers.json +0 -17
  143. package/dist/agent-src/skills/image-creator/evals/triggers.json +0 -17
  144. package/dist/agent-src/skills/image-editing/evals/triggers.json +0 -17
  145. package/dist/agent-src/skills/image-generation/evals/triggers.json +0 -17
  146. package/dist/agent-src/skills/image-provider-routing/evals/triggers.json +0 -17
  147. package/dist/agent-src/skills/incident-commander/evals/domain-truth.json +0 -16
  148. package/dist/agent-src/skills/incident-commander/evals/evals.json +0 -20
  149. package/dist/agent-src/skills/js-library-packaging/evals/triggers.json +0 -17
  150. package/dist/agent-src/skills/judge-artifact-completeness/evals/triggers.json +0 -17
  151. package/dist/agent-src/skills/judge-injection-defense/evals/triggers.json +0 -17
  152. package/dist/agent-src/skills/judge-synthesis/evals/triggers.json +0 -17
  153. package/dist/agent-src/skills/launch-readiness/evals/evals.json +0 -21
  154. package/dist/agent-src/skills/learning-tutor/evals/triggers.json +0 -52
  155. package/dist/agent-src/skills/legal-intake-triage/evals/triggers.json +0 -17
  156. package/dist/agent-src/skills/legal-practice-profile/evals/triggers.json +0 -17
  157. package/dist/agent-src/skills/license-compliance-audit/evals/triggers.json +0 -52
  158. package/dist/agent-src/skills/license-compliance-borrow-check/evals/triggers.json +0 -52
  159. package/dist/agent-src/skills/license-compliance-credits/evals/triggers.json +0 -52
  160. package/dist/agent-src/skills/llm-provider-knowledge/evals/triggers.json +0 -20
  161. package/dist/agent-src/skills/logging-monitoring/evals/evals.json +0 -16
  162. package/dist/agent-src/skills/logo-generation/evals/triggers.json +0 -17
  163. package/dist/agent-src/skills/messaging-architecture/evals/domain-truth.json +0 -16
  164. package/dist/agent-src/skills/messaging-architecture/evals/evals.json +0 -16
  165. package/dist/agent-src/skills/monorepo-workspace/evals/triggers.json +0 -17
  166. package/dist/agent-src/skills/nda-triage/evals/triggers.json +0 -17
  167. package/dist/agent-src/skills/operational-readiness/evals/triggers.json +0 -17
  168. package/dist/agent-src/skills/pdf-tools/evals/triggers.json +0 -52
  169. package/dist/agent-src/skills/perf-feedback-craft/evals/evals.json +0 -16
  170. package/dist/agent-src/skills/persona-improvement/evals/triggers.json +0 -52
  171. package/dist/agent-src/skills/php-coder/evals/triggers.json +0 -17
  172. package/dist/agent-src/skills/prediction-pool-optimizer/evals/triggers.json +0 -21
  173. package/dist/agent-src/skills/prompt-engineering-image/evals/triggers.json +0 -17
  174. package/dist/agent-src/skills/prompt-engineering-patterns/evals/evals.json +0 -16
  175. package/dist/agent-src/skills/prompt-validator/evals/triggers.json +0 -17
  176. package/dist/agent-src/skills/quality-tools/evals/evals.json +0 -15
  177. package/dist/agent-src/skills/reasoning-orchestrator/evals/triggers.json +0 -17
  178. package/dist/agent-src/skills/refine-prompt/evals/evals.json +0 -16
  179. package/dist/agent-src/skills/refine-ticket/evals/evals.json +0 -16
  180. package/dist/agent-src/skills/refine-ticket/evals/output-schema.yml +0 -16
  181. package/dist/agent-src/skills/refine-ticket/evals/triggers.json +0 -17
  182. package/dist/agent-src/skills/release-comms/evals/evals.json +0 -16
  183. package/dist/agent-src/skills/rice-prioritization/evals/evals.json +0 -20
  184. package/dist/agent-src/skills/runway-cognition/evals/domain-truth.json +0 -29
  185. package/dist/agent-src/skills/runway-cognition/evals/evals.json +0 -20
  186. package/dist/agent-src/skills/scenario-modeling/evals/domain-truth.json +0 -29
  187. package/dist/agent-src/skills/scenario-modeling/evals/evals.json +0 -20
  188. package/dist/agent-src/skills/schema-review/evals/triggers.json +0 -17
  189. package/dist/agent-src/skills/screenshot-hygiene/evals/triggers.json +0 -52
  190. package/dist/agent-src/skills/security-maturity-assessment/evals/triggers.json +0 -52
  191. package/dist/agent-src/skills/server-hardening/evals/triggers.json +0 -17
  192. package/dist/agent-src/skills/skill-writing/evals/triggers.json +0 -17
  193. package/dist/agent-src/skills/spreadsheet-authoring/evals/triggers.json +0 -47
  194. package/dist/agent-src/skills/storybook-workshop/evals/triggers.json +0 -17
  195. package/dist/agent-src/skills/subagent-orchestration/evals/evals.json +0 -60
  196. package/dist/agent-src/skills/supply-chain-intake/evals/triggers.json +0 -52
  197. package/dist/agent-src/skills/systematic-debugging/evals/evals.json +0 -20
  198. package/dist/agent-src/skills/test-case-discovery/evals/triggers.json +0 -52
  199. package/dist/agent-src/skills/test-driven-development/evals/evals.json +0 -16
  200. package/dist/agent-src/skills/threat-modeling/evals/evals.json +0 -21
  201. package/dist/agent-src/skills/typography-system/evals/evals.json +0 -14
  202. package/dist/agent-src/skills/typography-system/evals/triggers.json +0 -17
  203. package/dist/agent-src/skills/unit-economics-modeling/evals/domain-truth.json +0 -40
  204. package/dist/agent-src/skills/unit-economics-modeling/evals/evals.json +0 -24
  205. package/dist/agent-src/skills/verify-completion-evidence/evals/evals.json +0 -16
  206. package/dist/agent-src/skills/verify-repair-loop/evals/output-schema.yml +0 -20
  207. package/dist/agent-src/skills/verify-repair-loop/evals/triggers.json +0 -17
  208. package/dist/agent-src/skills/vision-articulation/evals/evals.json +0 -16
  209. package/dist/agent-src/skills/voice-and-tone-design/evals/evals.json +0 -16
  210. package/dist/agent-src/skills/wireframe/evals/triggers.json +0 -17
  211. package/dist/agent-src/skills/workspace-link/evals/triggers.json +0 -17
  212. package/dist/agent-src/skills/worktree-lifecycle/evals/evals.json +0 -38
  213. package/dist/agent-src/skills/worktree-lifecycle/evals/triggers.json +0 -52
  214. package/dist/cli/agent-config.js.map +0 -1
  215. package/dist/cli/bash/runBash.js.map +0 -1
  216. package/dist/cli/commands/commands.js.map +0 -1
  217. package/dist/cli/commands/config.js.map +0 -1
  218. package/dist/cli/commands/doctorShell.js.map +0 -1
  219. package/dist/cli/commands/mcpServer.js.map +0 -1
  220. package/dist/cli/commands/packs.js.map +0 -1
  221. package/dist/cli/commands/profiles.js.map +0 -1
  222. package/dist/cli/commands/recordTriggerEval.js.map +0 -1
  223. package/dist/cli/commands/rtkDetect.js.map +0 -1
  224. package/dist/cli/commands/settings.js.map +0 -1
  225. package/dist/cli/commands/uiAudit.js.map +0 -1
  226. package/dist/cli/commands/uiRender.js.map +0 -1
  227. package/dist/cli/commands/uiServe.js.map +0 -1
  228. package/dist/cli/commands/versions.js.map +0 -1
  229. package/dist/cli/commands/workspaces.js.map +0 -1
  230. package/dist/cli/configRoot.js.map +0 -1
  231. package/dist/cli/discovery/loadManifest.js.map +0 -1
  232. package/dist/cli/firstRunNotice.js.map +0 -1
  233. package/dist/cli/help.js.map +0 -1
  234. package/dist/cli/initRouting.js.map +0 -1
  235. package/dist/cli/log/logger.js.map +0 -1
  236. package/dist/cli/main.js.map +0 -1
  237. package/dist/cli/mcp/content.js.map +0 -1
  238. package/dist/cli/mcp/dispatch.js.map +0 -1
  239. package/dist/cli/mcp/stdio.js.map +0 -1
  240. package/dist/cli/paths.js.map +0 -1
  241. package/dist/cli/python/knowledge_ingest.js.map +0 -1
  242. package/dist/cli/python/workspace_analytics.js.map +0 -1
  243. package/dist/cli/python/workspace_crypto.js.map +0 -1
  244. package/dist/cli/python/workspace_documents.js.map +0 -1
  245. package/dist/cli/python/workspace_drive.js.map +0 -1
  246. package/dist/cli/python/workspace_drive_health.js.map +0 -1
  247. package/dist/cli/python/workspace_explain.js.map +0 -1
  248. package/dist/cli/python/workspace_hosts.js.map +0 -1
  249. package/dist/cli/python/workspace_inbox.js.map +0 -1
  250. package/dist/cli/python/workspace_render.js.map +0 -1
  251. package/dist/cli/python/workspace_roles.js.map +0 -1
  252. package/dist/cli/python/workspace_secrets.js.map +0 -1
  253. package/dist/cli/python/workspace_sessions.js.map +0 -1
  254. package/dist/cli/python/workspace_skills.js.map +0 -1
  255. package/dist/cli/registry.js.map +0 -1
  256. package/dist/install/agentSwitchDetection.js.map +0 -1
  257. package/dist/install/agentSwitchProfile.js.map +0 -1
  258. package/dist/install/atomic.js.map +0 -1
  259. package/dist/install/claudePathsPlan.js.map +0 -1
  260. package/dist/install/conflict.js.map +0 -1
  261. package/dist/install/detect.js.map +0 -1
  262. package/dist/install/emit_host_rules_cli.js.map +0 -1
  263. package/dist/install/globalRuleLayers.js.map +0 -1
  264. package/dist/install/hostLayerFingerprint.js.map +0 -1
  265. package/dist/install/partitionEligibility.js.map +0 -1
  266. package/dist/install/paths.js.map +0 -1
  267. package/dist/install/plan.js.map +0 -1
  268. package/dist/install/preflight.js.map +0 -1
  269. package/dist/install/rtkDetection.js.map +0 -1
  270. package/dist/install/ruleInScope.js.map +0 -1
  271. package/dist/install/ruleLayerPartition.js.map +0 -1
  272. package/dist/install/rule_scope.js.map +0 -1
  273. package/dist/install/rule_scope_cli.js.map +0 -1
  274. package/dist/install/selectedTools.js.map +0 -1
  275. package/dist/install/toolDetection.js.map +0 -1
  276. package/dist/install/txlog.js.map +0 -1
  277. package/dist/install/types.js.map +0 -1
  278. package/dist/install/wizard-plan.js.map +0 -1
  279. package/dist/install/wizardDismissals.js.map +0 -1
  280. package/dist/scripts/_lib/agent_settings.js.map +0 -1
  281. package/dist/scripts/_lib/agent_src.js.map +0 -1
  282. package/dist/scripts/_lib/claude_builtin_names.js.map +0 -1
  283. package/dist/scripts/_lib/claude_plugin.js.map +0 -1
  284. package/dist/scripts/_lib/claude_settings_hooks.js.map +0 -1
  285. package/dist/scripts/_lib/compile_time_toggles.js.map +0 -1
  286. package/dist/scripts/_lib/environment_detector.js.map +0 -1
  287. package/dist/scripts/_lib/host_launch.js.map +0 -1
  288. package/dist/scripts/_lib/install_layout.js.map +0 -1
  289. package/dist/scripts/_lib/installed_lock.js.map +0 -1
  290. package/dist/scripts/_lib/lean_projection_mode.js.map +0 -1
  291. package/dist/scripts/_lib/model_tier.js.map +0 -1
  292. package/dist/scripts/_lib/prune_empty_dirs.js.map +0 -1
  293. package/dist/scripts/_lib/scoped_projection.js.map +0 -1
  294. package/dist/scripts/_lib/script_output.js.map +0 -1
  295. package/dist/scripts/_lib/spawn_env.js.map +0 -1
  296. package/dist/scripts/_lib/surface_tiers.js.map +0 -1
  297. package/dist/scripts/_lib/token_count.js.map +0 -1
  298. package/dist/scripts/_lib/trigger_eval_floors.js.map +0 -1
  299. package/dist/scripts/_lib/ui_surface.js.map +0 -1
  300. package/dist/scripts/_lib/user_global_paths.js.map +0 -1
  301. package/dist/scripts/ai_council/modes.js.map +0 -1
  302. package/dist/scripts/ai_council/transport_resolver.js.map +0 -1
  303. package/dist/scripts/condense.js.map +0 -1
  304. package/dist/scripts/project_thin_rules.js.map +0 -1
  305. package/dist/server/app.js.map +0 -1
  306. package/dist/server/io/atomicMultiWrite.js.map +0 -1
  307. package/dist/server/io/atomicWrite.js.map +0 -1
  308. package/dist/server/io/sharedWriteCheck.js.map +0 -1
  309. package/dist/server/io/substituteTemplate.js.map +0 -1
  310. package/dist/server/io/yamlIO.js.map +0 -1
  311. package/dist/server/nicknameResolver.js.map +0 -1
  312. package/dist/server/port.js.map +0 -1
  313. package/dist/server/routes/discovery.js.map +0 -1
  314. package/dist/server/routes/install.js.map +0 -1
  315. package/dist/server/routes/ping.js.map +0 -1
  316. package/dist/server/routes/schema.js.map +0 -1
  317. package/dist/server/routes/settings.js.map +0 -1
  318. package/dist/server/routes/settingsChanges.js.map +0 -1
  319. package/dist/server/routes/userMd.js.map +0 -1
  320. package/dist/server/routes/wizard.js.map +0 -1
  321. package/dist/server/routes/workspace.js.map +0 -1
  322. package/dist/server/schemas/settings.js.map +0 -1
  323. package/dist/server/serverInfo.js.map +0 -1
  324. package/dist/server/token.js.map +0 -1
  325. package/dist/server/writeRoot.js.map +0 -1
  326. package/dist/shared/capabilities.js.map +0 -1
  327. package/dist/shared/interactiveContext.js.map +0 -1
  328. package/dist/shared/nicknamePrefill.js.map +0 -1
  329. package/dist/shared/settingsAsks.js.map +0 -1
  330. package/dist/shared/settingsCarveOut.js.map +0 -1
  331. package/dist/shared/settingsClasses.js.map +0 -1
  332. package/dist/shared/settingsConsent.js.map +0 -1
  333. package/dist/shared/settingsSurface.js.map +0 -1
  334. package/dist/shared/skillRanking.js.map +0 -1
  335. package/dist/shared/userMd/formAdapter.js.map +0 -1
  336. package/dist/shared/userMd/schema.js.map +0 -1
  337. package/dist/shared/userMd/utils.js.map +0 -1
  338. package/dist/ui/assets/index-Cxgy63kz.js.map +0 -1
  339. package/src/scripts/_lib/adr_frontmatter.test.ts +0 -365
  340. package/src/scripts/_lib/adversarial_council_gate.test.ts +0 -112
  341. package/src/scripts/_lib/adversarial_reconcile.test.ts +0 -197
  342. package/src/scripts/_lib/auto_dispatch.corpus.test.ts +0 -150
  343. package/src/scripts/_lib/secret_detector.test.ts +0 -108
  344. package/src/scripts/_lib/secret_detector_adversarial.test.ts +0 -116
  345. package/src/scripts/_lib/ui_authority.test.ts +0 -251
  346. package/src/scripts/_lib/untrusted_content.test.ts +0 -175
  347. package/src/scripts/check_secret_leak.test.ts +0 -90
  348. package/src/scripts/design_slop_rules.test.ts +0 -246
  349. package/src/scripts/self_review_gate.test.ts +0 -296
  350. package/src/shared/capabilities.test.ts +0 -43
@@ -57,6 +57,7 @@ import { main as rtkWrapMain } from './rtk_wrap_hook.js';
57
57
  import { _main as designPassMain } from './design_pass_hook.js';
58
58
  import { main as designSlopMain } from './design_slop_hook.js';
59
59
  import { main as codeGraphNudgeMain } from './code_graph_nudge_hook.js';
60
+ import { main as suggestionCaptureMain } from './suggestion_capture_hook.js';
60
61
  import { main as uiRouteNudgeMain } from './ui_route_nudge_hook.js';
61
62
  import { main as orchestrationRecordMain } from './orchestration_record_hook.js';
62
63
  import { main as telemetryUsageMain } from './telemetry_usage_hook.js';
@@ -114,6 +115,7 @@ export const CONCERN_REGISTRY: Readonly<Record<string, ConcernMain>> = {
114
115
  'src/scripts/hooks/design_pass_hook.ts': designPassMain as ConcernMain,
115
116
  'src/scripts/hooks/design_slop_hook.ts': designSlopMain as ConcernMain,
116
117
  'src/scripts/hooks/code_graph_nudge_hook.ts': codeGraphNudgeMain as ConcernMain,
118
+ 'src/scripts/hooks/suggestion_capture_hook.ts': suggestionCaptureMain as ConcernMain,
117
119
  'src/scripts/hooks/ui_route_nudge_hook.ts': uiRouteNudgeMain as ConcernMain,
118
120
  'src/scripts/hooks/orchestration_record_hook.ts': orchestrationRecordMain as ConcernMain,
119
121
  'src/scripts/hooks/telemetry_usage_hook.ts': telemetryUsageMain as ConcernMain,
@@ -0,0 +1,368 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Suggestion-block capture — a two-slot latch, counts only.
4
+ *
5
+ * ## What it measures and why it exists
6
+ *
7
+ * `command_suggester/render.ts` emits a numbered-options block with exactly one
8
+ * `Recommendation: N — …` line and the as-is option always last. Nothing records
9
+ * whether those blocks are ever *answered*, so no roadmap can cite a selection
10
+ * rate — and the model-carried alternative is measured and dead:
11
+ * `orchestration_record` captured 1 of 369 dispatches. This instrument exists so
12
+ * that figure is not repeated by construction.
13
+ *
14
+ * ## Two slots, not one — and the probe is why
15
+ *
16
+ * `road-to-suggestion-block-capture` planned a single `user_prompt_submit` hook
17
+ * reading the transcript tail. A live probe
18
+ * (`agents/evidence/analysis/suggestion-capture-probe.md`) found something
19
+ * cheaper: **`stop` carries `last_assistant_message` in the payload**, so the
20
+ * "was a block emitted" half needs no file read at all, and the turn-1 blind
21
+ * spot (`transcript_path` present but the file not yet written) does not arise.
22
+ *
23
+ * stop → detect the signature in `last_assistant_message`,
24
+ * write a latch keyed by `prompt_id`
25
+ * user_prompt_submit → CONSUME the latch, classify `prompt`, append one line
26
+ *
27
+ * ## The misclassification guard, which is the whole correctness argument
28
+ *
29
+ * A bare `1` three turns later must never read as a pick. So the latch is
30
+ * **consumed exactly once**: `user_prompt_submit` deletes it after reading, and
31
+ * a prompt arriving with no latch is `other`, never `option_n`. A latch older
32
+ * than `LATCH_TTL_MS` is `stale_block`. An unparseable latch is `stale_block`
33
+ * too — never a guess.
34
+ *
35
+ * ## Privacy is a property of the schema, not of a scrubber
36
+ *
37
+ * `src/config/suggestion-capture.json` registers five fields and **none can hold
38
+ * free text**: a timestamp, a boolean, an options COUNT, and two closed enums.
39
+ * `option_n` records THAT a numbered option was chosen and out of how many —
40
+ * deliberately not WHICH, because the block's option order is content and the
41
+ * capture-rate question does not need it. A type with no field able to carry a
42
+ * prompt cannot leak one; a scrubber can fail.
43
+ *
44
+ * Default-OFF (`hooks.suggestion_capture.enabled: true`). `fail_closed: false`,
45
+ * always exit 0: an instrument must never break a turn it is only observing.
46
+ */
47
+ import fs from 'node:fs';
48
+ import path from 'node:path';
49
+ import { fileURLToPath, pathToFileURL } from 'node:url';
50
+
51
+ import { unwrap } from './envelope.js';
52
+ import { readHookStdin } from './hook_stdin.js';
53
+
54
+ const EXIT_ALLOW = 0;
55
+ const SETTINGS_FILE = '.agent-settings.yml';
56
+ const SINK_REL = path.join('agents', 'runtime', 'state', 'audit', 'suggestion-capture.jsonl');
57
+ const LATCH_REL = path.join('agents', 'runtime', 'state', 'suggestion-latch.json');
58
+ /** A block older than this cannot be what the current prompt is answering. */
59
+ export const LATCH_TTL_MS = 15 * 60 * 1000;
60
+
61
+ export type Classification = 'option_n' | 'as_is' | 'other' | 'stale_block';
62
+ export type EvidenceClass = 'render-signature' | 'latch-consumed' | 'latch-stale' | 'no-latch';
63
+
64
+ type JsonValue = string | number | boolean | null | JsonValue[] | { [k: string]: JsonValue };
65
+ type JsonObject = { [k: string]: JsonValue };
66
+
67
+ const isObject = (v: unknown): v is JsonObject =>
68
+ typeof v === 'object' && v !== null && !Array.isArray(v);
69
+
70
+ const str = (v: JsonValue | undefined): string => (typeof v === 'string' ? v : '');
71
+
72
+ /* ── the render signature ──────────────────────────────────────────────────── */
73
+
74
+ /**
75
+ * Exactly one `Recommendation:`/`Empfehlung:` line, plus at least two numbered
76
+ * options. Both halves are required: a numbered list alone is ordinary prose,
77
+ * and a `Recommendation:` line alone is a sentence.
78
+ *
79
+ * The label is matched in both languages because `user-interaction`'s Iron Law 1
80
+ * makes a wrong-language label a rule violation, not a variant — so a German
81
+ * session's block carries `Empfehlung:` and a single-language matcher would
82
+ * under-count exactly the sessions the mirror rule produces.
83
+ */
84
+ export function detectBlock(text: string): { emitted: boolean; optionsCount: number } {
85
+ if (text === '') return { emitted: false, optionsCount: 0 };
86
+ const lines = text.split('\n');
87
+ const recommendation = lines.filter((l) => /^\s*(?:\*\*)?(?:Recommendation|Empfehlung)\s*:/i.test(l)).length;
88
+ const numbered = new Set<string>();
89
+ for (const l of lines) {
90
+ const m = /^\s*(\d{1,2})[.)]\s+\S/.exec(l);
91
+ if (m?.[1] !== undefined) numbered.add(m[1]);
92
+ }
93
+ const emitted = recommendation === 1 && numbered.size >= 2;
94
+ return { emitted, optionsCount: emitted ? numbered.size : 0 };
95
+ }
96
+
97
+ /* ── classification ────────────────────────────────────────────────────────── */
98
+
99
+ /**
100
+ * The as-is escape hatch, matched by intent rather than by an exact string:
101
+ * `command-suggestion-policy` requires it present and last but fixes no wording,
102
+ * so a literal matcher would silently classify every as-is pick as `other`.
103
+ */
104
+ const AS_IS_RE = /\b(as[- ]is|as is|unver(?:ä|ae)ndert|so lassen|wie es ist|keep it|leave it|no change)\b/i;
105
+
106
+ export function classify(prompt: string, optionsCount: number): Classification {
107
+ const t = prompt.trim();
108
+ if (t === '') return 'other';
109
+ if (AS_IS_RE.test(t)) return 'as_is';
110
+ // A bare number, or a number with trailing punctuation — the shape a pick
111
+ // actually takes. `1. do the thing` is NOT a pick: it is an instruction that
112
+ // happens to start with a digit, and counting it would inflate the rate.
113
+ const bare = /^(\d{1,2})\s*[.)]?\s*$/.exec(t);
114
+ if (bare?.[1] !== undefined) {
115
+ const n = Number(bare[1]);
116
+ // Out-of-range is `other`: a "7" against a 3-option block is answering
117
+ // something else, and recording it as a pick would corrupt the metric
118
+ // the whole instrument exists to produce.
119
+ if (n >= 1 && (optionsCount === 0 || n <= optionsCount)) return 'option_n';
120
+ }
121
+ return 'other';
122
+ }
123
+
124
+ /* ── settings, latch, sink ─────────────────────────────────────────────────── */
125
+
126
+ /** `hooks.suggestion_capture.enabled: true` — the same mini-parse siblings use. */
127
+ export function enabled(root: string): boolean {
128
+ let raw: string;
129
+ try {
130
+ raw = fs.readFileSync(path.join(root, SETTINGS_FILE), 'utf8');
131
+ } catch {
132
+ return false;
133
+ }
134
+ let inHooks = false;
135
+ let inSection = false;
136
+ for (const line of raw.split('\n')) {
137
+ if (/^hooks:\s*$/.test(line)) {
138
+ inHooks = true;
139
+ inSection = false;
140
+ continue;
141
+ }
142
+ if (/^\S/.test(line)) {
143
+ inHooks = false;
144
+ inSection = false;
145
+ continue;
146
+ }
147
+ if (!inHooks) continue;
148
+ if (/^\s{2}suggestion_capture:\s*$/.test(line)) {
149
+ inSection = true;
150
+ continue;
151
+ }
152
+ if (/^\s{2}\S/.test(line)) {
153
+ inSection = false;
154
+ continue;
155
+ }
156
+ if (inSection && /^\s{4}enabled:\s*true\s*$/.test(line)) return true;
157
+ }
158
+ return false;
159
+ }
160
+
161
+ export interface Latch {
162
+ readonly prompt_id: string;
163
+ readonly options_count: number;
164
+ readonly at: number;
165
+ }
166
+
167
+ export function writeLatch(root: string, latch: Latch): void {
168
+ const p = path.join(root, LATCH_REL);
169
+ fs.mkdirSync(path.dirname(p), { recursive: true });
170
+ fs.writeFileSync(p, `${JSON.stringify(latch)}\n`, 'utf8');
171
+ }
172
+
173
+ /** Read AND delete. Consume-once is the misclassification guard, not a detail. */
174
+ export function consumeLatch(root: string): Latch | null {
175
+ const p = path.join(root, LATCH_REL);
176
+ let raw: string;
177
+ try {
178
+ raw = fs.readFileSync(p, 'utf8');
179
+ } catch {
180
+ return null;
181
+ }
182
+ try {
183
+ fs.rmSync(p, { force: true });
184
+ } catch {
185
+ /* a latch we cannot delete is still consumed for this turn */
186
+ }
187
+ try {
188
+ const v = JSON.parse(raw) as unknown;
189
+ if (!isObject(v)) return null;
190
+ const at = v['at'];
191
+ const oc = v['options_count'];
192
+ const pid = v['prompt_id'];
193
+ if (typeof at !== 'number' || typeof oc !== 'number' || typeof pid !== 'string') return null;
194
+ return { prompt_id: pid, options_count: oc, at };
195
+ } catch {
196
+ return null; // unparseable → stale_block, never a guess
197
+ }
198
+ }
199
+
200
+ export interface Record_ {
201
+ readonly ts: string;
202
+ readonly block_emitted: boolean;
203
+ readonly options_count: number;
204
+ readonly evidence_class: EvidenceClass;
205
+ readonly turn_classification: Classification;
206
+ }
207
+
208
+ /** The exact key set `src/config/suggestion-capture.json` registers. */
209
+ export const RECORD_KEYS: readonly string[] = [
210
+ 'ts',
211
+ 'block_emitted',
212
+ 'options_count',
213
+ 'evidence_class',
214
+ 'turn_classification',
215
+ ];
216
+
217
+ export function appendRecord(root: string, rec: Record_, now: Date): void {
218
+ const p = path.join(root, SINK_REL);
219
+ fs.mkdirSync(path.dirname(p), { recursive: true });
220
+ const line = {
221
+ ts: now.toISOString().replace(/\.\d{3}Z$/, 'Z'),
222
+ block_emitted: rec.block_emitted,
223
+ options_count: rec.options_count,
224
+ evidence_class: rec.evidence_class,
225
+ turn_classification: rec.turn_classification,
226
+ };
227
+ fs.appendFileSync(p, `${JSON.stringify(line)}\n`, 'utf8');
228
+ }
229
+
230
+ /* ── the two slots ─────────────────────────────────────────────────────────── */
231
+
232
+ /** Decide what a prompt turn should record. Pure, so the tests drive it directly. */
233
+ export function classifyTurn(
234
+ prompt: string,
235
+ latch: Latch | null,
236
+ nowMs: number,
237
+ ): { record: boolean; evidence_class: EvidenceClass; turn_classification: Classification; options_count: number } {
238
+ if (latch === null) {
239
+ // No block preceded this turn. Recording every ordinary turn would make
240
+ // the sink a prompt log with the volume of one, so nothing is written.
241
+ return { record: false, evidence_class: 'no-latch', turn_classification: 'other', options_count: 0 };
242
+ }
243
+ if (nowMs - latch.at > LATCH_TTL_MS) {
244
+ return {
245
+ record: true,
246
+ evidence_class: 'latch-stale',
247
+ turn_classification: 'stale_block',
248
+ options_count: latch.options_count,
249
+ };
250
+ }
251
+ return {
252
+ record: true,
253
+ evidence_class: 'latch-consumed',
254
+ turn_classification: classify(prompt, latch.options_count),
255
+ options_count: latch.options_count,
256
+ };
257
+ }
258
+
259
+ /**
260
+ * Canonical event name, from either invocation shape.
261
+ *
262
+ * The dispatcher supplies `envelope.event` (`stop`, `user_prompt_submit`); a
263
+ * direct/legacy invocation carries only the host's own `hook_event_name`
264
+ * (`Stop`, `UserPromptSubmit`). Reading just the second one is how the first
265
+ * version of this concern became a silent no-op under the dispatcher: it ran,
266
+ * matched nothing, and exited 0 — indistinguishable from a disabled hook.
267
+ */
268
+ export function eventOf(envelope: JsonObject, payload: JsonObject): string {
269
+ const canonical = str(envelope['event'] as JsonValue | undefined);
270
+ const native = str(
271
+ (envelope['native_event'] ?? payload['hook_event_name']) as JsonValue | undefined,
272
+ );
273
+ const raw = (canonical !== '' ? canonical : native).toLowerCase();
274
+ if (raw === 'stop') return 'stop';
275
+ if (raw === 'userpromptsubmit' || raw === 'user_prompt_submit') return 'user_prompt_submit';
276
+ return raw;
277
+ }
278
+
279
+ /**
280
+ * The dispatcher calls every in-process concern as `main(argv)` — see
281
+ * `_run_concern_inproc` in `hooks/dispatch_hook.ts`, and the `ConcernMain`
282
+ * signature in `hooks/concern_registry.ts`.
283
+ *
284
+ * This used to be `main(now: Date = new Date())`, and the mismatch made the
285
+ * concern a SILENT NO-OP on every live dispatch: argv arrived where a Date was
286
+ * expected, `now.getTime()` threw `TypeError: now.getTime is not a function`,
287
+ * and the outer catch — "an instrument never breaks the turn it observes" —
288
+ * swallowed it and returned EXIT_ALLOW. The dispatcher recorded exit 0 and no
289
+ * output, which is indistinguishable from a disabled hook. Three earlier
290
+ * isolation attempts read that silence as a `--project-dir` problem; it was
291
+ * this.
292
+ *
293
+ * The clock stays injectable because the TTL and the timestamp need to be
294
+ * driven from a test, but it is now the SECOND parameter, where the dispatcher
295
+ * cannot land on it.
296
+ */
297
+ export function main(_argv: readonly string[] = [], now: Date = new Date()): number {
298
+ let envelope: JsonObject;
299
+ let payload: JsonObject;
300
+ try {
301
+ [envelope, payload] = unwrap(readHookStdin(), 'claude');
302
+ } catch {
303
+ return EXIT_ALLOW;
304
+ }
305
+
306
+ // `workspace_root` is the dispatcher's; `cwd` is the host payload's. Both
307
+ // are read because the concern must work under either invocation.
308
+ const root =
309
+ str(envelope['workspace_root'] as JsonValue | undefined) ||
310
+ str(payload['cwd'] as JsonValue | undefined) ||
311
+ str(envelope['project_root'] as JsonValue | undefined) ||
312
+ '.';
313
+ if (!enabled(root)) return EXIT_ALLOW;
314
+
315
+ const event = eventOf(envelope, payload);
316
+ try {
317
+ if (event === 'stop') {
318
+ const { emitted, optionsCount } = detectBlock(
319
+ str(payload['last_assistant_message'] as JsonValue | undefined),
320
+ );
321
+ if (emitted) {
322
+ writeLatch(root, {
323
+ prompt_id: str(payload['prompt_id'] as JsonValue | undefined),
324
+ options_count: optionsCount,
325
+ at: now.getTime(),
326
+ });
327
+ }
328
+ return EXIT_ALLOW;
329
+ }
330
+ if (event === 'user_prompt_submit') {
331
+ const latch = consumeLatch(root);
332
+ const v = classifyTurn(str(payload['prompt'] as JsonValue | undefined), latch, now.getTime());
333
+ if (v.record) {
334
+ appendRecord(
335
+ root,
336
+ {
337
+ ts: '',
338
+ block_emitted: true,
339
+ options_count: v.options_count,
340
+ evidence_class: v.evidence_class,
341
+ turn_classification: v.turn_classification,
342
+ },
343
+ now,
344
+ );
345
+ }
346
+ return EXIT_ALLOW;
347
+ }
348
+ } catch {
349
+ return EXIT_ALLOW; // an instrument never breaks the turn it observes
350
+ }
351
+ return EXIT_ALLOW;
352
+ }
353
+
354
+ declare const __AGENT_CONFIG_BUNDLE__: boolean | undefined;
355
+ function isCliEntry(): boolean {
356
+ if (typeof __AGENT_CONFIG_BUNDLE__ !== 'undefined' && __AGENT_CONFIG_BUNDLE__) return false;
357
+ if (process.argv[1] === undefined) return false;
358
+ const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
359
+ if (import.meta.url === argvUrl) return true;
360
+ try {
361
+ return fs.realpathSync(fileURLToPath(import.meta.url)) === fs.realpathSync(path.resolve(process.argv[1]));
362
+ } catch {
363
+ return false;
364
+ }
365
+ }
366
+ if (isCliEntry()) process.exit(main());
367
+
368
+ export { SINK_REL, LATCH_REL };
@@ -28,6 +28,7 @@
28
28
  * Exit codes: 0 clean · 1 findings · 2 internal error.
29
29
  */
30
30
  import * as fs from 'node:fs';
31
+ import { parse as parseYaml } from 'yaml';
31
32
  import * as path from 'node:path';
32
33
  import { fileURLToPath } from 'node:url';
33
34
 
@@ -122,20 +123,69 @@ export function checkBudgetRows(relPath: string, doc: Record<string, unknown>):
122
123
  return errors;
123
124
  }
124
125
 
126
+ /**
127
+ * Non-JSON budget files this gate governs, named ONE BY ONE.
128
+ *
129
+ * road-to-standing-payload-truth `b-budgets-yml-outside-ownership`: `budgets.yml`
130
+ * carries a `standing_rule_delivery` entry that gates the larger of the two red
131
+ * payload numbers, and it had no `owner` and no `review_by` because this gate
132
+ * filtered on `.json`.
133
+ *
134
+ * **An explicit list, NOT a widened glob**, and both council seats (2026-08-24)
135
+ * refused the glob for the same two reasons:
136
+ *
137
+ * 1. `*budget*.json` **IS** this gate's corpus definition (see `main`'s note), so
138
+ * widening it to `*budget*.{json,yml}` means any future `*budget*.yml` dropped
139
+ * into `src/config/` joins the governed set **silently**. A governed file
140
+ * should be a decision, not a filename coincidence.
141
+ * 2. It could not have worked as written anyway: the read below was an
142
+ * unconditional `JSON.parse`, so a YAML file entering through a widened glob
143
+ * would have been reported as `unparseable JSON` — a red gate blaming the
144
+ * file for the gate's own bug. Parsing is now dispatched on extension.
145
+ *
146
+ * Adding a row here is the whole cost of governing a new non-JSON budget, and
147
+ * `lint_budget_ownership.test.ts` asserts an unlisted `*budget*.yml` stays OUT.
148
+ */
149
+ export const GOVERNED_NON_JSON: readonly string[] = ['budgets.yml'];
150
+
125
151
  export function budgetFiles(dir: string = CONFIG_DIR): string[] {
126
152
  try {
127
- return fs
153
+ const globbed = fs
128
154
  .readdirSync(dir)
129
155
  .filter((f) => f.endsWith('.json') && f.toLowerCase().includes('budget'))
130
156
  .map((f) => path.join(dir, f));
157
+ const named = GOVERNED_NON_JSON.map((f) => path.join(dir, f)).filter((abs) =>
158
+ fs.existsSync(abs),
159
+ );
160
+ return [...globbed, ...named];
131
161
  } catch {
132
162
  return [];
133
163
  }
134
164
  }
135
165
 
166
+ /**
167
+ * Parse a budget file by EXTENSION.
168
+ *
169
+ * The previous unconditional `JSON.parse` is the defect that made a widened glob
170
+ * unworkable; keeping it while adding a `.yml` row would have turned this gate
171
+ * red on the very file it was extended to govern.
172
+ */
173
+ export function parseBudgetDoc(abs: string): Record<string, unknown> {
174
+ const text = fs.readFileSync(abs, 'utf-8');
175
+ if (abs.endsWith('.yml') || abs.endsWith('.yaml')) {
176
+ const doc = parseYaml(text) as unknown;
177
+ if (doc === null || typeof doc !== 'object' || Array.isArray(doc)) {
178
+ throw new Error('top level is not a mapping');
179
+ }
180
+ return doc as Record<string, unknown>;
181
+ }
182
+ return JSON.parse(text) as Record<string, unknown>;
183
+ }
184
+
136
185
  export function main(): number {
137
186
  const files = budgetFiles();
138
- // `*budget*.json` IS this gate's corpus definition, not a content-derived
187
+ // `*budget*.json` PLUS the explicitly named `GOVERNED_NON_JSON` rows IS this
188
+ // gate's corpus definition, not a content-derived
139
189
  // subset: zero matches means src/config/ moved or the budgets were deleted,
140
190
  // and the old "nothing to lint" exit 0 could not tell either from a healthy
141
191
  // tree. Of the two documented failure codes, 1 is the one the gate actually
@@ -160,9 +210,9 @@ export function main(): number {
160
210
  const rel = path.relative(REPO_ROOT, abs);
161
211
  let doc: Record<string, unknown>;
162
212
  try {
163
- doc = JSON.parse(fs.readFileSync(abs, 'utf-8')) as Record<string, unknown>;
213
+ doc = parseBudgetDoc(abs);
164
214
  } catch (e) {
165
- errors.push(`${rel}: unparseable JSON (${(e as Error).message})`);
215
+ errors.push(`${rel}: unparseable budget file (${(e as Error).message})`);
166
216
  continue;
167
217
  }
168
218
  errors.push(...checkBudgetDoc(rel, doc));