@event4u/agent-config 14.10.0 → 14.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (399) 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 +335 -0
  5. package/README.md +1 -1
  6. package/dist/agent-src/commands/design.md +88 -0
  7. package/dist/agent-src/commands/project/analyze.md +47 -0
  8. package/dist/agent-src/commands/review/changes.md +67 -6
  9. package/dist/agent-src/commands/roadmap/create.md +49 -0
  10. package/dist/agent-src/commands/roadmap/next.md +27 -11
  11. package/dist/agent-src/commands/roadmap/process-full.md +14 -0
  12. package/dist/agent-src/commands/video/from-script.md +60 -3
  13. package/dist/agent-src/commands/video/stitch.md +14 -0
  14. package/dist/agent-src/contexts/communication/rules-auto/guidelines-mechanics.md +1 -0
  15. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +154 -2
  16. package/dist/agent-src/rules/active-remediation.md +3 -3
  17. package/dist/agent-src/rules/code-provenance.md +2 -2
  18. package/dist/agent-src/rules/context-hygiene.md +13 -33
  19. package/dist/agent-src/rules/council-availability.md +2 -2
  20. package/dist/agent-src/rules/decision-revisit-gate.md +3 -1
  21. package/dist/agent-src/rules/design-fidelity.md +18 -0
  22. package/dist/agent-src/rules/design-review-after-ui-write.md +2 -2
  23. package/dist/agent-src/rules/evaluator-independence.md +56 -44
  24. package/dist/agent-src/rules/fix-what-you-see.md +9 -1
  25. package/dist/agent-src/rules/framework-neutrality-in-generic-skills.md +1 -1
  26. package/dist/agent-src/rules/minimal-safe-diff.md +14 -20
  27. package/dist/agent-src/rules/missing-skill-recovery.md +13 -1
  28. package/dist/agent-src/rules/playbook-precedence.md +105 -0
  29. package/dist/agent-src/rules/recurring-criticism.md +3 -1
  30. package/dist/agent-src/rules/roadmap-progress-sync.md +14 -13
  31. package/dist/agent-src/rules/scale-discipline.md +24 -0
  32. package/dist/agent-src/rules/security-sensitive-stop.md +2 -2
  33. package/dist/agent-src/rules/settings-ask-protocol.md +2 -2
  34. package/dist/agent-src/rules/ui-audit-gate.md +27 -15
  35. package/dist/agent-src/rules/untrusted-input-defense.md +2 -2
  36. package/dist/agent-src/skills/accessibility-auditor/SKILL.md +19 -0
  37. package/dist/agent-src/skills/adr-create/SKILL.md +7 -0
  38. package/dist/agent-src/skills/agents-md-thin-root/SKILL.md +33 -0
  39. package/dist/agent-src/skills/ai-council/references/advanced-modes.md +17 -3
  40. package/dist/agent-src/skills/ai-council/references/output-and-synthesis.md +31 -0
  41. package/dist/agent-src/skills/ai-council/references/procedure.md +19 -0
  42. package/dist/agent-src/skills/alerting-doctrine/SKILL.md +225 -0
  43. package/dist/agent-src/skills/alerting-doctrine/evals/triggers.json +17 -0
  44. package/dist/agent-src/skills/authz-review/SKILL.md +0 -1
  45. package/dist/agent-src/skills/blast-radius-analyzer/SKILL.md +32 -0
  46. package/dist/agent-src/skills/brand/SKILL.md +3 -0
  47. package/dist/agent-src/skills/brand-asset-generation/SKILL.md +3 -0
  48. package/dist/agent-src/skills/brand-audit/SKILL.md +3 -0
  49. package/dist/agent-src/skills/brand-identity/SKILL.md +3 -0
  50. package/dist/agent-src/skills/brand-strategy/SKILL.md +3 -0
  51. package/dist/agent-src/skills/brand-to-tokens/SKILL.md +3 -0
  52. package/dist/agent-src/skills/check-refs/SKILL.md +3 -0
  53. package/dist/agent-src/skills/code-review/SKILL.md +11 -0
  54. package/dist/agent-src/skills/command-routing/SKILL.md +6 -0
  55. package/dist/agent-src/skills/condense-memory/SKILL.md +3 -0
  56. package/dist/agent-src/skills/context-authoring/SKILL.md +1 -1
  57. package/dist/agent-src/skills/conventional-commits-writing/SKILL.md +3 -0
  58. package/dist/agent-src/skills/copilot-agents-optimization/SKILL.md +10 -0
  59. package/dist/agent-src/skills/corpus-grounding/SKILL.md +3 -0
  60. package/dist/agent-src/skills/design-intelligence/SKILL.md +3 -0
  61. package/dist/agent-src/skills/design-intelligence/data/landing.csv +36 -35
  62. package/dist/agent-src/skills/design-intelligence/data/stacks/threejs.csv +1 -0
  63. package/dist/agent-src/skills/design-review/SKILL.md +79 -1
  64. package/dist/agent-src/skills/design-review/references/verification-automation.md +41 -0
  65. package/dist/agent-src/skills/design-system-capture/SKILL.md +35 -40
  66. package/dist/agent-src/skills/design-system-capture/references/import-procedure.md +50 -0
  67. package/dist/agent-src/skills/design-tokens/SKILL.md +3 -0
  68. package/dist/agent-src/skills/design-variations/SKILL.md +3 -0
  69. package/dist/agent-src/skills/developer-like-execution/SKILL.md +3 -0
  70. package/dist/agent-src/skills/estimate-ticket/SKILL.md +3 -0
  71. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +20 -3
  72. package/dist/agent-src/skills/fe-design/SKILL.md +74 -41
  73. package/dist/agent-src/skills/fe-design/evals/triggers.json +65 -53
  74. package/dist/agent-src/skills/fe-design/references/craft-floor.md +52 -0
  75. package/dist/agent-src/skills/fe-design/references/source-led-port.md +36 -0
  76. package/dist/agent-src/skills/file-editor/SKILL.md +5 -0
  77. package/dist/agent-src/skills/finishing-a-development-branch/SKILL.md +2 -2
  78. package/dist/agent-src/skills/git-workflow/SKILL.md +3 -0
  79. package/dist/agent-src/skills/html-deck/SKILL.md +3 -0
  80. package/dist/agent-src/skills/iconography/SKILL.md +3 -0
  81. package/dist/agent-src/skills/image-editing/SKILL.md +3 -0
  82. package/dist/agent-src/skills/image-generation/SKILL.md +3 -0
  83. package/dist/agent-src/skills/image-provider-routing/SKILL.md +3 -0
  84. package/dist/agent-src/skills/incident-commander/SKILL.md +10 -0
  85. package/dist/agent-src/skills/js-library-packaging/SKILL.md +173 -0
  86. package/dist/agent-src/skills/js-library-packaging/evals/triggers.json +17 -0
  87. package/dist/agent-src/skills/js-library-packaging/scripts/check_package_surface.ts +270 -0
  88. package/dist/agent-src/skills/laravel-migration/SKILL.md +3 -0
  89. package/dist/agent-src/skills/learning-to-rule-or-skill/SKILL.md +3 -0
  90. package/dist/agent-src/skills/lint-skills/SKILL.md +3 -0
  91. package/dist/agent-src/skills/llm-provider-knowledge/SKILL.md +3 -0
  92. package/dist/agent-src/skills/logging-monitoring/SKILL.md +234 -51
  93. package/dist/agent-src/skills/logo-generation/SKILL.md +3 -0
  94. package/dist/agent-src/skills/md-language-check/SKILL.md +3 -0
  95. package/dist/agent-src/skills/monorepo-workspace/SKILL.md +149 -0
  96. package/dist/agent-src/skills/monorepo-workspace/evals/triggers.json +17 -0
  97. package/dist/agent-src/skills/motion-choreographer/SKILL.md +6 -0
  98. package/dist/agent-src/skills/multi-tenancy/SKILL.md +4 -0
  99. package/dist/agent-src/skills/operational-readiness/SKILL.md +184 -0
  100. package/dist/agent-src/skills/operational-readiness/evals/triggers.json +17 -0
  101. package/dist/agent-src/skills/override-management/SKILL.md +3 -0
  102. package/dist/agent-src/skills/persona-improvement/SKILL.md +3 -0
  103. package/dist/agent-src/skills/pest-testing/SKILL.md +3 -0
  104. package/dist/agent-src/skills/playbook-authoring/SKILL.md +162 -0
  105. package/dist/agent-src/skills/playwright-testing/SKILL.md +3 -0
  106. package/dist/agent-src/skills/privacy-review/SKILL.md +3 -0
  107. package/dist/agent-src/skills/prompt-engineering-image/SKILL.md +3 -0
  108. package/dist/agent-src/skills/quality-tools/SKILL.md +5 -0
  109. package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +52 -21
  110. package/dist/agent-src/skills/readme-reviewer/SKILL.md +3 -0
  111. package/dist/agent-src/skills/readme-writing/SKILL.md +3 -0
  112. package/dist/agent-src/skills/readme-writing-package/SKILL.md +3 -0
  113. package/dist/agent-src/skills/receiving-code-review/SKILL.md +2 -2
  114. package/dist/agent-src/skills/refine-prompt/SKILL.md +3 -0
  115. package/dist/agent-src/skills/refine-ticket/SKILL.md +3 -0
  116. package/dist/agent-src/skills/requesting-code-review/SKILL.md +2 -2
  117. package/dist/agent-src/skills/review-routing/SKILL.md +2 -2
  118. package/dist/agent-src/skills/roadmap-writing/SKILL.md +23 -41
  119. package/dist/agent-src/skills/roadmap-writing/references/follow-up-roadmap-shape.md +47 -0
  120. package/dist/agent-src/skills/rtk-output-filtering/SKILL.md +22 -6
  121. package/dist/agent-src/skills/runway-cognition/SKILL.md +3 -0
  122. package/dist/agent-src/skills/scene-expander/scene-blueprint.schema.yaml +26 -0
  123. package/dist/agent-src/skills/secrets-management/SKILL.md +4 -0
  124. package/dist/agent-src/skills/server-hardening/SKILL.md +159 -0
  125. package/dist/agent-src/skills/server-hardening/evals/triggers.json +17 -0
  126. package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +3 -0
  127. package/dist/agent-src/skills/skill-management/SKILL.md +3 -0
  128. package/dist/agent-src/skills/skill-reviewer/SKILL.md +3 -0
  129. package/dist/agent-src/skills/skill-writing/SKILL.md +6 -0
  130. package/dist/agent-src/skills/spreadsheet-authoring/SKILL.md +3 -0
  131. package/dist/agent-src/skills/storybook-workshop/SKILL.md +194 -0
  132. package/dist/agent-src/skills/storybook-workshop/evals/triggers.json +17 -0
  133. package/dist/agent-src/skills/storybook-workshop/scripts/story_contrast_floor.ts +134 -0
  134. package/dist/agent-src/skills/subagent-orchestration/SKILL.md +1 -1
  135. package/dist/agent-src/skills/subagent-orchestration/prompts/README.md +25 -0
  136. package/dist/agent-src/skills/supply-chain-intake/SKILL.md +12 -0
  137. package/dist/agent-src/skills/systematic-debugging/SKILL.md +1 -1
  138. package/dist/agent-src/skills/terraform/SKILL.md +3 -0
  139. package/dist/agent-src/skills/test-driven-development/SKILL.md +1 -1
  140. package/dist/agent-src/skills/threat-modeling/SKILL.md +0 -3
  141. package/dist/agent-src/skills/token-optimizer/SKILL.md +3 -0
  142. package/dist/agent-src/skills/typography-system/SKILL.md +3 -0
  143. package/dist/agent-src/skills/ui-component-architect/SKILL.md +9 -1
  144. package/dist/agent-src/skills/upstream-contribute/SKILL.md +3 -0
  145. package/dist/agent-src/skills/validate-feature-fit/SKILL.md +3 -0
  146. package/dist/agent-src/skills/verify-completion-evidence/SKILL.md +2 -2
  147. package/dist/agent-src/skills/wireframe/SKILL.md +3 -0
  148. package/dist/agent-src/skills/wireframe/references/story-beat-ledger.schema.json +115 -0
  149. package/dist/agent-src/skills/workspace-link/SKILL.md +131 -0
  150. package/dist/agent-src/skills/workspace-link/evals/triggers.json +17 -0
  151. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  152. package/dist/agent-src/templates/playbook.md +75 -0
  153. package/dist/agent-src/templates/roadmaps.md +37 -0
  154. package/dist/agent-src/templates/scripts/check_playbook_invokes.ts +231 -0
  155. package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +24 -0
  156. package/dist/agent-src/templates/scripts/work_engine/directives/ui/polish.ts +2 -0
  157. package/dist/agent-src/templates/scripts/work_engine/directives/ui/review.ts +216 -0
  158. package/dist/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +159 -0
  159. package/dist/agent-src/templates/scripts/work_engine/directives/ui/stack_bundles.ts +73 -4
  160. package/dist/agent-src/templates/scripts/work_engine/stack/detect.ts +325 -40
  161. package/dist/cli/commands/uiAudit.js +221 -0
  162. package/dist/cli/commands/uiAudit.js.map +1 -0
  163. package/dist/cli/commands/uiRender.js +206 -0
  164. package/dist/cli/commands/uiRender.js.map +1 -0
  165. package/dist/cli/main.js +22 -1
  166. package/dist/cli/main.js.map +1 -1
  167. package/dist/cli/mcp/content.js +64 -1
  168. package/dist/cli/mcp/content.js.map +1 -1
  169. package/dist/cli/mcp/dispatch.js +175 -10
  170. package/dist/cli/mcp/dispatch.js.map +1 -1
  171. package/dist/cli/registry.js +3 -0
  172. package/dist/cli/registry.js.map +1 -1
  173. package/dist/cli-delegate/{chunk-H4VIVD4C.js → chunk-236PNQZK.js} +413 -391
  174. package/dist/cli-delegate/{chunk-G23AT6GD.js → chunk-DMSZHXZS.js} +57 -1
  175. package/dist/cli-delegate/{chunk-77GR3VD4.js → chunk-FF7WAFMR.js} +58 -3
  176. package/dist/cli-delegate/{chunk-PLSG6UL5.js → chunk-K4FFODTS.js} +341 -204
  177. package/dist/cli-delegate/{chunk-YM4GZDJG.js → chunk-L43NCYPB.js} +5 -0
  178. package/dist/cli-delegate/{chunk-B2ERU2VA.js → chunk-WOKWX5PL.js} +1 -1
  179. package/dist/cli-delegate/cmd_brand_status.js +8 -6
  180. package/dist/cli-delegate/cmd_conformance.js +3 -3
  181. package/dist/cli-delegate/cmd_doctor.js +3 -3
  182. package/dist/cli-delegate/cmd_export.js +4 -4
  183. package/dist/cli-delegate/cmd_handoff.js +1 -1
  184. package/dist/cli-delegate/cmd_preflight.js +2 -2
  185. package/dist/cli-delegate/cmd_prune.js +4 -4
  186. package/dist/cli-delegate/cmd_session_recycle.js +70 -9
  187. package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
  188. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  189. package/dist/cli-delegate/cmd_sync.js +4 -4
  190. package/dist/cli-delegate/cmd_uninstall.js +4 -4
  191. package/dist/cli-delegate/cmd_validate.js +4 -4
  192. package/dist/discovery/deprecation-report.md +1 -1
  193. package/dist/discovery/discovery-manifest.json +359 -124
  194. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  195. package/dist/discovery/discovery-manifest.summary.md +4 -4
  196. package/dist/discovery/dormancy-report.md +1 -1
  197. package/dist/discovery/orphan-report.md +1 -1
  198. package/dist/discovery/packs.json +18 -8
  199. package/dist/discovery/trust-report.md +3 -3
  200. package/dist/discovery/workspaces.json +16 -6
  201. package/dist/hooks/dispatch.js +2337 -1436
  202. package/dist/install/install.mjs +795 -708
  203. package/dist/mcp/registry-manifest.json +3 -3
  204. package/dist/mcp/server.json +2 -2
  205. package/dist/mcp/server.mjs +1685 -1250
  206. package/dist/router.json +1 -1
  207. package/dist/scripts/_lib/lean_projection_mode.js +40 -0
  208. package/dist/scripts/_lib/lean_projection_mode.js.map +1 -0
  209. package/dist/scripts/_lib/scoped_projection.js +84 -0
  210. package/dist/scripts/_lib/scoped_projection.js.map +1 -1
  211. package/dist/scripts/_lib/ui_surface.js +89 -0
  212. package/dist/scripts/_lib/ui_surface.js.map +1 -0
  213. package/dist/scripts/condense.js +6 -10
  214. package/dist/scripts/condense.js.map +1 -1
  215. package/dist/scripts/project_thin_rules.js +40 -2
  216. package/dist/scripts/project_thin_rules.js.map +1 -1
  217. package/dist/server/schemas/settings.js +3 -0
  218. package/dist/server/schemas/settings.js.map +1 -1
  219. package/dist/shared/skillRanking.js +159 -0
  220. package/dist/shared/skillRanking.js.map +1 -0
  221. package/docs/contracts/provider-lifecycle.md +47 -18
  222. package/docs/contracts/settings-classes.md +14 -4
  223. package/docs/guidelines/agent-infra/context-hygiene-mechanics.md +49 -0
  224. package/docs/guidelines/agent-infra/evaluator-independence-mechanics.md +92 -0
  225. package/docs/guidelines/agent-infra/minimal-safe-diff-mechanics.md +45 -1
  226. package/docs/guidelines/agent-infra/roadmap-deferred-resolution-provenance.md +54 -0
  227. package/docs/guidelines/design-antipatterns.md +4 -2
  228. package/docs/guidelines/design-asset-discipline.md +38 -0
  229. package/docs/guidelines/design-fidelity-mechanics.md +45 -29
  230. package/docs/guidelines/design-handover-extraction.md +35 -5
  231. package/docs/guidelines/design-modes.md +41 -1
  232. package/docs/guidelines/monorepo-antipatterns.md +141 -0
  233. package/llms.txt +50 -39
  234. package/package.json +1 -1
  235. package/src/agent-src/templates/scripts/check_playbook_invokes.ts +231 -0
  236. package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +24 -0
  237. package/src/agent-src/templates/scripts/work_engine/directives/ui/polish.ts +2 -0
  238. package/src/agent-src/templates/scripts/work_engine/directives/ui/review.ts +216 -0
  239. package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +159 -0
  240. package/src/agent-src/templates/scripts/work_engine/directives/ui/stack_bundles.ts +73 -4
  241. package/src/agent-src/templates/scripts/work_engine/stack/detect.ts +325 -40
  242. package/src/config/agent-settings.template.yml +28 -0
  243. package/src/config/assurance-capability-registry.json +223 -0
  244. package/src/config/assurance-threshold-budget.json +65 -0
  245. package/src/config/ci-local-parity.yml +14 -0
  246. package/src/config/discovery/unassigned-artefacts.yml +2 -0
  247. package/src/config/dispatch-economy-metrics.json +4 -2
  248. package/src/config/evaluator-budgets.json +14 -14
  249. package/src/config/gate-coverage.yml +207 -8
  250. package/src/config/gate-violation-baselines.json +76 -72
  251. package/src/config/hook-latency-budget.json +1 -0
  252. package/src/config/hook-token-budget.json +137 -125
  253. package/src/config/pack-size-budget.json +4 -2
  254. package/src/config/quorum-attendance-budget.json +12 -4
  255. package/src/config/rule-activation-census.json +7 -3
  256. package/src/config/rule-enforcement-baseline.json +0 -1
  257. package/src/config/rule-stub-ceilings.json +112 -91
  258. package/src/scripts/_cli/cmd_doctor.ts +15 -16
  259. package/src/scripts/_cli/doctor_install_mode.ts +49 -0
  260. package/src/scripts/_cli/doctor_overrides.ts +154 -0
  261. package/src/scripts/_dispatch.bash +17 -0
  262. package/src/scripts/_lib/as_of.ts +412 -0
  263. package/src/scripts/_lib/exec_evidence.ts +7 -0
  264. package/src/scripts/_lib/hook_settings.ts +33 -0
  265. package/src/scripts/_lib/host_listing_model.ts +192 -0
  266. package/src/scripts/_lib/lean_projection_mode.ts +44 -0
  267. package/src/scripts/_lib/mcp_bridge.ts +199 -0
  268. package/src/scripts/_lib/review_independence.ts +62 -10
  269. package/src/scripts/_lib/rule_injection.ts +239 -0
  270. package/src/scripts/_lib/run_checkpoint.ts +54 -1
  271. package/src/scripts/_lib/scoped_projection.ts +96 -0
  272. package/src/scripts/_lib/session_register.ts +44 -2
  273. package/src/scripts/_lib/skill_catalogue.ts +38 -2
  274. package/src/scripts/_lib/transcript_reads.ts +207 -0
  275. package/src/scripts/_lib/ui_authority.test.ts +251 -0
  276. package/src/scripts/_lib/ui_authority.ts +343 -0
  277. package/src/scripts/ai-video/adapters/fal.sh +8 -3
  278. package/src/scripts/ai-video/adapters/gemini-veo.sh +10 -0
  279. package/src/scripts/ai-video/adapters/higgsfield.sh +10 -2
  280. package/src/scripts/ai-video/adapters/kling.sh +10 -0
  281. package/src/scripts/ai-video/adapters/sora.sh +10 -0
  282. package/src/scripts/ai-video/lib/calibrate-cost.sh +145 -0
  283. package/src/scripts/ai-video/lib/model-capabilities/README.md +53 -7
  284. package/src/scripts/ai-video/lib/model-capabilities/comfyui.json +15 -3
  285. package/src/scripts/ai-video/lib/model-capabilities/fal.json +36 -6
  286. package/src/scripts/ai-video/lib/model-capabilities/gemini-veo.json +23 -0
  287. package/src/scripts/ai-video/lib/model-capabilities/higgsfield.json +23 -0
  288. package/src/scripts/ai-video/lib/model-capabilities/kling.json +23 -0
  289. package/src/scripts/ai-video/lib/model-capabilities/musetalk.json +8 -2
  290. package/src/scripts/ai-video/lib/model-capabilities/replicate.json +36 -6
  291. package/src/scripts/ai-video/lib/model-capabilities/sora.json +45 -0
  292. package/src/scripts/ai-video/lib/model-capabilities/syncso.json +8 -2
  293. package/src/scripts/ai-video/lib/parse-blueprint.sh +52 -5
  294. package/src/scripts/ai-video/lib/resume-scan.sh +92 -3
  295. package/src/scripts/ai-video/lib/seam-plan.sh +212 -0
  296. package/src/scripts/ai-video/smoke-trace.sh +450 -2
  297. package/src/scripts/ai-video/stitch.sh +191 -10
  298. package/src/scripts/ai-video/test-pipeline.sh +84 -6
  299. package/src/scripts/ai_council/cli_agency_bounds.ts +139 -0
  300. package/src/scripts/ai_council/cli_least_agency_canary.ts +347 -0
  301. package/src/scripts/ai_council/clients.ts +3 -2
  302. package/src/scripts/ai_council/consensus.ts +63 -6
  303. package/src/scripts/ai_council/events_log.ts +46 -1
  304. package/src/scripts/ai_council/orchestrator.ts +103 -108
  305. package/src/scripts/ai_council/orchestrator_results.ts +166 -0
  306. package/src/scripts/ai_council/prompts.ts +12 -5
  307. package/src/scripts/ai_council/quorum.ts +82 -1
  308. package/src/scripts/ai_council/quorum_wiring.ts +82 -5
  309. package/src/scripts/ai_council/stance_tally.ts +22 -0
  310. package/src/scripts/bench_hook_latency.ts +62 -0
  311. package/src/scripts/build_proof.ts +12 -1
  312. package/src/scripts/capture_skill_catalogue.ts +21 -0
  313. package/src/scripts/check_always_budget.ts +2 -1
  314. package/src/scripts/check_augmentignore.ts +2 -1
  315. package/src/scripts/check_beta_review_markers.ts +2 -1
  316. package/src/scripts/check_composite_arming.ts +364 -0
  317. package/src/scripts/check_corpus_staleness.ts +2 -1
  318. package/src/scripts/check_council_pin_staleness.ts +3 -1
  319. package/src/scripts/check_enforcement_coverage.ts +116 -3
  320. package/src/scripts/check_enforcement_denominator.ts +365 -0
  321. package/src/scripts/check_gate_coverage.ts +2 -1
  322. package/src/scripts/check_hook_bundle_freshness.ts +13 -1
  323. package/src/scripts/check_knowledge_cards.ts +2 -1
  324. package/src/scripts/check_knowledge_pages.ts +3 -2
  325. package/src/scripts/check_memory.ts +2 -1
  326. package/src/scripts/check_no_automerge_key.ts +287 -0
  327. package/src/scripts/check_pack_conformance_fixture.ts +565 -0
  328. package/src/scripts/check_proposal.ts +2 -1
  329. package/src/scripts/check_reach_staleness.ts +2 -1
  330. package/src/scripts/check_release_adjacent_health.ts +2 -1
  331. package/src/scripts/check_release_pr_shape.ts +5 -0
  332. package/src/scripts/check_review_schema.ts +34 -1
  333. package/src/scripts/check_roadmap_trackable.ts +48 -3
  334. package/src/scripts/check_source_size_budget.ts +2 -1
  335. package/src/scripts/check_standing_payload_delta.ts +505 -0
  336. package/src/scripts/check_trigger_evals.ts +2 -1
  337. package/src/scripts/classify_change_risk.ts +281 -0
  338. package/src/scripts/compute_skill_tiers.ts +186 -0
  339. package/src/scripts/condense.ts +8 -13
  340. package/src/scripts/council_attendance_metrics.ts +183 -0
  341. package/src/scripts/council_cli.ts +10 -8
  342. package/src/scripts/council_parse_rate.ts +116 -0
  343. package/src/scripts/derive_playbooks.ts +406 -0
  344. package/src/scripts/design_slop_rules.test.ts +7 -0
  345. package/src/scripts/design_slop_rules.ts +56 -2
  346. package/src/scripts/dispatch_r2_reviewer.ts +16 -0
  347. package/src/scripts/frontend_corpus_hash.ts +116 -0
  348. package/src/scripts/grade_target_readiness.ts +478 -0
  349. package/src/scripts/hook_manifest.json +1 -1
  350. package/src/scripts/hook_manifest.yaml +97 -16
  351. package/src/scripts/hooks/concern_registry.ts +4 -0
  352. package/src/scripts/hooks/design_pass_hook.ts +356 -0
  353. package/src/scripts/hooks/rtk_wrap_hook.ts +30 -7
  354. package/src/scripts/hooks/rule_inject_hook.ts +318 -0
  355. package/src/scripts/hot_context_hook.ts +74 -7
  356. package/src/scripts/install.ts +40 -89
  357. package/src/scripts/lint_adapter_tier.ts +663 -0
  358. package/src/scripts/lint_archived_skills.ts +93 -0
  359. package/src/scripts/lint_budget_ownership.ts +2 -1
  360. package/src/scripts/lint_deterministic_time.ts +470 -0
  361. package/src/scripts/lint_framework_leakage.ts +5 -0
  362. package/src/scripts/lint_handoffs.ts +525 -1
  363. package/src/scripts/lint_judge_prompt_expectation.ts +155 -0
  364. package/src/scripts/lint_marketplace.ts +88 -0
  365. package/src/scripts/lint_media_policy_linkage.ts +140 -1
  366. package/src/scripts/lint_one_off_age.ts +6 -1
  367. package/src/scripts/lint_override_kernel_guard.ts +87 -5
  368. package/src/scripts/lint_roadmap_complexity.ts +56 -0
  369. package/src/scripts/lint_rule_enforcement_declaration.ts +24 -7
  370. package/src/scripts/lint_rule_norm_pin.ts +321 -0
  371. package/src/scripts/lint_spawn_payload.ts +42 -5
  372. package/src/scripts/lint_symptom_intake.ts +2 -1
  373. package/src/scripts/mcp_parity_smoke.ts +30 -4
  374. package/src/scripts/mcp_render.ts +66 -1
  375. package/src/scripts/mcp_tool_fingerprint.ts +241 -0
  376. package/src/scripts/measure_risk_class_drift.ts +140 -0
  377. package/src/scripts/measure_skill_ranker_baseline.ts +212 -0
  378. package/src/scripts/media/lib/adapter-common.sh +211 -1
  379. package/src/scripts/media/lib/adapter-contract.md +74 -6
  380. package/src/scripts/model_rule_injection.ts +907 -0
  381. package/src/scripts/project_thin_rules.ts +45 -2
  382. package/src/scripts/release.ts +66 -860
  383. package/src/scripts/release_drill.ts +59 -3
  384. package/src/scripts/release_env.ts +298 -0
  385. package/src/scripts/release_publication.ts +720 -0
  386. package/src/scripts/roadmap_context.ts +778 -0
  387. package/src/scripts/schemas/fidelity-measurement-sheet.schema.json +169 -0
  388. package/src/scripts/schemas/rule.schema.json +24 -2
  389. package/src/scripts/schemas/skill.schema.json +39 -0
  390. package/src/scripts/schemas/ui-authority.schema.json +114 -0
  391. package/src/scripts/session_register_hook.ts +29 -3
  392. package/src/scripts/sessions_cli.ts +67 -3
  393. package/src/scripts/skill_tools/score_skill_relevance.ts +36 -53
  394. package/src/scripts/token_sink_report.ts +239 -0
  395. package/src/scripts/trigger_coverage.ts +122 -3
  396. package/src/scripts/update_counts.ts +7 -3
  397. package/src/scripts/validate_frontmatter.ts +66 -0
  398. package/src/server/schemas/settings.ts +5 -0
  399. package/src/shared/skillRanking.ts +183 -0
@@ -19,17 +19,21 @@
19
19
  * 4. No archived slug still has a live SKILL.md (no zombies).
20
20
  * 5. No live SKILL.md cites an archived slug as a router target in its
21
21
  * frontmatter `replaced_by:` field.
22
+ * 6. A slug that had a `SKILL.md` at the base ref and has none in the
23
+ * working tree carries an archive note. See {@link removed_without_note}.
22
24
  *
23
25
  * Exit codes:
24
26
  * 0 contract holds
25
27
  * 1 one or more violations
26
28
  */
27
29
 
30
+ import { spawnSync } from 'node:child_process';
28
31
  import * as fs from 'node:fs';
29
32
  import * as path from 'node:path';
30
33
  import { fileURLToPath, pathToFileURL } from 'node:url';
31
34
 
32
35
  import { artefact_roots } from './_lib/agent_src.js';
36
+ import { resolveBaseRef } from './_lib/ratchet_base_ref.js';
33
37
  import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
34
38
 
35
39
  const _HERE = fileURLToPath(import.meta.url);
@@ -243,6 +247,19 @@ function main(): number {
243
247
  }
244
248
  }
245
249
 
250
+ // Rule 6 — removals that never entered the ledger.
251
+ const removalCheck = removed_without_note(REPO, archivedKeys, live);
252
+ if ('skipped' in removalCheck) {
253
+ process.stdout.write(`rule 6 skipped: ${removalCheck.skipped}\n`);
254
+ } else {
255
+ for (const slug of removalCheck.removed) {
256
+ errors.push(
257
+ `${slug}: SKILL.md removed since the base ref with no ` +
258
+ `agents/evidence/archived-skills/${slug}.md note`,
259
+ );
260
+ }
261
+ }
262
+
246
263
  if (errors.length) {
247
264
  process.stderr.write(
248
265
  `❌ lint_archived_skills: ${errors.length} violation(s) across ${notes.length} note(s)\n`,
@@ -261,6 +278,82 @@ function main(): number {
261
278
  return 0;
262
279
  }
263
280
 
281
+ /**
282
+ * Rule 6 — a slug that left the tree without entering the ledger.
283
+ *
284
+ * WHY THIS LIVES IN THE GATE AND NOT IN A SCAFFOLDER. The roadmap step that
285
+ * asked for this (`road-to-skill-link-integrity-and-manifest-sync` Phase 1
286
+ * Step 3) named `src/scripts/new_skill.ts --archive` as the extension point,
287
+ * on the premise that it is "a creation path with no symmetric removal path".
288
+ * That premise is false: `new_skill.ts` scaffolds into a `packages/<pack>/`
289
+ * container that ADR-051 retired, and its `main()` returns exit 2 with
290
+ * `error: no packages/ tree found` in this tree, because skill authoring moved
291
+ * to `src/skills/`. A mode added there could not run.
292
+ *
293
+ * The obligation is a CI property, not a tool property, and the distinction is
294
+ * the reason the ledger is empty: nothing forces a maintainer or an agent to
295
+ * delete a directory *through* a tool, so a scaffolder mode is a bypass by
296
+ * construction. Three slugs left this tree without a note while
297
+ * `agents/evidence/archived-skills/` held only a README.
298
+ *
299
+ * A RENAME IS A REMOVAL HERE, DELIBERATELY. Git would call
300
+ * `verify-before-complete` → `verify-completion-evidence` a rename; every link
301
+ * to the old slug still broke, and repairing eight of them is what produced
302
+ * this rule. So the old slug owes a note.
303
+ *
304
+ * THE COMPARISON IS AGAINST THE MERGE BASE, NOT THE TIP OF MAIN, and this was
305
+ * found the hard way: the first run of this rule reported `js-library-packaging`
306
+ * and `storybook-workshop` as unnoted removals. Neither had been removed — both
307
+ * were ADDED on main after this branch forked, so a branch that is merely
308
+ * BEHIND its base read every new skill as a deletion. That is the false-red
309
+ * class `_lib/ratchet_base_ref.ts` warns about in its own docstring ("the gate
310
+ * gets a reputation for false reds, and someone turns it off"). `git merge-base`
311
+ * answers the question the rule actually asks — did THIS branch remove it —
312
+ * because a skill added on main after the fork is not in the merge base at all.
313
+ *
314
+ * THE SKIP IS STATED, NEVER SILENT. With no resolvable base ref there is no
315
+ * "before" side, and comparing against an assumed-empty base would report every
316
+ * skill in the tree as removed. `resolveBaseRef` returns null in that case and
317
+ * this returns a `skipped` reason the caller prints — the honest default named
318
+ * in that helper's own contract. It is also blind to an uncommitted removal;
319
+ * CI is the authoritative gate and CI only ever sees committed work.
320
+ */
321
+ export function removed_without_note(
322
+ repoRoot: string = REPO,
323
+ archived: ReadonlySet<string> = new Set(archived_slugs()),
324
+ liveSlugs: ReadonlySet<string> = new Set(live_skill_slugs()),
325
+ ): { skipped: string } | { removed: string[] } {
326
+ const base = resolveBaseRef(repoRoot);
327
+ if (base === null) {
328
+ return { skipped: 'no base ref resolved — nothing to compare against' };
329
+ }
330
+ const mb = spawnSync('git', ['merge-base', 'HEAD', base], {
331
+ cwd: repoRoot,
332
+ encoding: 'utf-8',
333
+ });
334
+ // A missing merge base is not "compare against the tip instead" — that is
335
+ // the false-red above. It is no comparable before-side at all.
336
+ const forkPoint = mb.status === 0 ? String(mb.stdout).trim() : '';
337
+ if (!forkPoint) {
338
+ return { skipped: `no merge base between HEAD and ${base}` };
339
+ }
340
+ const res = spawnSync(
341
+ 'git',
342
+ ['ls-tree', '-r', '--name-only', forkPoint, '--', 'src/skills'],
343
+ { cwd: repoRoot, encoding: 'utf-8' },
344
+ );
345
+ if (res.status !== 0) {
346
+ return { skipped: `git ls-tree ${forkPoint} failed — base ref is not readable here` };
347
+ }
348
+ const atBase = new Set<string>();
349
+ for (const line of String(res.stdout).split('\n')) {
350
+ const m = /^src\/skills\/([^/]+)\/SKILL\.md$/.exec(line.trim());
351
+ if (m) atBase.add(m[1] as string);
352
+ }
353
+ const removed = [...atBase].filter((slug) => !liveSlugs.has(slug) && !archived.has(slug)).sort();
354
+ return { removed };
355
+ }
356
+
264
357
  // --- helpers --------------------------------------------------------------
265
358
 
266
359
  function _exists(p: string): boolean {
@@ -31,6 +31,7 @@ import * as fs from 'node:fs';
31
31
  import * as path from 'node:path';
32
32
  import { fileURLToPath } from 'node:url';
33
33
 
34
+ import { asOf } from './_lib/as_of.js';
34
35
  import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
35
36
 
36
37
  const REPO_ROOT = path.resolve(fileURLToPath(import.meta.url), '..', '..', '..');
@@ -154,7 +155,7 @@ export function main(): number {
154
155
  throw exc;
155
156
  }
156
157
  const errors: string[] = [];
157
- const today = new Date().toISOString().slice(0, 10);
158
+ const today = asOf().toISOString().slice(0, 10);
158
159
  for (const abs of files) {
159
160
  const rel = path.relative(REPO_ROOT, abs);
160
161
  let doc: Record<string, unknown>;
@@ -0,0 +1,470 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * CI gate: a gate script does not read the wall clock directly.
4
+ *
5
+ * WHY. A gate's verdict should be a function of the tree, not of the hour it
6
+ * ran. Measured on 2026-08-23, before this gate existed, **17** of the
7
+ * `check_*` / `lint_*` scripts under `src/scripts/` read the clock inline, every
8
+ * one of them to make an age or staleness judgement. None of those 17 verdicts
9
+ * was reproducible from the commit alone: the same tree greens on Monday and
10
+ * reds on Friday, and a reviewer cannot recover which "now" produced the verdict
11
+ * CI published.
12
+ *
13
+ * `src/scripts/_lib/as_of.ts` is the seam that fixes it — one resolver, four
14
+ * rungs (`--as-of` argv, `AC_AS_OF` env, the commit date under CI, and the wall
15
+ * clock with an explicit non-reproducibility WARN). This gate is what stops the
16
+ * defect coming back: substituting the seam once is a diff, keeping it
17
+ * substituted is a ratchet.
18
+ *
19
+ * SCOPE is deliberately the gate prefixes only (`check_*`, `lint_*` under
20
+ * `src/scripts/`). A generator, a bench runner, or a hook legitimately reads
21
+ * real time; a *verdict* should not. Widening this to the whole tree would turn
22
+ * a determinism ratchet into a style rule and earn the suppressions that come
23
+ * with one.
24
+ *
25
+ * WHY THE MATCH RUNS OVER STRIPPED SOURCE. This file's own docstring names the
26
+ * construct it forbids, and its self-test fixtures are string literals
27
+ * containing it. A gate whose scope includes its own filename (`lint_*` does)
28
+ * and which matched raw text would refuse itself — so comments and string /
29
+ * template literals are blanked to spaces (line numbers preserved) before the
30
+ * patterns run. The same reason `check_no_automerge_key` anchors on a key rather
31
+ * than a word.
32
+ *
33
+ * THE ESCAPE HATCH, AND WHY IT IS NARROW. `// wall-clock-required: <reason>` on
34
+ * the offending line exempts it, and the reason is mandatory — a bare marker is
35
+ * still a finding. It exists for the one legitimate case this ratchet must not
36
+ * break: measuring *elapsed duration* inside a single run, where real time is
37
+ * the subject rather than a threshold input. Prefer `performance.now()` there;
38
+ * the marker is for when a `Date` is genuinely what the API needs.
39
+ *
40
+ * Exit codes:
41
+ * 0 — no gate script reads the wall clock directly
42
+ * 1 — at least one raw wall-clock read (or a marker with no reason)
43
+ * 2 — the gate could not run (dead scan scope, bad args)
44
+ */
45
+ import * as fs from 'node:fs';
46
+ import * as os from 'node:os';
47
+ import * as path from 'node:path';
48
+ import { fileURLToPath, pathToFileURL } from 'node:url';
49
+
50
+ import { GateLedger, LedgerUsageError } from './_lib/gate_ledger.js';
51
+ import { runGateCli, runSelfTest, type SelfTestCase } from './_lib/gate_self_test.js';
52
+ import { DeadScopeError, reportScanned } from './_lib/scan_scope.js';
53
+
54
+ const _HERE = fileURLToPath(import.meta.url);
55
+ const DEFAULT_ROOT = path.join(path.dirname(_HERE), '..', '..');
56
+
57
+ /** This script's repo-relative path, for the self-test's CLI invocations. */
58
+ const SELF = 'src/scripts/lint_deterministic_time.ts';
59
+
60
+ const SELF_TEST_MIN_CASES = 6;
61
+ const SELF_TEST_MIN_REJECT = 3;
62
+
63
+ /** The directory the gate walks, and the prefixes that make a file a gate. */
64
+ const SCAN_DIR = 'src/scripts';
65
+ const GATE_PREFIXES = ['check_', 'lint_'] as const;
66
+
67
+ /**
68
+ * The single sanctioned reader of the wall clock. Repo-relative, and outside
69
+ * {@link GATE_PREFIXES} by construction, so it is never in the scan set — named
70
+ * here so the failure message can point at it.
71
+ */
72
+ const SEAM = 'src/scripts/_lib/as_of.ts';
73
+
74
+ /** Same-line exemption. The reason after the colon must be non-empty. */
75
+ const MARKER = 'wall-clock-required:';
76
+
77
+ /**
78
+ * The forbidden constructs. Written as tolerant regexes rather than literals so
79
+ * this file's own source does not contain the strings it bans — see the
80
+ * docstring's note on stripped source; belt and braces, and the belt is cheap.
81
+ */
82
+ const PATTERNS: readonly { id: string; re: RegExp; shown: string }[] = [
83
+ { id: 'date-now', re: /\bDate\s*\.\s*now\s*\(\s*\)/g, shown: 'Date' + '.now()' },
84
+ { id: 'new-date', re: /\bnew\s+Date\s*\(\s*\)/g, shown: 'new ' + 'Date()' },
85
+ ];
86
+
87
+ /**
88
+ * Neutralise every non-code region, preserving line numbers and line lengths so
89
+ * a match's line number is still the source's.
90
+ *
91
+ * Two details are load-bearing and were both found by running this gate against
92
+ * the real corpus before trusting it:
93
+ *
94
+ * 1. **Comments become spaces, string bodies become `x`.** Blanking a string to
95
+ * spaces collapses `new Date("2026-01-01")` into `new Date( )`,
96
+ * which the empty-parens pattern then matches — a false positive on a
97
+ * perfectly deterministic parse. `check_knowledge_pages.ts:103` is exactly
98
+ * that shape. Filling with a non-space keeps the argument visible as *an*
99
+ * argument without keeping its content.
100
+ * 2. **`${...}` inside a template literal is code.** Blanking it wholesale hid a
101
+ * real finding: `check_gate_coverage.ts:982` reads the clock inside a
102
+ * template substitution, and the first draft of this gate reported it clean.
103
+ *
104
+ * Regex literals are left alone deliberately: a regex describing the forbidden
105
+ * construct (this file's own {@link PATTERNS}) does not itself match it, so
106
+ * there is nothing to hide, and detecting a regex literal from a `/` needs
107
+ * expression context this scanner does not have.
108
+ */
109
+ export function stripNonCode(src: string): string {
110
+ const out = src.split('');
111
+ const n = src.length;
112
+ const put = (i: number, ch: string): void => {
113
+ if (out[i] !== '\n') out[i] = ch;
114
+ };
115
+
116
+ /** `//` to end of line. */
117
+ function lineComment(i: number): number {
118
+ while (i < n && src[i] !== '\n') put(i++, ' ');
119
+ return i;
120
+ }
121
+ /** `/*` to the closing delimiter. */
122
+ function blockComment(i: number): number {
123
+ put(i++, ' ');
124
+ put(i++, ' ');
125
+ while (i < n && !(src[i] === '*' && src[i + 1] === '/')) put(i++, ' ');
126
+ if (i < n) {
127
+ put(i++, ' ');
128
+ put(i++, ' ');
129
+ }
130
+ return i;
131
+ }
132
+ /** A `'` or `"` literal; an unterminated one stops at the newline. */
133
+ function simpleString(i: number): number {
134
+ const quote = src[i];
135
+ put(i++, 'x');
136
+ while (i < n) {
137
+ if (src[i] === '\\') {
138
+ put(i++, 'x');
139
+ if (i < n) put(i++, 'x');
140
+ continue;
141
+ }
142
+ if (src[i] === quote) {
143
+ put(i++, 'x');
144
+ break;
145
+ }
146
+ if (src[i] === '\n') break;
147
+ put(i++, 'x');
148
+ }
149
+ return i;
150
+ }
151
+ /** A `\`` literal, recursing into every `${...}` as code. */
152
+ function template(i: number): number {
153
+ put(i++, 'x');
154
+ while (i < n) {
155
+ if (src[i] === '\\') {
156
+ put(i++, 'x');
157
+ if (i < n) put(i++, 'x');
158
+ continue;
159
+ }
160
+ if (src[i] === '`') {
161
+ put(i++, 'x');
162
+ break;
163
+ }
164
+ if (src[i] === '$' && src[i + 1] === '{') {
165
+ i = substitution(i + 2);
166
+ continue;
167
+ }
168
+ put(i++, 'x');
169
+ }
170
+ return i;
171
+ }
172
+ /** Code inside `${ ... }`, up to the brace that closes it. */
173
+ function substitution(i: number): number {
174
+ let depth = 1;
175
+ while (i < n && depth > 0) {
176
+ const c = src[i];
177
+ const next = src[i + 1];
178
+ if (c === '/' && next === '/') {
179
+ i = lineComment(i);
180
+ continue;
181
+ }
182
+ if (c === '/' && next === '*') {
183
+ i = blockComment(i);
184
+ continue;
185
+ }
186
+ if (c === "'" || c === '"') {
187
+ i = simpleString(i);
188
+ continue;
189
+ }
190
+ if (c === '`') {
191
+ i = template(i);
192
+ continue;
193
+ }
194
+ if (c === '{') depth += 1;
195
+ if (c === '}') depth -= 1;
196
+ i += 1;
197
+ }
198
+ return i;
199
+ }
200
+
201
+ let i = 0;
202
+ while (i < n) {
203
+ const c = src[i];
204
+ const next = src[i + 1];
205
+ if (c === '/' && next === '/') {
206
+ i = lineComment(i);
207
+ continue;
208
+ }
209
+ if (c === '/' && next === '*') {
210
+ i = blockComment(i);
211
+ continue;
212
+ }
213
+ if (c === "'" || c === '"') {
214
+ i = simpleString(i);
215
+ continue;
216
+ }
217
+ if (c === '`') {
218
+ i = template(i);
219
+ continue;
220
+ }
221
+ i += 1;
222
+ }
223
+ return out.join('');
224
+ }
225
+
226
+ export interface Finding {
227
+ file: string;
228
+ line: number;
229
+ construct: string;
230
+ /** `raw` — an unmarked read. `bare-marker` — a marker with no reason. */
231
+ kind: 'raw' | 'bare-marker';
232
+ }
233
+
234
+ /** Does this raw line carry a marker, and does the marker carry a reason? */
235
+ function markerOn(rawLine: string): 'none' | 'bare' | 'reasoned' {
236
+ const at = rawLine.indexOf(MARKER);
237
+ if (at < 0) return 'none';
238
+ return rawLine.slice(at + MARKER.length).trim() === '' ? 'bare' : 'reasoned';
239
+ }
240
+
241
+ export function scanSource(relPath: string, src: string): Finding[] {
242
+ const rawLines = src.split('\n');
243
+ const codeLines = stripNonCode(src).split('\n');
244
+ const findings: Finding[] = [];
245
+ for (let n = 0; n < codeLines.length; n += 1) {
246
+ const code = codeLines[n] ?? '';
247
+ for (const pat of PATTERNS) {
248
+ pat.re.lastIndex = 0;
249
+ if (!pat.re.test(code)) continue;
250
+ const marker = markerOn(rawLines[n] ?? '');
251
+ if (marker === 'reasoned') continue;
252
+ findings.push({
253
+ file: relPath,
254
+ line: n + 1,
255
+ construct: pat.shown,
256
+ kind: marker === 'bare' ? 'bare-marker' : 'raw',
257
+ });
258
+ }
259
+ }
260
+ return findings;
261
+ }
262
+
263
+ /** Repo-relative paths of every gate script under {@link SCAN_DIR}. */
264
+ export function gateScripts(root: string): string[] {
265
+ const dir = path.join(root, SCAN_DIR);
266
+ let entries: string[];
267
+ try {
268
+ entries = fs.readdirSync(dir);
269
+ } catch {
270
+ return [];
271
+ }
272
+ return entries
273
+ .filter(
274
+ (name) =>
275
+ name.endsWith('.ts') &&
276
+ !name.endsWith('.test.ts') &&
277
+ GATE_PREFIXES.some((p) => name.startsWith(p)),
278
+ )
279
+ .sort()
280
+ .map((name) => `${SCAN_DIR}/${name}`);
281
+ }
282
+
283
+ export function check(root: string): { code: number; findings: Finding[]; scanned: number } {
284
+ const files = gateScripts(root);
285
+ const findings: Finding[] = [];
286
+ const ledger = new GateLedger('lint_deterministic_time');
287
+
288
+ try {
289
+ reportScanned({
290
+ gate: 'lint_deterministic_time',
291
+ scanned: files.length,
292
+ units: 'gate script(s)',
293
+ roots: [SCAN_DIR],
294
+ });
295
+ } catch (err) {
296
+ if (err instanceof DeadScopeError) {
297
+ process.stderr.write(`❌ ${err.message}\n`);
298
+ return { code: 2, findings, scanned: files.length };
299
+ }
300
+ throw err;
301
+ }
302
+
303
+ try {
304
+ ledger.plan(files);
305
+ for (const rel of files) {
306
+ const found = scanSource(rel, fs.readFileSync(path.join(root, rel), 'utf-8'));
307
+ if (found.length > 0) {
308
+ findings.push(...found);
309
+ ledger.fail(rel, `${String(found.length)} raw wall-clock read(s)`);
310
+ } else {
311
+ ledger.complete(rel);
312
+ }
313
+ }
314
+ } catch (err) {
315
+ if (err instanceof LedgerUsageError) {
316
+ process.stderr.write(`❌ ${err.message}\n`);
317
+ return { code: 2, findings, scanned: files.length };
318
+ }
319
+ throw err;
320
+ }
321
+ ledger.report();
322
+
323
+ return { code: findings.length > 0 ? 1 : 0, findings, scanned: files.length };
324
+ }
325
+
326
+ /* ------------------------------------------------------------------ *
327
+ * Self-test
328
+ * ------------------------------------------------------------------ */
329
+
330
+ function fixtureRoot(files: Record<string, string>): string {
331
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'det-time-'));
332
+ fs.mkdirSync(path.join(dir, SCAN_DIR), { recursive: true });
333
+ for (const [name, body] of Object.entries(files)) {
334
+ fs.writeFileSync(path.join(dir, SCAN_DIR, name), body);
335
+ }
336
+ return dir;
337
+ }
338
+
339
+ const CLEAN_GATE = ['import { asOf } from ', "'./_lib/as_of.js';", '\nconst now = asOf();\n'].join('');
340
+
341
+ export function selfTest(): number {
342
+ const made: string[] = [];
343
+ const run = (files: Record<string, string>): number => {
344
+ const d = fixtureRoot(files);
345
+ made.push(d);
346
+ return runGateCli(DEFAULT_ROOT, SELF, ['--quiet', '--root', d], DEFAULT_ROOT);
347
+ };
348
+ // Built by concatenation so this file's own source stays clean.
349
+ const rawNow = 'const t = ' + 'Date' + '.now();\n';
350
+ const rawNew = 'const d = new ' + 'Date();\n';
351
+
352
+ const cases: SelfTestCase[] = [
353
+ {
354
+ name: `rejects a planted ${PATTERNS[0]?.shown ?? ''} in a check_ script`,
355
+ expect: 'reject',
356
+ run: () => run({ 'check_planted.ts': rawNow }),
357
+ },
358
+ {
359
+ name: `rejects a planted bare ${PATTERNS[1]?.shown ?? ''} in a lint_ script`,
360
+ expect: 'reject',
361
+ run: () => run({ 'lint_planted.ts': rawNew }),
362
+ },
363
+ {
364
+ name: 'rejects a marker with no reason',
365
+ expect: 'reject',
366
+ run: () => run({ 'check_planted.ts': rawNow.trimEnd() + ' // ' + MARKER + '\n' }),
367
+ },
368
+ {
369
+ name: 'accepts a marker carrying a reason',
370
+ expect: 'accept',
371
+ run: () =>
372
+ run({
373
+ 'check_ok.ts': rawNow.trimEnd() + ' // ' + MARKER + ' elapsed-duration probe\n',
374
+ }),
375
+ },
376
+ {
377
+ name: 'accepts a mention inside a comment or a string literal',
378
+ expect: 'accept',
379
+ run: () =>
380
+ run({
381
+ 'check_prose.ts':
382
+ '// forbids ' + 'Date' + '.now()\nconst s = "new ' + 'Date()";\n',
383
+ }),
384
+ },
385
+ {
386
+ name: 'accepts a gate that routes through the seam',
387
+ expect: 'accept',
388
+ run: () => run({ 'check_ok.ts': CLEAN_GATE }),
389
+ },
390
+ {
391
+ name: 'rejects a moved scan root rather than reporting clean (exit 2)',
392
+ expect: 'reject',
393
+ run: () =>
394
+ runGateCli(
395
+ DEFAULT_ROOT,
396
+ SELF,
397
+ ['--quiet', '--root', path.join(DEFAULT_ROOT, 'no-such-root')],
398
+ DEFAULT_ROOT,
399
+ ),
400
+ },
401
+ ];
402
+
403
+ try {
404
+ return runSelfTest({
405
+ gate: 'lint_deterministic_time',
406
+ cases,
407
+ minCases: SELF_TEST_MIN_CASES,
408
+ minRejectCases: SELF_TEST_MIN_REJECT,
409
+ });
410
+ } finally {
411
+ for (const d of made) fs.rmSync(d, { recursive: true, force: true });
412
+ }
413
+ }
414
+
415
+ export function main(argv?: readonly string[]): number {
416
+ const args = argv ?? process.argv.slice(2);
417
+ if (args.includes('--self-test')) return selfTest();
418
+ const quiet = args.includes('--quiet');
419
+ const rootIdx = args.indexOf('--root');
420
+ const root = rootIdx >= 0 ? (args[rootIdx + 1] ?? DEFAULT_ROOT) : DEFAULT_ROOT;
421
+
422
+ const { code, findings, scanned } = check(root);
423
+ if (code === 2) return 2;
424
+
425
+ if (findings.length > 0) {
426
+ process.stdout.write('❌ gate script(s) read the wall clock directly:\n\n');
427
+ for (const f of findings) {
428
+ const why =
429
+ f.kind === 'bare-marker'
430
+ ? `\`${MARKER}\` with no reason`
431
+ : `raw \`${f.construct}\``;
432
+ process.stdout.write(` ${f.file}:${String(f.line)} — ${why}\n`);
433
+ }
434
+ process.stdout.write(
435
+ `\nA verdict that depends on the hour it ran is not reproducible from the\n` +
436
+ `commit. Route through \`asOf()\` in ${SEAM} — it resolves --as-of, AC_AS_OF,\n` +
437
+ `the commit date under CI, and the wall clock with an explicit WARN.\n` +
438
+ `If real elapsed time IS the subject, say so on the line:\n` +
439
+ ` // ${MARKER} <reason>\n`,
440
+ );
441
+ return 1;
442
+ }
443
+
444
+ if (!quiet) {
445
+ process.stdout.write(
446
+ `✅ no raw wall-clock read in ${String(scanned)} gate script(s) under ${SCAN_DIR}/.\n`,
447
+ );
448
+ }
449
+ return 0;
450
+ }
451
+
452
+ function _isCliEntry(): boolean {
453
+ if (process.argv[1] === undefined) return false;
454
+ const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
455
+ if (import.meta.url === argvUrl) return true;
456
+ try {
457
+ return (
458
+ fs.realpathSync(fileURLToPath(import.meta.url)) ===
459
+ fs.realpathSync(path.resolve(process.argv[1]))
460
+ );
461
+ } catch {
462
+ return false;
463
+ }
464
+ }
465
+
466
+ if (_isCliEntry() || process.argv[1] === _HERE) {
467
+ process.exit(main());
468
+ }
469
+
470
+ export { SCAN_DIR, GATE_PREFIXES, SEAM, MARKER };
@@ -102,6 +102,11 @@ const CARVE_OUT_PATTERNS = [
102
102
  '^mobile-e2e',
103
103
  '^project-analysis-(laravel|symfony|nextjs|react|node-express|zend-laminas)',
104
104
  '^docker', '^aws-', '^grafana', '^playwright',
105
+ // JS/TS workspace tooling. Same class as `^composer-` one line up: a skill
106
+ // ABOUT a package manager cannot describe it without naming its manifest.
107
+ // `package.json` / `node_modules` in these two files is the subject, not a
108
+ // mandate that some unrelated project adopt Node.
109
+ '^monorepo-workspace$', '^workspace-link$',
105
110
  '^laravel-', '^docker-', '^symfony-', '^copilot-', '^devcontainer',
106
111
  '-routing$',
107
112
  ];