@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
@@ -32,8 +32,10 @@
32
32
  * Dates are plain `YYYY-MM-DD`; ages are whole days in UTC, so the verdict does
33
33
  * not depend on the machine's timezone.
34
34
  */
35
+ import { execFileSync } from 'node:child_process';
35
36
  import * as fs from 'node:fs';
36
37
  import * as path from 'node:path';
38
+ import process from 'node:process';
37
39
 
38
40
  /** Days a baseline may sit unchanged before it must be fixed or reaffirmed. */
39
41
  export const STALE_AFTER_DAYS = 56;
@@ -105,12 +107,132 @@ export function loadBaselines(repoRoot: string, relPath: string = BASELINE_REL):
105
107
  } catch {
106
108
  return { gates: {} };
107
109
  }
110
+ return parseBaselines(raw);
111
+ }
112
+
113
+ function parseBaselines(raw: string): BaselineFile {
108
114
  const parsed = JSON.parse(raw) as Partial<BaselineFile>;
109
115
  const out: BaselineFile = { gates: parsed.gates ?? {} };
110
116
  if (parsed.$comment !== undefined) out.$comment = parsed.$comment;
111
117
  return out;
112
118
  }
113
119
 
120
+ /**
121
+ * A baseline-resolution failure, which is a HARD error and never a fallback.
122
+ *
123
+ * `loadBaselines` returns an empty ratchet when the file is missing, and that
124
+ * is correct for a working-tree read: a repository with no baseline file has no
125
+ * ratchet. It is NOT correct for a target-commit read. There, a failure to
126
+ * resolve the ref or the blob means the governing policy could not be
127
+ * determined — and silently falling back to the working tree would let an
128
+ * infrastructure error change which policy applies, invisibly. The AI council
129
+ * was unanimous on this point (2026-08-25) and stated it applies to whichever
130
+ * invariant won.
131
+ */
132
+ export class BaselineResolutionError extends Error {
133
+ constructor(message: string) {
134
+ super(message);
135
+ this.name = 'BaselineResolutionError';
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Read the baseline as it stands at a specific commit.
141
+ *
142
+ * `road-to-merge-surface-zero` Phase 3.1, under the **ABSOLUTE invariant** the
143
+ * AI council picked 2/2 on 2026-08-25 to resolve blocker B5.
144
+ *
145
+ * ## Why the target commit and not the merge base
146
+ *
147
+ * The merge-base read was the roadmap's original proposal and it selects the
148
+ * *contribution* invariant: a PR passes if it did not worsen the state it
149
+ * branched from. The worked example that killed it uses numbers this repository
150
+ * actually carries — `main` tightens `ci-parity:local-only` 165 → 160, a PR that
151
+ * branched earlier measures 163, the merge-base read returns 165, the PR
152
+ * **passes**, and after it merges `main` measures 163 against a baseline of 160.
153
+ * **A tightening is undone by a PR that never touched the baseline file and
154
+ * never saw a red.**
155
+ *
156
+ * The remedy of checking both was refused on a correctness ground rather than a
157
+ * preference: **violation counts are not necessarily compositional.** Conflict
158
+ * resolution, file movement, generated outputs and interactions with changes on
159
+ * `main` can make a prospective merge regress even when the isolated PR delta is
160
+ * non-positive. If trunk health is the invariant, the merge RESULT is what has
161
+ * to be measured.
162
+ *
163
+ * ## Why the TARGET commit specifically, and not the merge tree
164
+ *
165
+ * The count is measured on the prospective merge tree; the **policy** is read
166
+ * from the target. Reading the baseline out of the merge tree would let a PR
167
+ * loosen the very number it is being judged against, in the same diff.
168
+ *
169
+ * @throws BaselineResolutionError when the ref or the blob cannot be resolved.
170
+ */
171
+ export function loadBaselinesAt(
172
+ repoRoot: string,
173
+ targetRef: string,
174
+ relPath: string = BASELINE_REL,
175
+ run: (args: string[]) => string = (args) =>
176
+ execFileSync('git', args, { cwd: repoRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }),
177
+ ): BaselineFile {
178
+ let sha: string;
179
+ try {
180
+ sha = run(['rev-parse', '--verify', `${targetRef}^{commit}`]).trim();
181
+ } catch {
182
+ throw new BaselineResolutionError(
183
+ `cannot resolve target ref "${targetRef}" — the governing baseline is unknown, ` +
184
+ 'and a working-tree fallback would let an infrastructure error decide the policy.',
185
+ );
186
+ }
187
+ let raw: string;
188
+ try {
189
+ raw = run(['show', `${sha}:${relPath}`]);
190
+ } catch {
191
+ throw new BaselineResolutionError(
192
+ `${relPath} does not exist at ${sha.slice(0, 9)} ("${targetRef}") — this is a ` +
193
+ 'resolution failure, NOT an empty ratchet. A missing blob at a named commit ' +
194
+ 'means the read was wrong, not that the ratchet is empty.',
195
+ );
196
+ }
197
+ try {
198
+ return parseBaselines(raw);
199
+ } catch {
200
+ throw new BaselineResolutionError(
201
+ `${relPath} at ${sha.slice(0, 9)} is not parseable JSON — refusing to continue ` +
202
+ 'with an unknown policy.',
203
+ );
204
+ }
205
+ }
206
+
207
+ /**
208
+ * The merge-base reading, kept as a DIAGNOSTIC and never as the verdict.
209
+ *
210
+ * Both council seats asked for this explicitly. It does not decide pass/fail; it
211
+ * distinguishes two failures a reader cannot otherwise tell apart:
212
+ *
213
+ * - `branch-regression` — the branch itself made the count worse. Fix the code.
214
+ * - `main-tightened` — the branch is unchanged and `main` improved underneath
215
+ * it. Rebase, then re-run.
216
+ *
217
+ * The distinction matters because the remediations differ, and one seat was
218
+ * pointed about the wording: "rebase and re-run" is incomplete advice for a PR
219
+ * that genuinely reintroduced violations, so a diagnostic must say which case it
220
+ * is rather than always suggesting a rebase.
221
+ */
222
+ export type RegressionCause = 'branch-regression' | 'main-tightened' | 'none';
223
+
224
+ export function diagnoseRegression(opts: {
225
+ actual: number;
226
+ targetBaseline: number;
227
+ mergeBaseBaseline: number | null;
228
+ }): RegressionCause {
229
+ if (opts.actual <= opts.targetBaseline) return 'none';
230
+ if (opts.mergeBaseBaseline === null) return 'branch-regression';
231
+ // The branch is at or under the policy it branched from, and it still
232
+ // exceeds the target's policy: main tightened while this work was open.
233
+ return opts.actual <= opts.mergeBaseBaseline ? 'main-tightened' : 'branch-regression';
234
+ }
235
+
114
236
  export interface RatchetOptions {
115
237
  /** Gate name — the key in the baseline file. */
116
238
  gate: string;
@@ -122,6 +244,12 @@ export interface RatchetOptions {
122
244
  baselineRel?: string;
123
245
  /** Override "now" as `YYYY-MM-DD` (tests); defaults to the current UTC date. */
124
246
  today?: string;
247
+ /**
248
+ * Read the governing baseline from this commit instead of the working tree.
249
+ * Defaults to `process.env[TARGET_REF_ENV]`; absent means a local run with
250
+ * no merge to judge, which is not the same as a failed resolution.
251
+ */
252
+ targetRef?: string;
125
253
  }
126
254
 
127
255
  /**
@@ -131,9 +259,30 @@ export interface RatchetOptions {
131
259
  * does not print or exit. A gate with no entry behaves exactly as it did before
132
260
  * the ratchet existed: any violation fails.
133
261
  */
262
+ /**
263
+ * The env var that switches every ratchet onto the ABSOLUTE invariant at once.
264
+ *
265
+ * Set it to the target ref (`origin/main`, or the base SHA a PR job already
266
+ * knows) and all 18 `checkRatchet` call sites read the governing baseline from
267
+ * that commit instead of the working tree — no per-gate migration, no chance of
268
+ * migrating seventeen and forgetting one.
269
+ *
270
+ * **Unset is not a fallback.** A local run with no target configured has no
271
+ * merge to judge, so the working-tree read is the right and only answer there.
272
+ * What the council forbade is different and is honoured: a target that IS
273
+ * configured and fails to resolve throws, rather than quietly reverting to the
274
+ * working tree and changing the governing policy on an infrastructure error.
275
+ */
276
+ export const TARGET_REF_ENV = 'GATE_BASELINE_TARGET_REF';
277
+
134
278
  export function checkRatchet(opts: RatchetOptions): BaselineVerdict {
135
279
  const { gate, actual } = opts;
136
- const file = loadBaselines(opts.repoRoot, opts.baselineRel ?? BASELINE_REL);
280
+ const rel = opts.baselineRel ?? BASELINE_REL;
281
+ const target = opts.targetRef ?? process.env[TARGET_REF_ENV];
282
+ const file =
283
+ target !== undefined && target !== ''
284
+ ? loadBaselinesAt(opts.repoRoot, target, rel)
285
+ : loadBaselines(opts.repoRoot, rel);
137
286
  const entry = file.gates[gate];
138
287
 
139
288
  if (entry === undefined) {
@@ -0,0 +1,115 @@
1
+ /**
2
+ * The skill corpus, measured — the two dimensions `check_estate_count` ratchets.
3
+ *
4
+ * WHY TWO. A file count is what a `one_in_one_out` lint can express; it is not
5
+ * what a skill COSTS. Merging four large skills into one file satisfies a count
6
+ * ratchet while the description payload a host must carry stays put or grows.
7
+ * AI council 2/2 (2026-08-24) asked for both dimensions in one gate for exactly
8
+ * that reason, over the roadmap's own recommendation of a count plus an
9
+ * informational companion figure: "that supports treating tokens as a coarse
10
+ * secondary budget — not leaving the only cost-sensitive metric informational
11
+ * forever."
12
+ *
13
+ * WHY NOT THE OTHER TWO CANDIDATES, measured before asking rather than argued:
14
+ *
15
+ * - **Catalogue bytes** via `_lib/skill_catalogue.ts` read **0** on this
16
+ * checkout. `readProjectedCatalogue` walks `.claude/skills`, which is empty in
17
+ * any tree where `task generate-tools` cannot complete — it fails under
18
+ * `projection.mode=scoped` without the config package. A ratchet whose reading
19
+ * depends on whether a generator ran is a ratchet that reds for the
20
+ * environment.
21
+ * - **Host-listing slots** are the host's decision, not the repository's. A
22
+ * measured install published its own budget event stating it had stripped
23
+ * every description and dropped 402 entries.
24
+ *
25
+ * DEPRECATED SKILLS ARE EXCLUDED, and that is load-bearing rather than tidy:
26
+ * with them counted, deprecating a skill would create no headroom and the
27
+ * retirement mechanism would be unusable against its own gate. (Currently 0
28
+ * skills carry it, so the exclusion is a no-op today and a correctness property
29
+ * tomorrow.)
30
+ */
31
+ import * as fs from 'node:fs';
32
+ import * as path from 'node:path';
33
+
34
+ import { gpt_tokens } from './token_count.js';
35
+
36
+ /** Where the corpus lives, POSIX — the same string a base-tree subtree takes. */
37
+ export const SKILLS_POSIX = 'src/skills';
38
+
39
+ export interface SkillEstate {
40
+ /** Maintained `SKILL.md` files — `lifecycle: deprecated` excluded. */
41
+ skill_count: number;
42
+ /**
43
+ * Exact-BPE tokens across the maintained skills' `description:` fields.
44
+ *
45
+ * `null` when the tokeniser could not resolve. A ratchet must never compare
46
+ * an exact reading against a proxy one — the numbers differ by more than the
47
+ * growth a gate is trying to catch, so the metric is reported UNRESOLVED
48
+ * rather than measured with whatever was available.
49
+ */
50
+ skill_description_tokens: number | null;
51
+ /** Excluded from both figures, reported so the exclusion is visible. */
52
+ deprecated_count: number;
53
+ }
54
+
55
+ /** `description:` from a `SKILL.md` frontmatter block, unquoted. */
56
+ export function descriptionOf(text: string): string {
57
+ const fm = /^---\n([\s\S]*?)\n---/.exec(text);
58
+ if (fm === null) return '';
59
+ const m = /^description:\s*(.+)$/m.exec(fm[1]!);
60
+ if (m === null) return '';
61
+ return m[1]!.trim().replace(/^["']|["']$/g, '');
62
+ }
63
+
64
+ /** `lifecycle: deprecated` in the frontmatter — the retirement transition state. */
65
+ export function isDeprecated(text: string): boolean {
66
+ const fm = /^---\n([\s\S]*?)\n---/.exec(text);
67
+ if (fm === null) return false;
68
+ return /^lifecycle:\s*["']?deprecated["']?\s*$/m.test(fm[1]!);
69
+ }
70
+
71
+ /**
72
+ * Measure the corpus under `root`.
73
+ *
74
+ * `root` is a repository root OR a materialised base-ref tree holding
75
+ * `src/skills/...`, so the floor side runs this exact function over the base
76
+ * ref's own tree — the property `check_estate_count` already has for the roadmap
77
+ * estate, and the reason the "before" side cannot be rewritten by the change
78
+ * under review.
79
+ */
80
+ export function measureSkillEstate(root: string): SkillEstate {
81
+ const base = path.join(root, ...SKILLS_POSIX.split('/'));
82
+ let entries: fs.Dirent[];
83
+ try {
84
+ entries = fs.readdirSync(base, { withFileTypes: true });
85
+ } catch {
86
+ return { skill_count: 0, skill_description_tokens: null, deprecated_count: 0 };
87
+ }
88
+ let count = 0;
89
+ let deprecated = 0;
90
+ let tokens = 0;
91
+ let exact = true;
92
+ for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
93
+ if (!e.isDirectory()) continue;
94
+ const p = path.join(base, e.name, 'SKILL.md');
95
+ let text: string;
96
+ try {
97
+ text = fs.readFileSync(p, 'utf-8');
98
+ } catch {
99
+ continue;
100
+ }
101
+ if (isDeprecated(text)) {
102
+ deprecated += 1;
103
+ continue;
104
+ }
105
+ count += 1;
106
+ const r = gpt_tokens(descriptionOf(text));
107
+ if (!r.exact) exact = false;
108
+ tokens += r.tokens;
109
+ }
110
+ return {
111
+ skill_count: count,
112
+ skill_description_tokens: exact ? tokens : null,
113
+ deprecated_count: deprecated,
114
+ };
115
+ }
@@ -0,0 +1,130 @@
1
+ /**
2
+ * The argument-exhaustion stop predicate, with the ordering encoded.
3
+ *
4
+ * `road-to-inbox-harvest-2026-08-e-council-topology-evidence` Phase 6.2, and the
5
+ * resolution of its `early-stop-vs-dissent-ordering` blocker.
6
+ *
7
+ * ## The ordering IS the substance
8
+ *
9
+ * Anti-conformity repair must fire **before** the cost stop, because
10
+ * **convergence may be conformity collapse rather than solution stability**. A
11
+ * round where everyone agrees because nobody pushed back looks, to a cost meter,
12
+ * exactly like a round where the argument is genuinely finished — and stopping
13
+ * there reports the collapse as consensus. That is the failure Phase 6 exists to
14
+ * prevent, so `dissentRepairAttempted` is a **required conjunct of the
15
+ * predicate**, not a soft check somewhere upstream that the predicate hopes ran.
16
+ *
17
+ * Encoding it as a conjunct rather than as call-order discipline is deliberate:
18
+ * a caller that forgets the order produces a wrong answer silently, while a
19
+ * caller that forgets to set the field produces `false` — the safe direction,
20
+ * and a visible one.
21
+ *
22
+ * ## Why all four, and why none of them is majority size
23
+ *
24
+ * A stop fires only when **every** condition holds:
25
+ *
26
+ * 1. `roundsCompleted >= MIN_ROUNDS` — one round is an opening statement, not an
27
+ * argument. There is nothing to be exhausted yet.
28
+ * 2. `dissentRepairAttempted` — the conformity defence got its chance.
29
+ * 3. every present member is a self-near-duplicate of their own prior round —
30
+ * the members have stopped adding, judged by the novelty logic that already
31
+ * exists rather than by a new similarity measure.
32
+ * 4. no unresolved adversarial trigger — an open objection is unfinished
33
+ * argument whatever the novelty numbers say.
34
+ *
35
+ * **Majority size is absent on purpose** (6.3). Unanimity is the single most
36
+ * available signal and the single least trustworthy one here: it is exactly what
37
+ * conformity collapse produces. A predicate that read it would stop soonest in
38
+ * precisely the case it must not.
39
+ *
40
+ * Everything here is pure and reads already-paid outputs — no model call, which
41
+ * is 6.1's contract and the reason this can be evaluated after every round for
42
+ * free.
43
+ */
44
+
45
+ /** One round is an opening statement; exhaustion needs an exchange. */
46
+ export const MIN_ROUNDS = 2;
47
+
48
+ export interface StopInputs {
49
+ /** Rounds completed so far. */
50
+ roundsCompleted: number;
51
+ /**
52
+ * Whether anti-conformity repair ran for the round under judgement.
53
+ *
54
+ * The ordering conjunct. `false` when repair was skipped, deferred, or
55
+ * never reached — all three are the same thing to this predicate, because
56
+ * all three leave conformity collapse untested.
57
+ */
58
+ dissentRepairAttempted: boolean;
59
+ /**
60
+ * Per present member: did this round near-duplicate that member's own prior
61
+ * round? Absent members contribute no entry rather than a `false`.
62
+ */
63
+ selfNearDuplicate: readonly boolean[];
64
+ /** Adversarial triggers raised and not yet resolved. */
65
+ unresolvedAdversarialTriggers: number;
66
+ }
67
+
68
+ export type StopBlocker =
69
+ | 'too-few-rounds'
70
+ | 'dissent-repair-not-attempted'
71
+ | 'members-still-adding'
72
+ | 'unresolved-adversarial-trigger'
73
+ | 'no-members-present';
74
+
75
+ export interface StopVerdict {
76
+ stop: boolean;
77
+ /** Every condition that failed, not just the first — a caller acting on one
78
+ * reason at a time would re-evaluate N times to learn N blockers. */
79
+ blockers: StopBlocker[];
80
+ }
81
+
82
+ export function evaluateStop(x: StopInputs): StopVerdict {
83
+ const blockers: StopBlocker[] = [];
84
+
85
+ if (x.roundsCompleted < MIN_ROUNDS) blockers.push('too-few-rounds');
86
+
87
+ // The ordering conjunct. Deliberately evaluated as its own condition rather
88
+ // than folded into a caller-side guard: see the module docstring.
89
+ if (!x.dissentRepairAttempted) blockers.push('dissent-repair-not-attempted');
90
+
91
+ if (x.selfNearDuplicate.length === 0) {
92
+ // No present member is not "everyone is done" — it is no evidence at
93
+ // all. `every()` over an empty array is vacuously true, which would
94
+ // make an empty council the easiest one to stop.
95
+ blockers.push('no-members-present');
96
+ } else if (!x.selfNearDuplicate.every((d) => d)) {
97
+ blockers.push('members-still-adding');
98
+ }
99
+
100
+ if (x.unresolvedAdversarialTriggers > 0) blockers.push('unresolved-adversarial-trigger');
101
+
102
+ return { stop: blockers.length === 0, blockers };
103
+ }
104
+
105
+ /**
106
+ * The rendering contract for a stopped run (6.4).
107
+ *
108
+ * A stopped run must be **textually distinguishable** from a full one. The
109
+ * failure this prevents is quiet and expensive: an artifact that reads as though
110
+ * all configured rounds executed, when three of five did, is a claim about
111
+ * deliberation depth that nobody made on purpose.
112
+ */
113
+ export interface StopRender {
114
+ roundsCompleted: number;
115
+ roundsConfigured: number;
116
+ savedCalls: number;
117
+ savedCostUsd: number;
118
+ exhaustedMembers: readonly string[];
119
+ }
120
+
121
+ export function renderStop(r: StopRender): string {
122
+ return [
123
+ `council: STOPPED EARLY — round ${String(r.roundsCompleted)} of ${String(r.roundsConfigured)}.`,
124
+ ' reason: argument exhaustion — every present member near-duplicated their own',
125
+ ' prior round, after anti-conformity repair had already been attempted.',
126
+ ` exhausted: ${r.exhaustedMembers.length > 0 ? r.exhaustedMembers.join(', ') : '(none named)'}`,
127
+ ` saved: ${String(r.savedCalls)} call(s), $${r.savedCostUsd.toFixed(4)}`,
128
+ ' NOT a full run: the remaining configured rounds did not execute.',
129
+ ].join('\n');
130
+ }
@@ -1,6 +1,41 @@
1
1
  #!/usr/bin/env tsx
2
2
  /**
3
- * Guard against build-machine path leakage in the tracked install bundle.
3
+ * Guard against build-machine path leakage in the tracked install bundle AND in
4
+ * published Markdown.
5
+ *
6
+ * ## The published-.md scope (road-to-inbox-harvest-2026-08-e-command-surface-legibility Phase 0)
7
+ *
8
+ * The bundle roots below were the original scope. A second population ships the
9
+ * same leak class: every `.md` inside `package.json` `files[]` — 1,079 files —
10
+ * and a `/Users/<name>/…` in a shipped skill is the same defect as one in
11
+ * `install.mjs`.
12
+ *
13
+ * **It is EXTENDED here rather than given a second gate**, deliberately: this one
14
+ * already runs in CI, already carries the patterns, the username masking and the
15
+ * `scan_scope` dead-scope protection, and a sibling gate would duplicate all
16
+ * four.
17
+ *
18
+ * ### Why the floor is ZERO UNAPPROVED and not the measured 11
19
+ *
20
+ * The published-.md baseline measured **11 hits in 8 files, none of them a leak**
21
+ * (`agents/evidence/analysis/published-md-path-leakage-baseline.md`): three
22
+ * documentation examples, six occurrences inside the rules that FORBID the
23
+ * pattern and must quote it, two legitimately absolute paths.
24
+ *
25
+ * A numeric floor of 11 was rejected by both council seats, 2026-08-24, for one
26
+ * reason: it lets an approved hit disappear while a real leak takes its slot, and
27
+ * the count stays 11. So the floor is **0 unapproved matches**, and the eleven are
28
+ * pinned exceptions in `.path-leak-allow` — line-pinned, so an entry that drifts
29
+ * off its line stops matching and the gate REDS, which is the safe direction.
30
+ *
31
+ * ### And why NOT "exempt matches inside backticks"
32
+ *
33
+ * That was the other candidate, and it was rejected on a false-negative argument
34
+ * both seats found decisive: **a real leaked path is commonly formatted as code**,
35
+ * so exempting inline code and fenced blocks opens a channel that hides exactly
36
+ * the defect this gate exists to catch. It would also hide a skill author's
37
+ * accidental real path inside an example block — the contribution path where this
38
+ * class is most likely to arrive.
4
39
  *
5
40
  * road-to-feedback-9.2.0-followups Phase 4.1. Three 9.1 commits
6
41
  * (f8752443b, 5933bf1a9, f30adb5a1) each rebuilt `dist/install/install.mjs`
@@ -75,6 +110,40 @@ const BUNDLE_ROOTS: readonly string[] = [
75
110
  'dist/cli-delegate',
76
111
  ];
77
112
 
113
+ /**
114
+ * Published `.md` — the second population, derived from `package.json` `files[]`
115
+ * rather than guessed, so the scope is what the tarball actually ships. Roots
116
+ * only; the per-file filter below keeps it to `.md`.
117
+ */
118
+ const PUBLISHED_MD_ROOTS: readonly string[] = [
119
+ 'dist/agent-src',
120
+ 'docs/guidelines',
121
+ 'src/scripts',
122
+ // NOT `src/agent-src` wholesale: `files[]` ships only these two subtrees, and
123
+ // scanning the parent would gate content no consumer receives. Caught by this
124
+ // gate's own test, which asserts every root is inside `files[]`.
125
+ 'src/agent-src/scripts',
126
+ 'src/agent-src/templates/scripts',
127
+ 'agents/templates',
128
+ 'src/templates',
129
+ 'src/config',
130
+ ];
131
+
132
+ /** Named `.md` / text files `files[]` ships individually. */
133
+ const PUBLISHED_MD_FILES: readonly string[] = [
134
+ 'AGENTS.md',
135
+ 'CHANGELOG.md',
136
+ 'CONTRIBUTING.md',
137
+ 'MIGRATION.md',
138
+ 'README.md',
139
+ 'docs/contracts/persona-schema.md',
140
+ 'docs/contracts/provider-lifecycle.md',
141
+ 'docs/contracts/settings-classes.md',
142
+ ];
143
+
144
+ /** Repo-root allow file for audited published-`.md` exceptions. */
145
+ const ALLOW_FILE = '.path-leak-allow';
146
+
78
147
  interface LeakPattern {
79
148
  readonly name: string;
80
149
  readonly re: RegExp;
@@ -192,6 +261,49 @@ function _splitlines(text: string): string[] {
192
261
  return text.split('\n').filter((l) => l.length > 0);
193
262
  }
194
263
 
264
+ /**
265
+ * Parse `.path-leak-allow` into `path:line` keys.
266
+ *
267
+ * Line-pinned on purpose, and the trade-off is the same one `.secret-allow`
268
+ * documents for itself: an entry that drifts off its line stops matching and the
269
+ * gate REDS. That is the safe direction — someone re-audits and moves the pin —
270
+ * whereas a path-only entry would silently allow a real leak anywhere in the
271
+ * file. This tree has already been bitten by the opposite: a `.secret-allow` pin
272
+ * sat one line off on `main` for a day, covering nothing, invisible because that
273
+ * gate is diff-scoped.
274
+ */
275
+ export function parse_allow_file(text: string): Set<string> {
276
+ const out = new Set<string>();
277
+ for (const raw of text.split('\n')) {
278
+ const line = raw.replace(/#.*$/, '').trim();
279
+ if (line === '') continue;
280
+ out.add(line);
281
+ }
282
+ return out;
283
+ }
284
+
285
+ function allow_set(): Set<string> {
286
+ try {
287
+ return parse_allow_file(fs.readFileSync(path.join(REPO, ALLOW_FILE), 'utf-8'));
288
+ } catch {
289
+ return new Set();
290
+ }
291
+ }
292
+
293
+ /** Every tracked `.md` inside the published roots, plus the named files. */
294
+ function tracked_published_md(): string[] {
295
+ const seen = new Set<string>();
296
+ for (const root of PUBLISHED_MD_ROOTS) {
297
+ for (const rel of _splitlines(_git(['ls-files', root]))) {
298
+ if (rel.endsWith('.md')) seen.add(rel);
299
+ }
300
+ }
301
+ for (const rel of PUBLISHED_MD_FILES) {
302
+ for (const got of _splitlines(_git(['ls-files', rel]))) seen.add(got);
303
+ }
304
+ return [...seen].sort();
305
+ }
306
+
195
307
  /** Every tracked path under the bundle roots, deduped and sorted. */
196
308
  function tracked_bundle_files(): string[] {
197
309
  const seen = new Set<string>();
@@ -291,7 +403,12 @@ function parse_args(argv: readonly string[]): Options {
291
403
 
292
404
  function main(argv: readonly string[] = process.argv.slice(2)): number {
293
405
  const opts = parse_args(argv);
294
- const targets = opts.files.length > 0 ? opts.files : tracked_bundle_files();
406
+ const bundle = opts.files.length > 0 ? opts.files : tracked_bundle_files();
407
+ // Published `.md` joins the default target list. With explicit `--file`
408
+ // arguments it does NOT — an ad-hoc single-file run stays a single-file run,
409
+ // which is what the existing tests and the pre-push path rely on.
410
+ const published = opts.files.length > 0 ? [] : tracked_published_md();
411
+ const targets = [...bundle, ...published];
295
412
 
296
413
  // `git ls-files <untracked-root>` returns nothing, by design (BUNDLE_ROOTS
297
414
  // lists roots that may not be tracked yet). That tolerance is also the
@@ -305,8 +422,11 @@ function main(argv: readonly string[] = process.argv.slice(2)): number {
305
422
  assertScanned({
306
423
  gate: 'check_bundle_path_leakage',
307
424
  scanned: targets.length,
308
- units: 'bundle file(s)',
309
- roots: opts.files.length > 0 ? ['<explicit file arguments>'] : BUNDLE_ROOTS,
425
+ units: 'bundle + published-md file(s)',
426
+ roots:
427
+ opts.files.length > 0
428
+ ? ['<explicit file arguments>']
429
+ : [...BUNDLE_ROOTS, ...PUBLISHED_MD_ROOTS],
310
430
  });
311
431
  } catch (exc) {
312
432
  if (exc instanceof DeadScopeError) {
@@ -316,17 +436,48 @@ function main(argv: readonly string[] = process.argv.slice(2)): number {
316
436
  throw exc;
317
437
  }
318
438
 
319
- const hits = scan_files(targets);
439
+ const allowed = allow_set();
440
+ const all = scan_files(targets);
441
+ // The floor is ZERO UNAPPROVED, never a count. A pinned exception that has
442
+ // drifted off its line simply stops matching, so the hit resurfaces and the
443
+ // gate reds — deliberately, per the header.
444
+ const hits = all.filter((h) => !allowed.has(`${h.file}:${String(h.line)}`));
445
+ const suppressed = all.length - hits.length;
320
446
 
321
447
  if (hits.length > 0) {
322
448
  process.stderr.write(`${format_report(hits)}\n`);
449
+ // The per-pattern hints are bundle-shaped ("rebuild from a clean
450
+ // checkout"), which is the wrong instruction for a `.md` hit — nothing is
451
+ // rebuilt to fix prose. Published-md hits get their own line rather than
452
+ // a misleading one.
453
+ if (hits.some((h) => h.file.endsWith('.md'))) {
454
+ process.stderr.write(
455
+ ` NOTE: a \`.md\` hit is not fixed by rebuilding. Either anonymise the path,\n` +
456
+ ` or — if it IS the pattern being documented — pin it in ${ALLOW_FILE}.\n`,
457
+ );
458
+ }
459
+ if (suppressed > 0) {
460
+ process.stderr.write(
461
+ ` (${suppressed} further match(es) are pinned exceptions in ${ALLOW_FILE})\n`,
462
+ );
463
+ }
464
+ process.stderr.write(
465
+ ` A published-.md match that is the PATTERN BEING DOCUMENTED — a rule quoting\n` +
466
+ ` the shape it forbids, or an anonymised example — belongs in ${ALLOW_FILE}\n` +
467
+ ` as \`<path>:<line>\` with its reason above it. A real path does not.\n`,
468
+ );
323
469
  return 1;
324
470
  }
325
471
 
326
472
  if (!opts.quiet) {
473
+ // A gate that scans nothing and exits green is indistinguishable from a
474
+ // broken one, so the green path names both populations and the
475
+ // suppression count — an exception set that quietly grows is the failure
476
+ // mode a bare "no leakage" line would hide.
327
477
  process.stdout.write(
328
- `✅ check_bundle_path_leakage: no build-machine path leakage ` +
329
- `(${targets.length} tracked bundle file(s) scanned).\n`,
478
+ `✅ check_bundle_path_leakage: no unapproved path leakage ` +
479
+ `(${String(bundle.length)} bundle + ${String(published.length)} published-md ` +
480
+ `file(s) scanned, ${String(suppressed)} pinned exception(s)).\n`,
330
481
  );
331
482
  }
332
483
  return 0;
@@ -359,6 +510,10 @@ if (_isCliEntry() || process.argv[1] === _HERE) {
359
510
  export {
360
511
  REPO,
361
512
  BUNDLE_ROOTS,
513
+ PUBLISHED_MD_ROOTS,
514
+ PUBLISHED_MD_FILES,
515
+ ALLOW_FILE,
516
+ tracked_published_md,
362
517
  LEAK_PATTERNS,
363
518
  MAX_SNIPPET,
364
519
  MAX_HITS_SHOWN_PER_GROUP,