@getarbiter/cli 0.5.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 (989) hide show
  1. package/CHANGELOG.md +713 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +31 -0
  4. package/PRIVACY.md +111 -0
  5. package/README.md +237 -0
  6. package/THIRD_PARTY_LICENSES.md +472 -0
  7. package/dist/.src-manifest.json +1 -0
  8. package/dist/capabilities/host-probe.js +3 -0
  9. package/dist/cli.d.ts +2 -0
  10. package/dist/cli.js +87 -0
  11. package/dist/commands/adopt-policy.js +3 -0
  12. package/dist/commands/configure-interactive.js +3 -0
  13. package/dist/commands/configure.js +5 -0
  14. package/dist/commands/cross-model-review.js +3 -0
  15. package/dist/commands/diff.js +20 -0
  16. package/dist/commands/doc-set.js +3 -0
  17. package/dist/commands/doctor/clean.js +2 -0
  18. package/dist/commands/doctor/fail-open-census.js +12 -0
  19. package/dist/commands/doctor/health.js +11 -0
  20. package/dist/commands/doctor/prove-gates.js +13 -0
  21. package/dist/commands/doctor/recover-lock.js +12 -0
  22. package/dist/commands/doctor/repair-state.js +6 -0
  23. package/dist/commands/doctor/tool-pin-extract.js +4 -0
  24. package/dist/commands/doctor/tool-pins.js +9 -0
  25. package/dist/commands/doctor-interactive.js +2 -0
  26. package/dist/commands/doctor.js +2 -0
  27. package/dist/commands/explain.js +19 -0
  28. package/dist/commands/gate-exec.js +4 -0
  29. package/dist/commands/gold-audit.js +14 -0
  30. package/dist/commands/graph.js +5 -0
  31. package/dist/commands/init/build-arbiter-config.js +1 -0
  32. package/dist/commands/init/generate.js +42 -0
  33. package/dist/commands/init/github-setup.js +5 -0
  34. package/dist/commands/init/maturity-gates.js +10 -0
  35. package/dist/commands/init/resolve-config.js +3 -0
  36. package/dist/commands/init/toolchain.js +10 -0
  37. package/dist/commands/init/types.js +1 -0
  38. package/dist/commands/init.js +17 -0
  39. package/dist/commands/method-interactive.js +4 -0
  40. package/dist/commands/method.js +11 -0
  41. package/dist/commands/obsidian.js +2 -0
  42. package/dist/commands/plugin.js +8 -0
  43. package/dist/commands/pr-merged.js +2 -0
  44. package/dist/commands/retire-policy.js +3 -0
  45. package/dist/commands/review-diff.js +3 -0
  46. package/dist/commands/settings.js +7 -0
  47. package/dist/commands/ship-config.js +2 -0
  48. package/dist/commands/ship-profile.js +2 -0
  49. package/dist/commands/ship-review.js +1 -0
  50. package/dist/commands/ship-tier.js +2 -0
  51. package/dist/commands/ship-train.js +2 -0
  52. package/dist/commands/task-mark.js +2 -0
  53. package/dist/commands/task-note.js +3 -0
  54. package/dist/commands/task-record-red.js +2 -0
  55. package/dist/commands/task-record-tech-debt.js +4 -0
  56. package/dist/commands/task-ship.js +3 -0
  57. package/dist/commands/task-state.js +6 -0
  58. package/dist/commands/task.js +59 -0
  59. package/dist/commands/update.js +60 -0
  60. package/dist/commands/upgrade-level-interactive.js +2 -0
  61. package/dist/commands/upgrade-level.js +7 -0
  62. package/dist/commands/verify-plan.js +8 -0
  63. package/dist/commands/verify-tdd.js +2 -0
  64. package/dist/commands/verify.js +7 -0
  65. package/dist/commands/wizard/coherence.js +1 -0
  66. package/dist/commands/worktree-prune.js +4 -0
  67. package/dist/commands/worktree.js +39 -0
  68. package/dist/compatibility/cross-language-matrix.json +540 -0
  69. package/dist/compatibility/index.d.ts +4 -0
  70. package/dist/compatibility/index.js +2 -0
  71. package/dist/compatibility/matcher.js +1 -0
  72. package/dist/compatibility/matrix.json +27 -0
  73. package/dist/compatibility/parsers.d.ts +38 -0
  74. package/dist/compatibility/parsers.js +1 -0
  75. package/dist/compatibility/probe.d.ts +41 -0
  76. package/dist/compatibility/probe.js +4 -0
  77. package/dist/compatibility/report.js +2 -0
  78. package/dist/compatibility/schema.d.ts +80 -0
  79. package/dist/compatibility/schema.js +1 -0
  80. package/dist/compatibility/skills-matrix.json +98 -0
  81. package/dist/compatibility/skills-validator.d.ts +30 -0
  82. package/dist/compatibility/skills-validator.js +4 -0
  83. package/dist/compatibility/tooling-proposals.json +5 -0
  84. package/dist/config/arbiter-ignore.js +3 -0
  85. package/dist/config/collaboration-mode-defaults.js +1 -0
  86. package/dist/config/diff.js +1 -0
  87. package/dist/config/env-overrides.js +3 -0
  88. package/dist/config/env-registry.js +2 -0
  89. package/dist/config/levels.js +1 -0
  90. package/dist/config/migrations/index.js +2 -0
  91. package/dist/config/migrations/v0-to-v1.js +2 -0
  92. package/dist/config/migrations/v1-to-v2.js +2 -0
  93. package/dist/config/migrations/v2-to-v3.js +2 -0
  94. package/dist/config/migrations/v3-to-v4.js +2 -0
  95. package/dist/config/override-resolver.js +2 -0
  96. package/dist/config/resolve-project-config.js +2 -0
  97. package/dist/config/resolve-project-name.js +2 -0
  98. package/dist/config/schema.d.ts +480 -0
  99. package/dist/config/schema.js +1 -0
  100. package/dist/config/test-pyramid-profiles.js +1 -0
  101. package/dist/config/thresholds.js +2 -0
  102. package/dist/conformance/dimensions.js +3 -0
  103. package/dist/conformance/engine.js +7 -0
  104. package/dist/conformance/gate-proofs.js +4 -0
  105. package/dist/conformance/shared.js +2 -0
  106. package/dist/detectors/axis.js +1 -0
  107. package/dist/detectors/build.js +2 -0
  108. package/dist/detectors/existing.js +2 -0
  109. package/dist/detectors/external-model.js +2 -0
  110. package/dist/detectors/framework.js +2 -0
  111. package/dist/detectors/git.js +11 -0
  112. package/dist/detectors/github.js +2 -0
  113. package/dist/detectors/lanes.js +5 -0
  114. package/dist/detectors/language-hooks.js +117 -0
  115. package/dist/detectors/language.js +2 -0
  116. package/dist/detectors/package-manager.js +2 -0
  117. package/dist/detectors/package.js +2 -0
  118. package/dist/detectors/scaffold-wiring.js +2 -0
  119. package/dist/detectors/workflow-collision.js +4 -0
  120. package/dist/evidence/gate-binding.js +4 -0
  121. package/dist/evidence/git-checks.js +3 -0
  122. package/dist/evidence/load.js +2 -0
  123. package/dist/evidence/provenance.js +2 -0
  124. package/dist/evidence/summary.js +2 -0
  125. package/dist/evidence/tdd-reexecute.js +2 -0
  126. package/dist/evidence/tdd.js +4 -0
  127. package/dist/experimental/index.js +2 -0
  128. package/dist/experimental/parse-argv.js +2 -0
  129. package/dist/experimental/registry.js +2 -0
  130. package/dist/generators/agents-claude.js +2 -0
  131. package/dist/generators/agents-md.js +2 -0
  132. package/dist/generators/anti-drift-validators.js +3 -0
  133. package/dist/generators/anti-proforma.js +2 -0
  134. package/dist/generators/api-e2e.js +2 -0
  135. package/dist/generators/api-middleware.js +2 -0
  136. package/dist/generators/archunit.js +2 -0
  137. package/dist/generators/audit-toolchain.js +2 -0
  138. package/dist/generators/auth.js +2 -0
  139. package/dist/generators/behavioral-tests.js +2 -0
  140. package/dist/generators/boundaries.js +2 -0
  141. package/dist/generators/check-all.js +2 -0
  142. package/dist/generators/check-stack-conformity.js +2 -0
  143. package/dist/generators/ci-five-lane.js +2 -0
  144. package/dist/generators/ci-tier.js +2 -0
  145. package/dist/generators/claude.js +3 -0
  146. package/dist/generators/codex-hooks.js +2 -0
  147. package/dist/generators/codex-known-limitations.js +2 -0
  148. package/dist/generators/codex.js +15 -0
  149. package/dist/generators/commit-footer.js +2 -0
  150. package/dist/generators/compliance-menu.js +2 -0
  151. package/dist/generators/compliance.js +2 -0
  152. package/dist/generators/conformance.js +2 -0
  153. package/dist/generators/contract-testing.js +2 -0
  154. package/dist/generators/coverage.js +2 -0
  155. package/dist/generators/debt-gates.js +24 -0
  156. package/dist/generators/debt-ratchet.js +2 -0
  157. package/dist/generators/derived-class.js +2 -0
  158. package/dist/generators/doc-set.js +4 -0
  159. package/dist/generators/docs.js +2 -0
  160. package/dist/generators/duplication.js +2 -0
  161. package/dist/generators/e2e-constitution.js +2 -0
  162. package/dist/generators/env-template.js +2 -0
  163. package/dist/generators/evidence-retention.js +2 -0
  164. package/dist/generators/feature-matrix.js +2 -0
  165. package/dist/generators/frontend-governance.js +2 -0
  166. package/dist/generators/frontend-quality.js +2 -0
  167. package/dist/generators/gap.js +2 -0
  168. package/dist/generators/gdpr.js +2 -0
  169. package/dist/generators/githooks.js +2 -0
  170. package/dist/generators/github-setup.js +2 -0
  171. package/dist/generators/github.js +2 -0
  172. package/dist/generators/gitignore.js +2 -0
  173. package/dist/generators/global-invariants.js +3 -0
  174. package/dist/generators/go-boundaries.js +2 -0
  175. package/dist/generators/gold-kit.js +2 -0
  176. package/dist/generators/infra.js +2 -0
  177. package/dist/generators/integration-testing.js +7 -0
  178. package/dist/generators/iso27001.js +2 -0
  179. package/dist/generators/iso9001.js +2 -0
  180. package/dist/generators/labels.js +2 -0
  181. package/dist/generators/local-wrapper.js +2 -0
  182. package/dist/generators/modulith.js +2 -0
  183. package/dist/generators/mutation.js +2 -0
  184. package/dist/generators/observability.js +2 -0
  185. package/dist/generators/operations.js +2 -0
  186. package/dist/generators/perf-k6.js +2 -0
  187. package/dist/generators/pharma.js +2 -0
  188. package/dist/generators/playwright-python.js +2 -0
  189. package/dist/generators/playwright-ts.js +2 -0
  190. package/dist/generators/pr-tooling.js +2 -0
  191. package/dist/generators/python-boundaries.js +2 -0
  192. package/dist/generators/quality.js +2 -0
  193. package/dist/generators/registry.js +2 -0
  194. package/dist/generators/regulated.js +2 -0
  195. package/dist/generators/resilience.js +2 -0
  196. package/dist/generators/risk-register.js +2 -0
  197. package/dist/generators/root.js +2 -0
  198. package/dist/generators/rust-boundaries.js +2 -0
  199. package/dist/generators/safety-class.js +2 -0
  200. package/dist/generators/security.js +2 -0
  201. package/dist/generators/seed.js +2 -0
  202. package/dist/generators/self-validation.js +2 -0
  203. package/dist/generators/ship-driver.js +2 -0
  204. package/dist/generators/skill-names.json +17 -0
  205. package/dist/generators/skills.js +2 -0
  206. package/dist/generators/smoke-journeys.js +2 -0
  207. package/dist/generators/solo-exception.js +2 -0
  208. package/dist/generators/ssot.js +2 -0
  209. package/dist/generators/stride-enforcement.js +2 -0
  210. package/dist/generators/suppressions.js +2 -0
  211. package/dist/generators/test-pyramid-manifest.js +3 -0
  212. package/dist/generators/test-taxonomy.js +2 -0
  213. package/dist/generators/wiki.js +2 -0
  214. package/dist/github/branch-protection.js +2 -0
  215. package/dist/github/classify-gh-error.js +2 -0
  216. package/dist/github/labels.js +2 -0
  217. package/dist/github/project-board.js +2 -0
  218. package/dist/graph/builders/adr.js +4 -0
  219. package/dist/graph/builders/ast.js +3 -0
  220. package/dist/graph/builders/canon.js +2 -0
  221. package/dist/graph/builders/evidence.js +2 -0
  222. package/dist/graph/builders/inv.js +1 -0
  223. package/dist/graph/builders/req.js +3 -0
  224. package/dist/graph/builders/test-nodes.js +3 -0
  225. package/dist/graph/builders/utils.js +2 -0
  226. package/dist/graph/load.js +2 -0
  227. package/dist/graph/model.js +2 -0
  228. package/dist/graph/query.js +3 -0
  229. package/dist/graph/store.js +1 -0
  230. package/dist/i18n/en.json +293 -0
  231. package/dist/i18n/index.js +4 -0
  232. package/dist/i18n/types.js +2 -0
  233. package/dist/integrations/companions.js +2 -0
  234. package/dist/integrations/external-review.js +10 -0
  235. package/dist/integrations/plugin-schema.js +2 -0
  236. package/dist/integrations/skill-detector.js +7 -0
  237. package/dist/integrations/skills-matrix.js +2 -0
  238. package/dist/integrations/types.d.ts +23 -0
  239. package/dist/integrations/types.js +2 -0
  240. package/dist/internal/cli-deprecation-registry.js +2 -0
  241. package/dist/internal/deprecate.js +3 -0
  242. package/dist/internal/experimental-warn.js +3 -0
  243. package/dist/invariants/catalog.d.ts +2 -0
  244. package/dist/invariants/catalog.js +1 -0
  245. package/dist/invariants/filter.d.ts +28 -0
  246. package/dist/invariants/filter.js +1 -0
  247. package/dist/invariants/index.d.ts +4 -0
  248. package/dist/invariants/index.js +2 -0
  249. package/dist/invariants/tiers.js +1 -0
  250. package/dist/invariants/types.d.ts +1 -0
  251. package/dist/invariants/types.js +1 -0
  252. package/dist/kit/applicability.js +3 -0
  253. package/dist/kit/audit-report.js +3 -0
  254. package/dist/kit/brownfield-detect.js +4 -0
  255. package/dist/kit/canonical-mapping.json +1 -0
  256. package/dist/kit/catalog.js +2 -0
  257. package/dist/kit/catalog.json +1 -0
  258. package/dist/kit/derived.json +1 -0
  259. package/dist/kit/emit-issues.js +8 -0
  260. package/dist/kit/measure.js +5 -0
  261. package/dist/kit/redaction.js +3 -0
  262. package/dist/kit/schema.js +2 -0
  263. package/dist/kit/taxonomy.js +2 -0
  264. package/dist/kit/thresholds.d.ts +12 -0
  265. package/dist/kit/thresholds.js +2 -0
  266. package/dist/kit/wave-engine.js +1 -0
  267. package/dist/recipes/loader.js +6 -0
  268. package/dist/recipes/schema.js +2 -0
  269. package/dist/risk/classifier.js +2 -0
  270. package/dist/risk/sha-check.js +2 -0
  271. package/dist/state/backups.js +2 -0
  272. package/dist/state/envelope.js +2 -0
  273. package/dist/state/generated-manifest.js +3 -0
  274. package/dist/state/manifest-key.js +2 -0
  275. package/dist/state/migrations/index.js +1 -0
  276. package/dist/state/migrations/v0-to-v1.js +2 -0
  277. package/dist/state/retired-renders.js +2 -0
  278. package/dist/templates/.nvmrc.ejs +1 -0
  279. package/dist/templates/HANDOFF.template.md +45 -0
  280. package/dist/templates/__tests__/contract/error-shape.contract.test.ts.ejs +47 -0
  281. package/dist/templates/agents-md/AGENTS.md.ejs +517 -0
  282. package/dist/templates/api-e2e/README.md.ejs +33 -0
  283. package/dist/templates/api-e2e/go-httptest.go.ejs +43 -0
  284. package/dist/templates/api-e2e/java-restassured.java.ejs +28 -0
  285. package/dist/templates/api-e2e/kotlin-restassured.kt.ejs +30 -0
  286. package/dist/templates/api-e2e/manifest.json.ejs +8 -0
  287. package/dist/templates/api-e2e/postman.collection.json.ejs +30 -0
  288. package/dist/templates/api-e2e/python-httpx.py.ejs +17 -0
  289. package/dist/templates/api-e2e/run.sh.ejs +61 -0
  290. package/dist/templates/api-e2e/ts-supertest.test.ts.ejs +18 -0
  291. package/dist/templates/archunit/AntiCyclicTest.java.ejs +29 -0
  292. package/dist/templates/archunit/AntiProformaExempt.java.ejs +35 -0
  293. package/dist/templates/archunit/AntiProformaTest.java.ejs +104 -0
  294. package/dist/templates/archunit/ArchitectureTest.java.ejs +87 -0
  295. package/dist/templates/archunit/DependencyFlowTest.java.ejs +55 -0
  296. package/dist/templates/archunit/DomainPurityTest.java.ejs +47 -0
  297. package/dist/templates/archunit/NamingConventionsTest.java.ejs +54 -0
  298. package/dist/templates/archunit/NoH2ArchTest.java.ejs +30 -0
  299. package/dist/templates/archunit/NoMockMvcTest.java.ejs +71 -0
  300. package/dist/templates/archunit/PortsIndependenceTest.java.ejs +55 -0
  301. package/dist/templates/archunit/RestAssuredArchTest.java.ejs +32 -0
  302. package/dist/templates/archunit/RestAssuredBaseIT.java.ejs +66 -0
  303. package/dist/templates/archunit/TestCoverageArchTest.java.ejs +29 -0
  304. package/dist/templates/archunit/arch-test-deps-maven.md.ejs +42 -0
  305. package/dist/templates/archunit/arch-test-deps.gradle.ejs +14 -0
  306. package/dist/templates/audit/gdpr/check-gdpr-controls.mjs.ejs +102 -0
  307. package/dist/templates/audit/gdpr/controls-to-gates.md.ejs +42 -0
  308. package/dist/templates/audit/gdpr/data-flow-map.md.ejs +30 -0
  309. package/dist/templates/audit/gdpr/dpia.md.ejs +45 -0
  310. package/dist/templates/audit/generic/audit-gate-rules.md.ejs +24 -0
  311. package/dist/templates/audit/generic/audit-trail-policy.md.ejs +38 -0
  312. package/dist/templates/audit/iso27001/iso27001-controls-gate-map.md.ejs +60 -0
  313. package/dist/templates/auth/setup.md.ejs +661 -0
  314. package/dist/templates/behavioral-tests/ExampleBehavioralTest.java.ejs +67 -0
  315. package/dist/templates/behavioral-tests/TESTING_POLICY.md.ejs +116 -0
  316. package/dist/templates/behavioral-tests/bdd/ExampleBddIT.java.ejs +19 -0
  317. package/dist/templates/behavioral-tests/bdd/ExampleSteps.java.ejs +61 -0
  318. package/dist/templates/behavioral-tests/bdd/example.feature.ejs +14 -0
  319. package/dist/templates/behavioral-tests/bdd/example.steps.ts.ejs +37 -0
  320. package/dist/templates/behavioral-tests/bdd/example_bdd_test.rs.ejs +64 -0
  321. package/dist/templates/behavioral-tests/bdd/example_test.go.ejs +80 -0
  322. package/dist/templates/behavioral-tests/bdd/test_example_bdd.py.ejs +47 -0
  323. package/dist/templates/behavioral-tests/eslint-playwright.json.ejs +10 -0
  324. package/dist/templates/behavioral-tests/eslint.config.playwright.mjs.ejs +29 -0
  325. package/dist/templates/behavioral-tests/example.behavioral.test.ts.ejs +47 -0
  326. package/dist/templates/behavioral-tests/example_behavioral_test.go.ejs +51 -0
  327. package/dist/templates/behavioral-tests/example_behavioral_test.rs.ejs +47 -0
  328. package/dist/templates/behavioral-tests/test_example_behavioral.py.ejs +40 -0
  329. package/dist/templates/boundaries/.eslintrc-boundaries.cjs.ejs +81 -0
  330. package/dist/templates/boundaries/.eslintrc-frontend-spa.cjs.ejs +79 -0
  331. package/dist/templates/boundaries/.importlinter.ejs +48 -0
  332. package/dist/templates/boundaries/check-boundaries-go.mjs.ejs +94 -0
  333. package/dist/templates/boundaries/check-boundaries-python.mjs.ejs +118 -0
  334. package/dist/templates/boundaries/check-boundaries-rust.mjs.ejs +111 -0
  335. package/dist/templates/boundaries/check-boundaries.mjs.ejs +34 -0
  336. package/dist/templates/boundaries/clippy.toml.ejs +50 -0
  337. package/dist/templates/boundaries/deny.toml.ejs +56 -0
  338. package/dist/templates/boundaries/eslint.config.boundaries.mjs.ejs +112 -0
  339. package/dist/templates/boundaries/eslint.config.frontend-spa.mjs.ejs +93 -0
  340. package/dist/templates/boundaries/golangci-boundaries.yml.ejs +70 -0
  341. package/dist/templates/boundaries/ruff-boundaries.toml.ejs +32 -0
  342. package/dist/templates/claude/CLAUDE.md.ejs +78 -0
  343. package/dist/templates/claude/agents/agent-write-classes.json +30 -0
  344. package/dist/templates/claude/agents/bridge-reviewer.md.ejs +68 -0
  345. package/dist/templates/claude/agents/codebase-scanner.md.ejs +69 -0
  346. package/dist/templates/claude/agents/context-checker.md.ejs +73 -0
  347. package/dist/templates/claude/agents/red-team.md.ejs +134 -0
  348. package/dist/templates/claude/commands/close-gold-gap.md.ejs +43 -0
  349. package/dist/templates/claude/commands/drain.md.ejs +81 -0
  350. package/dist/templates/claude/commands/gold-audit.md.ejs +43 -0
  351. package/dist/templates/claude/commands/impact.md.ejs +39 -0
  352. package/dist/templates/claude/commands/levelup.md.ejs +61 -0
  353. package/dist/templates/claude/commands/ship.md.ejs +902 -0
  354. package/dist/templates/claude/commands/tabletop.md.ejs +46 -0
  355. package/dist/templates/claude/commands/task.md.ejs +87 -0
  356. package/dist/templates/claude/commands/wt-close.md.ejs +58 -0
  357. package/dist/templates/claude/commands/wt-list.md.ejs +48 -0
  358. package/dist/templates/claude/commands/wt-open.md.ejs +61 -0
  359. package/dist/templates/claude/commands/wt-prune.md.ejs +38 -0
  360. package/dist/templates/claude/hooks/check-circular-deps.mjs.ejs +56 -0
  361. package/dist/templates/claude/hooks/check-no-orphan-todo.mjs.ejs +45 -0
  362. package/dist/templates/claude/hooks/check-no-pii.mjs.ejs +121 -0
  363. package/dist/templates/claude/hooks/check-no-placeholders.mjs.ejs +71 -0
  364. package/dist/templates/claude/hooks/check-no-skipped-tests.mjs +80 -0
  365. package/dist/templates/claude/hooks/check-no-unused-exports.mjs +62 -0
  366. package/dist/templates/claude/hooks/closer-mode-guard.mjs.ejs +102 -0
  367. package/dist/templates/claude/hooks/debug-state-on-failure.mjs.ejs +98 -0
  368. package/dist/templates/claude/hooks/enforce-gate-before-pr.mjs +142 -0
  369. package/dist/templates/claude/hooks/enforce-read-only.mjs +28 -0
  370. package/dist/templates/claude/hooks/exitplanmode-banner.mjs.ejs +43 -0
  371. package/dist/templates/claude/hooks/guard-done-evidence.mjs.ejs +85 -0
  372. package/dist/templates/claude/hooks/guard-task-completion.mjs.ejs +56 -0
  373. package/dist/templates/claude/hooks/hooks.mjs.ejs +127 -0
  374. package/dist/templates/claude/hooks/lib.mjs.ejs +523 -0
  375. package/dist/templates/claude/hooks/post-brainstorm-stop.mjs +48 -0
  376. package/dist/templates/claude/hooks/post-commit-check.mjs.ejs +65 -0
  377. package/dist/templates/claude/hooks/post-commit-checklists/go/backend.ejs +4 -0
  378. package/dist/templates/claude/hooks/post-commit-checklists/go/docs.ejs +4 -0
  379. package/dist/templates/claude/hooks/post-commit-checklists/go/frontend.ejs +4 -0
  380. package/dist/templates/claude/hooks/post-commit-checklists/java/backend.ejs +4 -0
  381. package/dist/templates/claude/hooks/post-commit-checklists/java/docs.ejs +4 -0
  382. package/dist/templates/claude/hooks/post-commit-checklists/java/frontend.ejs +4 -0
  383. package/dist/templates/claude/hooks/post-commit-checklists/python/backend.ejs +4 -0
  384. package/dist/templates/claude/hooks/post-commit-checklists/python/docs.ejs +4 -0
  385. package/dist/templates/claude/hooks/post-commit-checklists/python/frontend.ejs +4 -0
  386. package/dist/templates/claude/hooks/post-commit-checklists/rust/backend.ejs +5 -0
  387. package/dist/templates/claude/hooks/post-commit-checklists/rust/docs.ejs +4 -0
  388. package/dist/templates/claude/hooks/post-commit-checklists/rust/frontend.ejs +4 -0
  389. package/dist/templates/claude/hooks/post-commit-checklists/ts/backend.ejs +4 -0
  390. package/dist/templates/claude/hooks/post-commit-checklists/ts/docs.ejs +4 -0
  391. package/dist/templates/claude/hooks/post-commit-checklists/ts/frontend.ejs +5 -0
  392. package/dist/templates/claude/hooks/post-edit-dispatch.mjs.ejs +73 -0
  393. package/dist/templates/claude/hooks/post-subagent-release.mjs +80 -0
  394. package/dist/templates/claude/hooks/pre-compact.mjs.ejs +79 -0
  395. package/dist/templates/claude/hooks/pre-edit-plan-anchor.mjs.ejs +225 -0
  396. package/dist/templates/claude/hooks/pre-edit-ssot-guard.mjs +204 -0
  397. package/dist/templates/claude/hooks/pre-spawn-worktree-guard.mjs +129 -0
  398. package/dist/templates/claude/hooks/skill-forced-eval.mjs.ejs +281 -0
  399. package/dist/templates/claude/hooks/stop-dangerous.mjs +86 -0
  400. package/dist/templates/claude/hooks/stop-evidence-guard.mjs.ejs +245 -0
  401. package/dist/templates/claude/hooks/stop-finding-loss.mjs.ejs +168 -0
  402. package/dist/templates/claude/hooks/wiki-on-commit.mjs.ejs +38 -0
  403. package/dist/templates/claude/knowledge-map.json.ejs +26 -0
  404. package/dist/templates/claude/rules/05-agent-lifecycle.md +7 -0
  405. package/dist/templates/claude/rules/25-todo-folder-policy.md +5 -0
  406. package/dist/templates/claude/rules/40-context-economy.md +32 -0
  407. package/dist/templates/claude/rules/45-mcp-fallback.md +25 -0
  408. package/dist/templates/claude/rules/50-batch-execution.md +75 -0
  409. package/dist/templates/claude/rules/55-brainstorm-terminal-state.md +44 -0
  410. package/dist/templates/claude/rules/60-incidental-capture.md +43 -0
  411. package/dist/templates/claude/rules/75-impact-vault-reading.md +43 -0
  412. package/dist/templates/claude/rules/90-exec-protocol.md.ejs +40 -0
  413. package/dist/templates/claude/rules/95-closer-mode.md +45 -0
  414. package/dist/templates/claude/settings.json.ejs +165 -0
  415. package/dist/templates/claude/skills/architect-review/SKILL.md.ejs +82 -0
  416. package/dist/templates/claude/skills/brainstorming/SKILL.md.ejs +86 -0
  417. package/dist/templates/claude/skills/clean-code/SKILL.md.ejs +39 -0
  418. package/dist/templates/claude/skills/close-gold-gap/SKILL.md.ejs +58 -0
  419. package/dist/templates/claude/skills/codebase-audit/SKILL.md.ejs +88 -0
  420. package/dist/templates/claude/skills/configure/SKILL.md.ejs +72 -0
  421. package/dist/templates/claude/skills/epic-decompose/SKILL.md.ejs +86 -0
  422. package/dist/templates/claude/skills/gold-audit/SKILL.md.ejs +91 -0
  423. package/dist/templates/claude/skills/impact/SKILL.md.ejs +104 -0
  424. package/dist/templates/claude/skills/levelup/SKILL.md.ejs +146 -0
  425. package/dist/templates/claude/skills/tabletop/SKILL.md.ejs +118 -0
  426. package/dist/templates/claude/skills/tdd/SKILL.md.ejs +157 -0
  427. package/dist/templates/claude/skills/understand-code/SKILL.md.ejs +93 -0
  428. package/dist/templates/claude/skills/verification/SKILL.md.ejs +68 -0
  429. package/dist/templates/claude/skills/wave-drain/SKILL.md.ejs +491 -0
  430. package/dist/templates/codex/CODEX.md.ejs +140 -0
  431. package/dist/templates/codex/codex-adapter.mjs +132 -0
  432. package/dist/templates/codex/config.toml.ejs +189 -0
  433. package/dist/templates/compliance/compliance-mapping.md.ejs +124 -0
  434. package/dist/templates/compliance/compliance-menu.md.ejs +89 -0
  435. package/dist/templates/contract-testing/CONTRACTS_POLICY.md.ejs +30 -0
  436. package/dist/templates/contract-testing/api-snapshots/config-keys-baseline.json.ejs +1 -0
  437. package/dist/templates/contract-testing/api-snapshots/config-response-baseline.json.ejs +1 -0
  438. package/dist/templates/contract-testing/api-snapshots/enum-values-baseline.json.ejs +1 -0
  439. package/dist/templates/contract-testing/api-snapshots/error-shape-baseline.json.ejs +6 -0
  440. package/dist/templates/contract-testing/api-snapshots/openapi-baseline.json.ejs +12 -0
  441. package/dist/templates/contract-testing/api-snapshots/openapi-content-types-baseline.json.ejs +3 -0
  442. package/dist/templates/contract-testing/api-snapshots/openapi-paths-baseline.json.ejs +1 -0
  443. package/dist/templates/contract-testing/api-snapshots/openapi-required-fields-baseline.json.ejs +1 -0
  444. package/dist/templates/contract-testing/api-snapshots/openapi-response-status-baseline.json.ejs +3 -0
  445. package/dist/templates/contract-testing/api-snapshots/test-snapshot.json.ejs +4 -0
  446. package/dist/templates/contract-testing/env/.env.pact.ejs +4 -0
  447. package/dist/templates/contract-testing/graphql/GraphqlSchemaTest.java.ejs +29 -0
  448. package/dist/templates/contract-testing/graphql/graphql-inspector.test.ts.ejs +20 -0
  449. package/dist/templates/contract-testing/graphql/graphql_schema_test.go.ejs +27 -0
  450. package/dist/templates/contract-testing/graphql/graphql_schema_test.rs.ejs +27 -0
  451. package/dist/templates/contract-testing/graphql/test_graphql_schema.py.ejs +21 -0
  452. package/dist/templates/contract-testing/grpc/GrpcContractTest.java.ejs +23 -0
  453. package/dist/templates/contract-testing/grpc/buf-breaking.yml.ejs +6 -0
  454. package/dist/templates/contract-testing/grpc/buf.yaml.ejs +11 -0
  455. package/dist/templates/contract-testing/grpc/grpc-contract.test.ts.ejs +16 -0
  456. package/dist/templates/contract-testing/grpc/grpc_contract_test.go.ejs +22 -0
  457. package/dist/templates/contract-testing/grpc/grpc_contract_test.rs.ejs +22 -0
  458. package/dist/templates/contract-testing/grpc/test_grpc_contract.py.ejs +18 -0
  459. package/dist/templates/contract-testing/message-queue/SchemaRegistryCheckIT.java.ejs +44 -0
  460. package/dist/templates/contract-testing/message-queue/schema-registry-check.ts.ejs +42 -0
  461. package/dist/templates/contract-testing/message-queue/schema_registry_test.go.ejs +71 -0
  462. package/dist/templates/contract-testing/message-queue/schema_registry_test.rs.ejs +61 -0
  463. package/dist/templates/contract-testing/message-queue/test_schema_registry.py.ejs +31 -0
  464. package/dist/templates/contract-testing/pact-samples/assignment-response.json.ejs +7 -0
  465. package/dist/templates/contract-testing/pact-samples/availability-response.json.ejs +13 -0
  466. package/dist/templates/contract-testing/pact-samples/availability-rule-response.json.ejs +8 -0
  467. package/dist/templates/contract-testing/pact-samples/capacity-response.json.ejs +11 -0
  468. package/dist/templates/contract-testing/pact-samples/fully-booked-response.json.ejs +6 -0
  469. package/dist/templates/contract-testing/pact-samples/schedule-override-response.json.ejs +7 -0
  470. package/dist/templates/contract-testing/rest-owned/PactVerificationIT.java.ejs +31 -0
  471. package/dist/templates/contract-testing/rest-owned/pact-consumer.test.ts.ejs +36 -0
  472. package/dist/templates/contract-testing/rest-owned/pact-deps.gradle.ejs +15 -0
  473. package/dist/templates/contract-testing/rest-owned/pact_consumer_test.go.ejs +48 -0
  474. package/dist/templates/contract-testing/rest-owned/pact_consumer_test.rs.ejs +23 -0
  475. package/dist/templates/contract-testing/rest-owned/test_pact_consumer.py.ejs +35 -0
  476. package/dist/templates/contract-testing/rest-public/OpenApiDiffIT.java.ejs +44 -0
  477. package/dist/templates/contract-testing/rest-public/export-openapi-java.gradle.ejs +13 -0
  478. package/dist/templates/contract-testing/rest-public/export-openapi.mjs.ejs +21 -0
  479. package/dist/templates/contract-testing/rest-public/export_openapi.go.ejs +30 -0
  480. package/dist/templates/contract-testing/rest-public/export_openapi.py.ejs +27 -0
  481. package/dist/templates/contract-testing/rest-public/export_openapi.rs.ejs +32 -0
  482. package/dist/templates/contract-testing/rest-public/openapi-diff.ts.ejs +42 -0
  483. package/dist/templates/contract-testing/rest-public/openapi_diff_test.go.ejs +35 -0
  484. package/dist/templates/contract-testing/rest-public/openapi_diff_test.rs.ejs +36 -0
  485. package/dist/templates/contract-testing/rest-public/test_openapi_diff.py.ejs +32 -0
  486. package/dist/templates/coverage/.coveragerc.ejs +26 -0
  487. package/dist/templates/coverage/.tarpaulin.toml.ejs +30 -0
  488. package/dist/templates/coverage/Cargo.toml.profile.release.ejs +24 -0
  489. package/dist/templates/coverage/jacoco-maven-setup.md.ejs +72 -0
  490. package/dist/templates/coverage/jacoco.gradle.ejs +49 -0
  491. package/dist/templates/coverage/kover.gradle.ejs +35 -0
  492. package/dist/templates/coverage/vitest.browser.config.ts.ejs +47 -0
  493. package/dist/templates/coverage/vitest.config.ts.ejs +47 -0
  494. package/dist/templates/docs/CODING_STANDARDS.md.ejs +81 -0
  495. package/dist/templates/docs/FEATURE_MATRIX.md.ejs +84 -0
  496. package/dist/templates/docs/GAP.md.ejs +58 -0
  497. package/dist/templates/docs/ISO27001_ANNEX_A.md.ejs +144 -0
  498. package/dist/templates/docs/MASTER_TEST_PLAN.md.ejs +52 -0
  499. package/dist/templates/docs/POST_MERGE_REVIEW_TEMPLATE.md.ejs +63 -0
  500. package/dist/templates/docs/SECURE_CODING_CHECKLIST.md.ejs +61 -0
  501. package/dist/templates/docs/adr/ADR-000_template.md.ejs +28 -0
  502. package/dist/templates/docs/bugs/bug-analysis.md.ejs +47 -0
  503. package/dist/templates/docs/bugs/bug-report.md.ejs +51 -0
  504. package/dist/templates/docs/bugs/bug-verification.md.ejs +42 -0
  505. package/dist/templates/docs/runbooks/deployment.md.ejs +119 -0
  506. package/dist/templates/docs/runbooks/prod-checklist.md.ejs +90 -0
  507. package/dist/templates/docs/runbooks/rollback.md.ejs +124 -0
  508. package/dist/templates/docs/runbooks/troubleshooting.md.ejs +130 -0
  509. package/dist/templates/docs/skeletons/arc42-canvas.md.ejs +46 -0
  510. package/dist/templates/docs/skeletons/arc42-full.md.ejs +59 -0
  511. package/dist/templates/docs/skeletons/decision-registry.md.ejs +38 -0
  512. package/dist/templates/docs/skeletons/er-model.md.ejs +19 -0
  513. package/dist/templates/docs/skeletons/glossary.md.ejs +8 -0
  514. package/dist/templates/docs/skeletons/governance.md.ejs +13 -0
  515. package/dist/templates/docs/skeletons/slo.md.ejs +21 -0
  516. package/dist/templates/docs/skeletons/technical-debt.md.ejs +8 -0
  517. package/dist/templates/docs/skeletons/test-strategy.md.ejs +14 -0
  518. package/dist/templates/docs/skeletons/threat-model-4q.md.ejs +20 -0
  519. package/dist/templates/docs/skeletons/threat-model-stride.md.ejs +22 -0
  520. package/dist/templates/docs/specs/atomic-task-list.md.ejs +57 -0
  521. package/dist/templates/docs/steering/product.md.ejs +44 -0
  522. package/dist/templates/docs/steering/structure.md.ejs +49 -0
  523. package/dist/templates/docs/steering/tech.md.ejs +53 -0
  524. package/dist/templates/documentation/cli-catalog.md.ejs +21 -0
  525. package/dist/templates/e2e/E2E_CONSTITUTION.md.ejs +77 -0
  526. package/dist/templates/e2e/playwright-python/conftest.py.ejs +22 -0
  527. package/dist/templates/e2e/playwright-python/run_axe.py.ejs +99 -0
  528. package/dist/templates/e2e/playwright-python/test_a11y.py.ejs +38 -0
  529. package/dist/templates/e2e/playwright-python/test_smoke.py.ejs +28 -0
  530. package/dist/templates/e2e/playwright-ts/a11y.spec.ts.ejs +20 -0
  531. package/dist/templates/e2e/playwright-ts/render-smoke.spec.ts.ejs +90 -0
  532. package/dist/templates/e2e/playwright-ts/run-axe.ts.ejs +74 -0
  533. package/dist/templates/e2e/smoke-journeys/journeys.spec.ts.ejs +53 -0
  534. package/dist/templates/evidence-files.json.ejs +13 -0
  535. package/dist/templates/frontend/FE_DESIGN_PRINCIPLES.md.ejs +115 -0
  536. package/dist/templates/frontend/FRONTEND_CONSTITUTION.md.ejs +170 -0
  537. package/dist/templates/frontend/design-tokens.json.ejs +107 -0
  538. package/dist/templates/frontend/vrt-setup.md.ejs +83 -0
  539. package/dist/templates/githooks/commit-msg.ejs +60 -0
  540. package/dist/templates/githooks/pre-commit.ejs +95 -0
  541. package/dist/templates/githooks/pre-push.ejs +251 -0
  542. package/dist/templates/githooks/setup-hooks.sh.ejs +8 -0
  543. package/dist/templates/github/PULL_REQUEST_TEMPLATE.md +37 -0
  544. package/dist/templates/github/PULL_REQUEST_TEMPLATE.md.ejs +52 -0
  545. package/dist/templates/github/actions/build-cache/action.yml.ejs +84 -0
  546. package/dist/templates/github/actions/nas-ssh/action.yml.ejs +53 -0
  547. package/dist/templates/github/actions/setup-java-maven/action.yml.ejs +53 -0
  548. package/dist/templates/github/actions/setup-node-pnpm/action.yml.ejs +39 -0
  549. package/dist/templates/github/actions/sign-and-attest/action.yml.ejs +53 -0
  550. package/dist/templates/github/dependabot.yml.ejs +66 -0
  551. package/dist/templates/github/extended-ci-paths.txt.ejs +38 -0
  552. package/dist/templates/github/issue-templates/bug-report.yml +72 -0
  553. package/dist/templates/github/issue-templates/compliance-item.yml.ejs +75 -0
  554. package/dist/templates/github/issue-templates/config.yml +2 -0
  555. package/dist/templates/github/issue-templates/epic.yml +66 -0
  556. package/dist/templates/github/issue-templates/feature-request.yml +24 -0
  557. package/dist/templates/github/issue-templates/task-brief.yml.ejs +125 -0
  558. package/dist/templates/github/labels.yml.ejs +161 -0
  559. package/dist/templates/github/scripts/sticky-failure-issue.sh.ejs +61 -0
  560. package/dist/templates/github/workflows/01-pr-fast.yml.ejs +1089 -0
  561. package/dist/templates/github/workflows/02-pr-extended.yml.ejs +512 -0
  562. package/dist/templates/github/workflows/03-human-approval.yml.ejs +76 -0
  563. package/dist/templates/github/workflows/04-deploy-test.yml.ejs +425 -0
  564. package/dist/templates/github/workflows/05-release.yml.ejs +813 -0
  565. package/dist/templates/github/workflows/06-nightly-lite.yml.ejs +125 -0
  566. package/dist/templates/github/workflows/06-nightly.yml.ejs +25 -0
  567. package/dist/templates/github/workflows/07-weekly-lite.yml.ejs +221 -0
  568. package/dist/templates/github/workflows/07-weekly.yml.ejs +23 -0
  569. package/dist/templates/github/workflows/08-monthly.yml.ejs +23 -0
  570. package/dist/templates/github/workflows/09-heartbeat.yml.ejs +268 -0
  571. package/dist/templates/github/workflows/10-deploy-prod.yml.ejs +144 -0
  572. package/dist/templates/github/workflows/11-k6-on-demand.yml.ejs +43 -0
  573. package/dist/templates/github/workflows/12-mutation-scheduled.yml.ejs +100 -0
  574. package/dist/templates/github/workflows/13-archunit-extended.yml.ejs +33 -0
  575. package/dist/templates/github/workflows/14-license-scan.yml.ejs +45 -0
  576. package/dist/templates/github/workflows/15-codeql.yml.ejs +117 -0
  577. package/dist/templates/github/workflows/16-frontend-quality.yml.ejs +154 -0
  578. package/dist/templates/github/workflows/17-ossf-scorecard.yml.ejs +46 -0
  579. package/dist/templates/github/workflows/18-frontend-lane.yml.ejs +50 -0
  580. package/dist/templates/github/workflows/_ai-draft-check.yml.ejs +44 -0
  581. package/dist/templates/github/workflows/_contract-postman.yml.ejs +93 -0
  582. package/dist/templates/github/workflows/_cosign-copy/aws-ecs.ejs +90 -0
  583. package/dist/templates/github/workflows/_cosign-copy/azure-container-app.ejs +97 -0
  584. package/dist/templates/github/workflows/_cosign-copy/gcp-cloud-run.ejs +102 -0
  585. package/dist/templates/github/workflows/_cosign-copy/ghcr.ejs +76 -0
  586. package/dist/templates/github/workflows/_cosign-copy/nas-compose.ejs +69 -0
  587. package/dist/templates/github/workflows/_deploy/aws-ecs.ejs +78 -0
  588. package/dist/templates/github/workflows/_deploy/azure-container-app.ejs +59 -0
  589. package/dist/templates/github/workflows/_deploy/gcp-cloud-run.ejs +71 -0
  590. package/dist/templates/github/workflows/_deploy/ghcr.ejs +19 -0
  591. package/dist/templates/github/workflows/_deploy/nas-compose.ejs +45 -0
  592. package/dist/templates/github/workflows/_k6-runner.yml.ejs +107 -0
  593. package/dist/templates/github/workflows/_label-on-approve.yml.ejs +44 -0
  594. package/dist/templates/github/workflows/_label-sync.yml.ejs +58 -0
  595. package/dist/templates/github/workflows/_monthly.yml.ejs +343 -0
  596. package/dist/templates/github/workflows/_nightly.yml.ejs +826 -0
  597. package/dist/templates/github/workflows/_notify.yml.ejs +96 -0
  598. package/dist/templates/github/workflows/_partials/macos-keychain-bypass.ejs +6 -0
  599. package/dist/templates/github/workflows/_partials/macos-keychain-restore.ejs +6 -0
  600. package/dist/templates/github/workflows/_partials/scheduled-heavy-jobs.ejs +117 -0
  601. package/dist/templates/github/workflows/_partials/sigstore-preflight.ejs +8 -0
  602. package/dist/templates/github/workflows/_partials/trivy-fs-java.ejs +27 -0
  603. package/dist/templates/github/workflows/_post-merge-notify.yml.ejs +228 -0
  604. package/dist/templates/github/workflows/_pr-staleness.yml.ejs +85 -0
  605. package/dist/templates/github/workflows/_shared-security.yml.ejs +132 -0
  606. package/dist/templates/github/workflows/_sigstore-retry-sign.yml.ejs +102 -0
  607. package/dist/templates/github/workflows/_weekly.yml.ejs +313 -0
  608. package/dist/templates/github/workflows/drift-shadow.yml.ejs +51 -0
  609. package/dist/templates/github/workflows/five-lane/ci.yml.ejs +54 -0
  610. package/dist/templates/github/workflows/five-lane/nightly.yml.ejs +70 -0
  611. package/dist/templates/github/workflows/five-lane/release.yml.ejs +53 -0
  612. package/dist/templates/github/workflows/five-lane/weekly.yml.ejs +72 -0
  613. package/dist/templates/github/workflows/issue-state.yml.ejs +93 -0
  614. package/dist/templates/global-invariants/GLOBAL_INVARIANTS.md.ejs +34 -0
  615. package/dist/templates/governance/RACI.md.ejs +21 -0
  616. package/dist/templates/governance/ci-mental-model.md.ejs +268 -0
  617. package/dist/templates/governance/contract-integrity-policy.md.ejs +116 -0
  618. package/dist/templates/governance/enterprise-compliance-baseline.md.ejs +111 -0
  619. package/dist/templates/governance/evidence-retention.md.ejs +43 -0
  620. package/dist/templates/governance/gdpr-erasure-runbook.md.ejs +165 -0
  621. package/dist/templates/governance/qa-audit-phases.md.ejs +129 -0
  622. package/dist/templates/governance/risk-assessment-template.md.ejs +105 -0
  623. package/dist/templates/governance/risk-register.md.ejs +76 -0
  624. package/dist/templates/governance/solo-dev-exception.md.ejs +96 -0
  625. package/dist/templates/governance/validation-evidence.md.ejs +131 -0
  626. package/dist/templates/infra/azure/containerapp.tpl.yaml.ejs +74 -0
  627. package/dist/templates/infra/nas-compose/deploy.sh.ejs +114 -0
  628. package/dist/templates/integration-testing/AbstractIntegrationTest.java.ejs +36 -0
  629. package/dist/templates/integration-testing/NoH2ArchTest.java.ejs +24 -0
  630. package/dist/templates/integration-testing/conftest.py.ejs +20 -0
  631. package/dist/templates/integration-testing/db_fixture.rs.ejs +48 -0
  632. package/dist/templates/integration-testing/eslint-no-fake-db.json.ejs +19 -0
  633. package/dist/templates/integration-testing/eslint.config.no-fake-db.mjs.ejs +46 -0
  634. package/dist/templates/integration-testing/main_test.go.ejs +53 -0
  635. package/dist/templates/integration-testing/main_test_sqlite.go.ejs +37 -0
  636. package/dist/templates/integration-testing/test-setup.ts.ejs +26 -0
  637. package/dist/templates/integration-testing/testcontainers-deps.gradle.ejs +9 -0
  638. package/dist/templates/java/DeprecationInterceptor.java.ejs +50 -0
  639. package/dist/templates/java/archunit-hexagonal.ejs +69 -0
  640. package/dist/templates/java/modulith/ApplicationModulesTest.java.ejs +21 -0
  641. package/dist/templates/java/modulith/modulith-deps.gradle.ejs +13 -0
  642. package/dist/templates/java/modulith/modulith-maven-setup.md.ejs +21 -0
  643. package/dist/templates/java/pharma/AuditEvent.java.ejs +138 -0
  644. package/dist/templates/java/pharma/AuditMapper.java.ejs +60 -0
  645. package/dist/templates/java/pharma/archunit-pharma.ejs +128 -0
  646. package/dist/templates/kit/GLOBAL_KIT.md.ejs +11 -0
  647. package/dist/templates/kit/dim.md.ejs +33 -0
  648. package/dist/templates/llms-txt.config.json.ejs +22 -0
  649. package/dist/templates/local-wrapper/.env.example.ejs +15 -0
  650. package/dist/templates/local-wrapper/Makefile.ejs +44 -0
  651. package/dist/templates/local-wrapper/run.sh.ejs +5 -0
  652. package/dist/templates/middleware/410-gone-handler.ts.ejs +41 -0
  653. package/dist/templates/middleware/correlation-id.ts.ejs +32 -0
  654. package/dist/templates/middleware/deprecation.ts.ejs +45 -0
  655. package/dist/templates/middleware/error-handler.ts.ejs +73 -0
  656. package/dist/templates/middleware/payload-size-limit.ts.ejs +51 -0
  657. package/dist/templates/mutation/README.md.ejs +67 -0
  658. package/dist/templates/mutation/cargo-mutants.toml.ejs +9 -0
  659. package/dist/templates/mutation/mutmut-config.toml.ejs +7 -0
  660. package/dist/templates/mutation/parse-mutants.mjs.ejs +44 -0
  661. package/dist/templates/mutation/parse-mutmut.py.ejs +44 -0
  662. package/dist/templates/mutation/pitest-l2-setup.md.ejs +66 -0
  663. package/dist/templates/mutation/pitest-maven-setup.md.ejs +64 -0
  664. package/dist/templates/mutation/pitest.gradle.ejs +23 -0
  665. package/dist/templates/mutation/stryker.conf.json.ejs +17 -0
  666. package/dist/templates/observability/setup.md.ejs +495 -0
  667. package/dist/templates/operations/handbook.md.ejs +217 -0
  668. package/dist/templates/perf/bundle-budget.json.ejs +29 -0
  669. package/dist/templates/perf/k6/reports/csv-report.py.ejs +91 -0
  670. package/dist/templates/perf/k6/reports/html-report.py.ejs +114 -0
  671. package/dist/templates/perf/k6/reports/json-report.py.ejs +74 -0
  672. package/dist/templates/perf/k6/scenarios/breakpoint.js.ejs +26 -0
  673. package/dist/templates/perf/k6/scenarios/burst.js.ejs +43 -0
  674. package/dist/templates/perf/k6/scenarios/endurance.js.ejs +36 -0
  675. package/dist/templates/perf/k6/scenarios/load.js.ejs +26 -0
  676. package/dist/templates/perf/k6/scenarios/ramp-down.js.ejs +29 -0
  677. package/dist/templates/perf/k6/scenarios/ramp-up.js.ejs +32 -0
  678. package/dist/templates/perf/k6/scenarios/smoke.js.ejs +23 -0
  679. package/dist/templates/perf/k6/scenarios/soak.js.ejs +26 -0
  680. package/dist/templates/perf/k6/scenarios/spike.js.ejs +29 -0
  681. package/dist/templates/perf/k6/scenarios/steady-state.js.ejs +31 -0
  682. package/dist/templates/perf/k6/scenarios/stress.js.ejs +29 -0
  683. package/dist/templates/perf/k6/scenarios/volume.js.ejs +36 -0
  684. package/dist/templates/perf/k6/seed/test-data.sql.ejs +59 -0
  685. package/dist/templates/perf/lighthouserc.json.ejs +41 -0
  686. package/dist/templates/quality/iso9001/CAPA_LOG.md.ejs +36 -0
  687. package/dist/templates/quality/iso9001/DOCUMENT_CONTROL.md.ejs +36 -0
  688. package/dist/templates/quality/iso9001/REQUIREMENTS_TRACEABILITY.md.ejs +61 -0
  689. package/dist/templates/regulated/overlay.json.ejs +26 -0
  690. package/dist/templates/regulated/regulated-overlay.md.ejs +40 -0
  691. package/dist/templates/resilience/RESILIENCE.md.ejs +310 -0
  692. package/dist/templates/root/.gitignore.ejs +85 -0
  693. package/dist/templates/root/CODEOWNERS.ejs +18 -0
  694. package/dist/templates/root/CONTRIBUTING.md.ejs +62 -0
  695. package/dist/templates/root/SECURITY.md.ejs +19 -0
  696. package/dist/templates/root/TEST_TAXONOMY.md.ejs +100 -0
  697. package/dist/templates/root/commitlint.config.js.ejs +4 -0
  698. package/dist/templates/root/docs/METHOD/CANONICAL_PATHS.md.ejs +24 -0
  699. package/dist/templates/root/docs/METHOD/ENGINEERING_DEFAULTS.md.ejs +136 -0
  700. package/dist/templates/root/docs/METHOD/KNOWLEDGE_MAP.md.ejs +90 -0
  701. package/dist/templates/root/docs/METHOD/PATTERNS_CATALOG.md.ejs +51 -0
  702. package/dist/templates/root/docs/METHOD/REUSE_REGISTRY.md.ejs +26 -0
  703. package/dist/templates/root/docs/METHOD/REUSE_REGISTRY_SPEC.md.ejs +80 -0
  704. package/dist/templates/root/docs/METHOD/SSOT_CORE_SET.md.ejs +62 -0
  705. package/dist/templates/root/docs/METHOD/TRACK_ROUTER.md.ejs +39 -0
  706. package/dist/templates/root/docs/SYSTEM/CANON.md.ejs +359 -0
  707. package/dist/templates/root/docs/SYSTEM/FAIL_CLOSED.md.ejs +171 -0
  708. package/dist/templates/root/editorconfig.ejs +39 -0
  709. package/dist/templates/root/tsconfig.json.ejs +21 -0
  710. package/dist/templates/rust/clippy.toml.ejs +14 -0
  711. package/dist/templates/schemas/milestone.schema.json.ejs +164 -0
  712. package/dist/templates/schemas/rtm-verdict.schema.json.ejs +52 -0
  713. package/dist/templates/schemas/source-record.schema.json.ejs +91 -0
  714. package/dist/templates/schemas/tabletop-evidence.schema.json.ejs +53 -0
  715. package/dist/templates/schemas/use-case.schema.json.ejs +59 -0
  716. package/dist/templates/scripts/apply-branch-protection.mjs.ejs +70 -0
  717. package/dist/templates/scripts/audit-toolchain.mjs.ejs +112 -0
  718. package/dist/templates/scripts/bg-run.sh.ejs +59 -0
  719. package/dist/templates/scripts/capacity-probe.mjs.ejs +147 -0
  720. package/dist/templates/scripts/capture-debt-baseline.mjs.ejs +99 -0
  721. package/dist/templates/scripts/check-acceptance.mjs.ejs +298 -0
  722. package/dist/templates/scripts/check-action-pins.mjs.ejs +215 -0
  723. package/dist/templates/scripts/check-agent-return.mjs.ejs +177 -0
  724. package/dist/templates/scripts/check-all.mjs.ejs +1746 -0
  725. package/dist/templates/scripts/check-anti-fake-green.mjs.ejs +80 -0
  726. package/dist/templates/scripts/check-anti-proforma.mjs.ejs +547 -0
  727. package/dist/templates/scripts/check-api-e2e.mjs.ejs +106 -0
  728. package/dist/templates/scripts/check-arc42-slots.mjs.ejs +25 -0
  729. package/dist/templates/scripts/check-assertion-delta.mjs.ejs +287 -0
  730. package/dist/templates/scripts/check-audit-dry-pass.mjs.ejs +176 -0
  731. package/dist/templates/scripts/check-bundle-size.mjs.ejs +151 -0
  732. package/dist/templates/scripts/check-canonical-paths.mjs.ejs +39 -0
  733. package/dist/templates/scripts/check-ci-tiers.mjs.ejs +111 -0
  734. package/dist/templates/scripts/check-claude-md-lint.mjs.ejs +142 -0
  735. package/dist/templates/scripts/check-collab-mode-wired.mjs.ejs +60 -0
  736. package/dist/templates/scripts/check-commit-footer-rationale.mjs.ejs +170 -0
  737. package/dist/templates/scripts/check-constraint-scan.mjs.ejs +681 -0
  738. package/dist/templates/scripts/check-consumer-audit.mjs.ejs +414 -0
  739. package/dist/templates/scripts/check-continue-on-error.mjs.ejs +227 -0
  740. package/dist/templates/scripts/check-cross-model-review.mjs.ejs +458 -0
  741. package/dist/templates/scripts/check-decision-registry.mjs.ejs +101 -0
  742. package/dist/templates/scripts/check-doc-freshness.mjs.ejs +17 -0
  743. package/dist/templates/scripts/check-doc-links.mjs.ejs +98 -0
  744. package/dist/templates/scripts/check-doc-set.mjs.ejs +18 -0
  745. package/dist/templates/scripts/check-docs.mjs.ejs +71 -0
  746. package/dist/templates/scripts/check-domain-api-surface.mjs.ejs +80 -0
  747. package/dist/templates/scripts/check-drift.mjs.ejs +167 -0
  748. package/dist/templates/scripts/check-duplication.mjs.ejs +45 -0
  749. package/dist/templates/scripts/check-e2e-escalation.mjs.ejs +199 -0
  750. package/dist/templates/scripts/check-e2e-quarantine.mjs.ejs +68 -0
  751. package/dist/templates/scripts/check-emission-parity.mjs.ejs +91 -0
  752. package/dist/templates/scripts/check-exit-code-contract.mjs.ejs +138 -0
  753. package/dist/templates/scripts/check-fail-closed-audit.mjs.ejs +389 -0
  754. package/dist/templates/scripts/check-fe-boundaries.mjs.ejs +184 -0
  755. package/dist/templates/scripts/check-feature-matrix.mjs.ejs +835 -0
  756. package/dist/templates/scripts/check-frontend-lane.mjs.ejs +73 -0
  757. package/dist/templates/scripts/check-grace-window.mjs.ejs +102 -0
  758. package/dist/templates/scripts/check-handoff-doc.mjs.ejs +192 -0
  759. package/dist/templates/scripts/check-hook-routing.mjs.ejs +224 -0
  760. package/dist/templates/scripts/check-image-pins.mjs.ejs +90 -0
  761. package/dist/templates/scripts/check-inline-suppressions.mjs.ejs +153 -0
  762. package/dist/templates/scripts/check-iso9001.mjs.ejs +146 -0
  763. package/dist/templates/scripts/check-knowledge-map.mjs.ejs +49 -0
  764. package/dist/templates/scripts/check-local-ci-parity.mjs.ejs +382 -0
  765. package/dist/templates/scripts/check-m16-handoff.mjs.ejs +112 -0
  766. package/dist/templates/scripts/check-merge-method.mjs.ejs +92 -0
  767. package/dist/templates/scripts/check-milestones.mjs.ejs +678 -0
  768. package/dist/templates/scripts/check-min-test-execution.mjs.ejs +225 -0
  769. package/dist/templates/scripts/check-muted-test.mjs.ejs +270 -0
  770. package/dist/templates/scripts/check-no-stub-redirects.mjs.ejs +154 -0
  771. package/dist/templates/scripts/check-no-tracked-artifacts.mjs.ejs +134 -0
  772. package/dist/templates/scripts/check-oracle-discrimination.mjs.ejs +244 -0
  773. package/dist/templates/scripts/check-pii-scan.mjs.ejs +67 -0
  774. package/dist/templates/scripts/check-pipe-tee-hazard.mjs.ejs +85 -0
  775. package/dist/templates/scripts/check-pr-size-gate.mjs.ejs +85 -0
  776. package/dist/templates/scripts/check-refutation-verdicts.mjs.ejs +256 -0
  777. package/dist/templates/scripts/check-regulated-overlay.mjs.ejs +158 -0
  778. package/dist/templates/scripts/check-render-smoke.mjs.ejs +100 -0
  779. package/dist/templates/scripts/check-reuse-registry.mjs.ejs +44 -0
  780. package/dist/templates/scripts/check-review-completion.mjs.ejs +708 -0
  781. package/dist/templates/scripts/check-safety-adopt-ratchet.mjs.ejs +142 -0
  782. package/dist/templates/scripts/check-secret-presence.mjs.ejs +191 -0
  783. package/dist/templates/scripts/check-secret-scan.mjs.ejs +82 -0
  784. package/dist/templates/scripts/check-skip-critical-e2e.mjs.ejs +142 -0
  785. package/dist/templates/scripts/check-smoke-journeys.mjs.ejs +272 -0
  786. package/dist/templates/scripts/check-solo-reactivation.mjs.ejs +102 -0
  787. package/dist/templates/scripts/check-sources.mjs.ejs +245 -0
  788. package/dist/templates/scripts/check-ssot-core.mjs.ejs +45 -0
  789. package/dist/templates/scripts/check-stack-conformity.mjs.ejs +160 -0
  790. package/dist/templates/scripts/check-stride-traceability.mjs.ejs +101 -0
  791. package/dist/templates/scripts/check-suppression-expiry.mjs.ejs +79 -0
  792. package/dist/templates/scripts/check-suppression-rationale.mjs.ejs +76 -0
  793. package/dist/templates/scripts/check-suppressions.mjs.ejs +150 -0
  794. package/dist/templates/scripts/check-tabletop-evidence.mjs.ejs +499 -0
  795. package/dist/templates/scripts/check-tdd-evidence.mjs.ejs +381 -0
  796. package/dist/templates/scripts/check-test-naming.mjs.ejs +151 -0
  797. package/dist/templates/scripts/check-test-pyramid.mjs.ejs +170 -0
  798. package/dist/templates/scripts/check-test-scope-tier.mjs.ejs +208 -0
  799. package/dist/templates/scripts/check-tier-coverage.mjs.ejs +71 -0
  800. package/dist/templates/scripts/check-todo-max-age.mjs.ejs +205 -0
  801. package/dist/templates/scripts/check-touched-vs-manifest.mjs.ejs +179 -0
  802. package/dist/templates/scripts/check-unwired-guards.mjs.ejs +367 -0
  803. package/dist/templates/scripts/check-use-cases.mjs.ejs +345 -0
  804. package/dist/templates/scripts/check-validator-helptext.mjs.ejs +80 -0
  805. package/dist/templates/scripts/check-wiki-lint.mjs.ejs +162 -0
  806. package/dist/templates/scripts/check-workflow-docs-sync.mjs.ejs +91 -0
  807. package/dist/templates/scripts/check-workflow-job-naming.mjs.ejs +102 -0
  808. package/dist/templates/scripts/check-workflow-perms.mjs.ejs +54 -0
  809. package/dist/templates/scripts/check-workflow-runners.mjs.ejs +87 -0
  810. package/dist/templates/scripts/check-workflow-sha-pinning.mjs.ejs +89 -0
  811. package/dist/templates/scripts/check-workflow-test-integrity.mjs.ejs +235 -0
  812. package/dist/templates/scripts/checks/check-rust-no-unsafe.mjs.ejs +58 -0
  813. package/dist/templates/scripts/checks/check-rust-no-unwrap.mjs.ejs +61 -0
  814. package/dist/templates/scripts/ci-classify-changes.mjs.ejs +119 -0
  815. package/dist/templates/scripts/conformance.mjs.ejs +37 -0
  816. package/dist/templates/scripts/constraint-map.json.ejs +25 -0
  817. package/dist/templates/scripts/debt-lib.mjs.ejs +1015 -0
  818. package/dist/templates/scripts/debt-report.mjs.ejs +199 -0
  819. package/dist/templates/scripts/domain-api-surface.json.ejs +23 -0
  820. package/dist/templates/scripts/done-evidence.mjs.ejs +359 -0
  821. package/dist/templates/scripts/evidence-collect.mjs.ejs +264 -0
  822. package/dist/templates/scripts/evidence-prune.mjs.ejs +96 -0
  823. package/dist/templates/scripts/evidence-rotate.mjs.ejs +53 -0
  824. package/dist/templates/scripts/evidence-writer.sh.ejs +220 -0
  825. package/dist/templates/scripts/gate-registry.yml.ejs +286 -0
  826. package/dist/templates/scripts/gen-doc-index.mjs.ejs +19 -0
  827. package/dist/templates/scripts/gen-gap.mjs.ejs +153 -0
  828. package/dist/templates/scripts/gen-llms-txt.mjs.ejs +18 -0
  829. package/dist/templates/scripts/gen-wiki.mjs.ejs +196 -0
  830. package/dist/templates/scripts/gh-cleanup-expired-artifacts.mjs.ejs +134 -0
  831. package/dist/templates/scripts/gold-audit.mjs.ejs +19 -0
  832. package/dist/templates/scripts/i18n-literal-scanner.mjs.ejs +175 -0
  833. package/dist/templates/scripts/ingest-zap-report.mjs.ejs +107 -0
  834. package/dist/templates/scripts/inject-pact-samples.sh.ejs +96 -0
  835. package/dist/templates/scripts/issue-readiness.mjs.ejs +84 -0
  836. package/dist/templates/scripts/knowledge-map-update.mjs.ejs +32 -0
  837. package/dist/templates/scripts/lib/acceptance-criteria.mjs.ejs +213 -0
  838. package/dist/templates/scripts/lib/agent-return-validate.mjs.ejs +481 -0
  839. package/dist/templates/scripts/lib/coverage-gate.mjs.ejs +54 -0
  840. package/dist/templates/scripts/lib/e2e-reliability.mjs.ejs +207 -0
  841. package/dist/templates/scripts/lib/ephemeral-server.mjs.ejs +148 -0
  842. package/dist/templates/scripts/lib/evidence-binding.mjs.ejs +140 -0
  843. package/dist/templates/scripts/lib/exact-sha-policy.mjs.ejs +233 -0
  844. package/dist/templates/scripts/lib/gate-args.mjs.ejs +20 -0
  845. package/dist/templates/scripts/lib/gate-evidence.mjs.ejs +622 -0
  846. package/dist/templates/scripts/lib/gate-mutex.mjs.ejs +340 -0
  847. package/dist/templates/scripts/lib/glob-walk.mjs.ejs +148 -0
  848. package/dist/templates/scripts/lib/run-helpers.mjs.ejs +416 -0
  849. package/dist/templates/scripts/lib/seed-common.sh.ejs +105 -0
  850. package/dist/templates/scripts/lib/waiter-count.mjs.ejs +62 -0
  851. package/dist/templates/scripts/muted-tests-baseline.json.ejs +4 -0
  852. package/dist/templates/scripts/optional-emissions.json.ejs +117 -0
  853. package/dist/templates/scripts/oracle-discrimination-baseline.json.ejs +6 -0
  854. package/dist/templates/scripts/pid-watch.sh.ejs +78 -0
  855. package/dist/templates/scripts/pii-scan.mjs.ejs +125 -0
  856. package/dist/templates/scripts/pr-merge-watch.mjs.ejs +445 -0
  857. package/dist/templates/scripts/record-agent-return.mjs.ejs +279 -0
  858. package/dist/templates/scripts/record-journey-evidence.mjs.ejs +69 -0
  859. package/dist/templates/scripts/rework-log.mjs.ejs +172 -0
  860. package/dist/templates/scripts/run-postman-tests.sh.ejs +112 -0
  861. package/dist/templates/scripts/schemas/agent-return-external.schema.json +74 -0
  862. package/dist/templates/scripts/schemas/agent-return.schema.json.ejs +181 -0
  863. package/dist/templates/scripts/schemas/cross-model-dispatch.schema.json +95 -0
  864. package/dist/templates/scripts/seed-test-data.sh.ejs +99 -0
  865. package/dist/templates/scripts/self-validation.mjs.ejs +126 -0
  866. package/dist/templates/scripts/setup-repo.sh.ejs +78 -0
  867. package/dist/templates/scripts/spotbugs-baseline.json.ejs +4 -0
  868. package/dist/templates/scripts/test-gate-layering.mjs.ejs +92 -0
  869. package/dist/templates/scripts/validate-api-snapshots.mjs.ejs +86 -0
  870. package/dist/templates/scripts/validate-k6-scenarios.mjs.ejs +96 -0
  871. package/dist/templates/scripts/validate-openapi-field-types.mjs.ejs +124 -0
  872. package/dist/templates/scripts/validate-postman-collection.mjs.ejs +159 -0
  873. package/dist/templates/scripts/verify-fe-coverage.mjs.ejs +138 -0
  874. package/dist/templates/scripts/verify-i18n-parity.mjs.ejs +114 -0
  875. package/dist/templates/scripts/verify-module-coverage.mjs.ejs +207 -0
  876. package/dist/templates/scripts/verify-mutation-baseline.mjs.ejs +209 -0
  877. package/dist/templates/scripts/verify-spotbugs.mjs.ejs +216 -0
  878. package/dist/templates/scripts/verify-tokens.mjs.ejs +177 -0
  879. package/dist/templates/security/RISK_ASSESSMENT.md.ejs +35 -0
  880. package/dist/templates/security/STRIDE.md.ejs +24 -0
  881. package/dist/templates/security/gitleaks.toml.ejs +21 -0
  882. package/dist/templates/security/zap/baseline-auth.context.ejs +58 -0
  883. package/dist/templates/security/zap/rules.tsv.ejs +100 -0
  884. package/dist/templates/ship/TICK_PROMPT.md.ejs +55 -0
  885. package/dist/templates/ship/supervisor.sh.ejs +46 -0
  886. package/dist/templates/smoke-journeys/manifest.json.ejs +1 -0
  887. package/dist/templates/sonar-project.properties.ejs +25 -0
  888. package/dist/templates/standards/doc-profile.ejs +27 -0
  889. package/dist/templates/standards/gold-doc-set.yml.ejs +528 -0
  890. package/dist/templates/standards/gold-registry.java.yml.ejs +266 -0
  891. package/dist/templates/standards/gold-registry.typescript.yml.ejs +104 -0
  892. package/dist/templates/standards/gold-registry.yml.ejs +119 -0
  893. package/dist/templates/standards/thresholds.yml.ejs +86 -0
  894. package/dist/templates/static-analysis/.dependency-cruiser.cjs.ejs +78 -0
  895. package/dist/templates/static-analysis/.golangci.yml.ejs +70 -0
  896. package/dist/templates/static-analysis/.shellcheckrc.ejs +13 -0
  897. package/dist/templates/static-analysis/.yamllint.yml.ejs +41 -0
  898. package/dist/templates/static-analysis/checkstyle.xml.ejs +83 -0
  899. package/dist/templates/static-analysis/detekt.yml.ejs +26 -0
  900. package/dist/templates/static-analysis/eslint.config.mjs.ejs +54 -0
  901. package/dist/templates/static-analysis/eslint.config.static.mjs.ejs +38 -0
  902. package/dist/templates/static-analysis/eslintrc-static.json.ejs +30 -0
  903. package/dist/templates/static-analysis/jscpd.json.ejs +25 -0
  904. package/dist/templates/static-analysis/knip.json.ejs +26 -0
  905. package/dist/templates/static-analysis/pmd-ruleset.xml.ejs +92 -0
  906. package/dist/templates/static-analysis/prettierignore.ejs +59 -0
  907. package/dist/templates/static-analysis/prettierrc.json.ejs +10 -0
  908. package/dist/templates/static-analysis/requirements-dev.txt.ejs +11 -0
  909. package/dist/templates/static-analysis/ruff.toml.ejs +40 -0
  910. package/dist/templates/static-analysis/rustfmt.toml.ejs +3 -0
  911. package/dist/templates/static-analysis/spotbugs-exclude.xml.ejs +120 -0
  912. package/dist/templates/static-analysis/spotbugs.gradle.ejs +18 -0
  913. package/dist/templates/static-analysis/spotless.gradle.ejs +38 -0
  914. package/dist/templates/static-analysis/stylelintrc.json.ejs +16 -0
  915. package/dist/templates/suppressions/archunit-baseline.json.ejs +1 -0
  916. package/dist/templates/suppressions/consumer-audit-allowlist.json.ejs +1 -0
  917. package/dist/templates/suppressions/gitleaksignore.ejs +20 -0
  918. package/dist/templates/suppressions/pii-allowlist.json.ejs +1 -0
  919. package/dist/templates/suppressions/suppressions-schema.json.ejs +39 -0
  920. package/dist/templates/suppressions/trivyignore.ejs +17 -0
  921. package/dist/templates/testing/test-taxonomy.md.ejs +120 -0
  922. package/dist/test/example.behavioral.test.js +2 -0
  923. package/dist/types/memory.d.ts +19 -0
  924. package/dist/types/memory.js +1 -0
  925. package/dist/types/plan.d.ts +102 -0
  926. package/dist/types/plan.js +2 -0
  927. package/dist/types/plugin.d.ts +45 -0
  928. package/dist/types/plugin.js +1 -0
  929. package/dist/utils/canon-loader.js +2 -0
  930. package/dist/utils/channel.js +1 -0
  931. package/dist/utils/config.d.ts +79 -0
  932. package/dist/utils/config.js +5 -0
  933. package/dist/utils/confirm-downgrade.js +5 -0
  934. package/dist/utils/env.js +2 -0
  935. package/dist/utils/error-catalog.js +2 -0
  936. package/dist/utils/errors.js +2 -0
  937. package/dist/utils/evidence-log.js +3 -0
  938. package/dist/utils/file-lock.js +2 -0
  939. package/dist/utils/first-run.js +8 -0
  940. package/dist/utils/fs.js +3 -0
  941. package/dist/utils/github-issue-helper.js +4 -0
  942. package/dist/utils/gradle.js +21 -0
  943. package/dist/utils/json-output.js +2 -0
  944. package/dist/utils/logger-config.js +4 -0
  945. package/dist/utils/logger.js +3 -0
  946. package/dist/utils/maturity-check.js +2 -0
  947. package/dist/utils/pkg.js +3 -0
  948. package/dist/utils/platform.js +2 -0
  949. package/dist/utils/plugin-loader.js +2 -0
  950. package/dist/utils/plugin-worker.js +2 -0
  951. package/dist/utils/prettier-format.js +2 -0
  952. package/dist/utils/profiler.js +2 -0
  953. package/dist/utils/render.js +2 -0
  954. package/dist/utils/replay.js +3 -0
  955. package/dist/utils/run-cli.js +6 -0
  956. package/dist/utils/run-id.js +3 -0
  957. package/dist/utils/safe-read.js +2 -0
  958. package/dist/utils/task-id.js +2 -0
  959. package/dist/utils/today.js +2 -0
  960. package/dist/utils/tty.js +2 -0
  961. package/dist/utils/walk-dir.js +2 -0
  962. package/dist/verify/rules/drive-by-scope.js +1 -0
  963. package/dist/verify/rules/orphan-todos.js +1 -0
  964. package/dist/verify/rules/registry.js +1 -0
  965. package/dist/verify/rules/skip-patterns.js +1 -0
  966. package/dist/verify/rules/types.d.ts +15 -0
  967. package/dist/verify/rules/types.js +1 -0
  968. package/dist/verify/rules/ui-language.js +1 -0
  969. package/dist/verify/run.js +3 -0
  970. package/dist/wizard/archetype-defaults.js +1 -0
  971. package/dist/wizard/presets.js +1 -0
  972. package/dist/wizard/prompts.js +44 -0
  973. package/dist/wizard/types.d.ts +637 -0
  974. package/dist/wizard/types.js +1 -0
  975. package/dist/worktree/harvest.js +2 -0
  976. package/dist/worktree/links.js +2 -0
  977. package/dist/worktree/paths.js +2 -0
  978. package/dist/worktree/validate.js +3 -0
  979. package/package.json +124 -0
  980. package/scripts/check-arc42-slots.mjs +738 -0
  981. package/scripts/check-doc-freshness.mjs +236 -0
  982. package/scripts/check-doc-set.mjs +243 -0
  983. package/scripts/check-doc-style.mjs +228 -0
  984. package/scripts/gold-audit.mjs +319 -0
  985. package/scripts/lib/doc-gate-allowlist.mjs +123 -0
  986. package/scripts/lib/doc-set-resolve.mjs +274 -0
  987. package/scripts/lib/glob-walk.mjs +148 -0
  988. package/scripts/lib/gold-audit-lib.mjs +1260 -0
  989. package/scripts/lib/run-helpers.mjs +413 -0
@@ -0,0 +1,1260 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // gold-audit-lib.mjs — deterministic gold-audit evaluator (#1373).
3
+ //
4
+ // Reimplements the upstream reference gold-audit registry evaluator natively in node (zero
5
+ // Python dependency). The score is computed by code, never by an AI. Pure functions only — no
6
+ // process.exit, no argv, no console. The thin CLI (scripts/gold-audit.mjs) and the report
7
+ // consumer (scripts/gold-report.mjs) both call evaluate() so there is one code path.
8
+ //
9
+ // Verdicts (per check):
10
+ // Y — verified true by code, with evidence (file [+ line])
11
+ // P — partial (a count_matches check met some but not all of its target, or a
12
+ // version_consistency check found real-but-divergent substance)
13
+ // N — verified false by code, with evidence (the path/pattern that was absent)
14
+ // NA — not applicable (applies_if overlay disabled)
15
+ // NV — not verified by code (manual / attestation-required check)
16
+ //
17
+ // Determinism contract: identical repo + identical registry ⇒ identical evaluate() output.
18
+ // Checks are evaluated in stable id order; no wall-clock value enters the scored payload.
19
+
20
+ import { existsSync, readFileSync, statSync, lstatSync } from 'node:fs'
21
+ import { resolve, relative, isAbsolute } from 'node:path'
22
+ import { globToRegExp, validateGlob, walkRepo } from './glob-walk.mjs'
23
+
24
+ /**
25
+ * Resolve a registry-declared path inside root; reject traversal + null bytes. Parity contract:
26
+ * behaves byte-identically to src/conformance/shared.ts safeResolve for string inputs (reject only
27
+ * `\0` + post-resolve `..` escape — a literal filename containing '..' that stays in-root is
28
+ * allowed, matching the TS engine). The leading non-string guard is .mjs-only safety (the TS engine
29
+ * defaults a missing args.path to ''); for any real path both engines agree.
30
+ */
31
+ function safeResolve(root, p) {
32
+ if (typeof p !== 'string' || p.includes('\0')) return null
33
+ const abs = resolve(root, p)
34
+ const rel = relative(root, abs)
35
+ if (rel.startsWith('..') || isAbsolute(rel)) return null
36
+ return abs
37
+ }
38
+
39
+ /** Read a file's text, or null if missing/unreadable. */
40
+ function readText(abs) {
41
+ try {
42
+ return readFileSync(abs, 'utf-8')
43
+ } catch {
44
+ return null
45
+ }
46
+ }
47
+
48
+ // ── #1525: ReDoS + unbounded-read hardening for registry-supplied regexes ────────────────────────
49
+ //
50
+ // The gold-registry is PROJECT-AUTHORED data and, in a consumer that wires this audit into CI and
51
+ // accepts fork PRs, an attacker controls both the registry pattern AND the scanned file contents.
52
+ // Two DETERMINISTIC layers (fixed input ⇒ fixed verdict ⇒ engine-parity), byte-identical to
53
+ // src/conformance/shared.ts: (1) readScanText caps the bytes a registry regex runs over; (2)
54
+ // hasNestedUnboundedQuantifier rejects the catastrophic-backtracking family at compile. A bare
55
+ // try/catch around new RegExp() only catches invalid-syntax — it never stops (a+)+$ from hanging.
56
+
57
+ /** Byte cap for any file a registry-supplied regex is run over (ReDoS input bound, ~2 MB). */
58
+ const MAX_SCAN_BYTES = 2_000_000
59
+
60
+ /**
61
+ * Read a file for regex scanning, capped at MAX_SCAN_BYTES. Returns { ok:true, text } or
62
+ * { ok:false, reason:'unreadable'|'oversize' }; an over-cap file is NEVER read (fails closed).
63
+ */
64
+ function readScanText(abs) {
65
+ let size
66
+ try {
67
+ size = statSync(abs).size
68
+ } catch {
69
+ return { ok: false, reason: 'unreadable' }
70
+ }
71
+ if (size > MAX_SCAN_BYTES) return { ok: false, reason: 'oversize' }
72
+ const text = readText(abs)
73
+ return text === null ? { ok: false, reason: 'unreadable' } : { ok: true, text }
74
+ }
75
+
76
+ /** Advance past a `[...]` char class starting at `i` (on '['). Returns the index just after ']'. */
77
+ function skipCharClass(src, i) {
78
+ let j = i + 1
79
+ if (src[j] === '^') j++
80
+ if (src[j] === ']') j++ // a ']' immediately after '[' / '[^' is a literal class member
81
+ while (j < src.length && src[j] !== ']') {
82
+ if (src[j] === '\\') j++ // an escaped char inside the class spans two positions
83
+ j++
84
+ }
85
+ return j + 1
86
+ }
87
+
88
+ /** Parse a `{...}` quantifier at `i` (on '{'). isQuant=false ⇒ '{' is a literal, not a quantifier. */
89
+ function braceQuantifier(src, i) {
90
+ const close = src.indexOf('}', i)
91
+ if (close < 0) return { isQuant: false }
92
+ const body = src.slice(i + 1, close)
93
+ // JS quantifier syntax: {n}, {n,}, {n,m}. {,m} is NOT a quantifier (treated as a literal).
94
+ if (!/^\d+(,\d*)?$/.test(body)) return { isQuant: false }
95
+ return { isQuant: true, unbounded: /^\d+,$/.test(body), next: close + 1 }
96
+ }
97
+
98
+ /**
99
+ * True when `source` applies an UNBOUNDED quantifier (`*`, `+`, `{n,}`) to a group whose body itself
100
+ * contains an unbounded quantifier — the nested-quantifier / star-height≥2 family (e.g. `(a+)+`,
101
+ * `(.*)+`, `((a+))+`) whose backtracking grows exponentially with input length. One linear,
102
+ * dependency-free, DETERMINISTIC scan, byte-identical to src/conformance/shared.ts. Deliberately
103
+ * narrow defense-in-depth (paired with MAX_SCAN_BYTES): not every ReDoS family is modelled.
104
+ */
105
+ function hasNestedUnboundedQuantifier(source) {
106
+ const groupSawUnbounded = [false]
107
+ let prevGroupSawUnbounded = false
108
+ let i = 0
109
+ const n = source.length
110
+ while (i < n) {
111
+ const ch = source[i]
112
+ if (ch === '\\') {
113
+ i += 2
114
+ prevGroupSawUnbounded = false
115
+ continue
116
+ }
117
+ if (ch === '[') {
118
+ i = skipCharClass(source, i)
119
+ prevGroupSawUnbounded = false
120
+ continue
121
+ }
122
+ if (ch === '(') {
123
+ groupSawUnbounded.push(false)
124
+ i++
125
+ prevGroupSawUnbounded = false
126
+ continue
127
+ }
128
+ if (ch === ')') {
129
+ const closed = groupSawUnbounded.length > 1 ? (groupSawUnbounded.pop() ?? false) : false
130
+ if (closed) groupSawUnbounded[groupSawUnbounded.length - 1] = true
131
+ prevGroupSawUnbounded = closed
132
+ i++
133
+ continue
134
+ }
135
+ let unbounded = false
136
+ let next = i + 1
137
+ if (ch === '*' || ch === '+') {
138
+ unbounded = true
139
+ } else if (ch === '{') {
140
+ const q = braceQuantifier(source, i)
141
+ if (q.isQuant) {
142
+ unbounded = q.unbounded
143
+ next = q.next
144
+ }
145
+ }
146
+ if (unbounded) {
147
+ if (prevGroupSawUnbounded) return true
148
+ groupSawUnbounded[groupSawUnbounded.length - 1] = true
149
+ prevGroupSawUnbounded = false
150
+ i = next
151
+ continue
152
+ }
153
+ prevGroupSawUnbounded = false
154
+ i = next
155
+ }
156
+ return false
157
+ }
158
+
159
+ /** 1-based line number of the first occurrence of `needle` in `text`, or null. */
160
+ function lineOf(text, needle) {
161
+ const idx = text.indexOf(needle)
162
+ if (idx < 0) return null
163
+ let line = 1
164
+ for (let i = 0; i < idx; i++) if (text[i] === '\n') line++
165
+ return line
166
+ }
167
+
168
+ /** 1-based line number of the character at `idx` in `text`. */
169
+ function lineAtIndex(text, idx) {
170
+ let line = 1
171
+ for (let i = 0; i < idx && i < text.length; i++) if (text[i] === '\n') line++
172
+ return line
173
+ }
174
+
175
+ // ── Constrained deterministic glob (#1470) ──────────────────────────────────────
176
+ //
177
+ // Glob support reuses scripts/lib/glob-walk.mjs (globToRegExp/validateGlob/walkRepo: POSIX paths,
178
+ // prunes SKIP_DIRS) — one canonical matcher, never a hand-written copy (CANON-16). The TS engine
179
+ // mirrors this via src/conformance/shared.ts expandGlob (engine-parity gate). Both sort with a plain
180
+ // `.sort()` (code-unit), NEVER localeCompare, so the file order is byte-identical across engines.
181
+
182
+ /**
183
+ * Expand a repo-rooted glob to a SORTED array of repo-relative POSIX paths, or null when the glob
184
+ * is invalid (absolute / `..`-traversal / non-string / empty). An empty array is a valid "matched
185
+ * nothing". The glob's RegExp is compiled ONCE (via globToRegExp) and `.test()`d per file, not
186
+ * recompiled per file (#1522/#1600).
187
+ *
188
+ * `files` is an OPTIONAL pre-walked repo file list (#1600): when a single evaluate() resolves several
189
+ * globs it walks the tree once and threads the result here, so K glob checks share ONE tree-walk
190
+ * instead of K. Absent ⇒ this walks the tree itself (the standalone-call contract is unchanged).
191
+ * Output-invariant ⇒ engine-parity stays byte-identical; mirrors src/conformance/shared.ts expandGlob.
192
+ */
193
+ function expandGlob(root, pattern, files) {
194
+ if (typeof pattern !== 'string' || pattern === '' || !validateGlob(pattern)) return null
195
+ const re = globToRegExp(pattern)
196
+ return (files ?? walkRepo(root)).filter((f) => re.test(f)).sort()
197
+ }
198
+
199
+ /**
200
+ * Deterministic, locale-independent string order (UTF-16 code units) — byte-identical across the
201
+ * .mjs and TS engines and immune to Node/ICU collation drift (#1471). Used for every check /
202
+ * dimension sort so the scored payload's ordering can never silently diverge between engines.
203
+ */
204
+ function cmpCodeUnit(a, b) {
205
+ return a < b ? -1 : a > b ? 1 : 0
206
+ }
207
+
208
+ /**
209
+ * Resolve a glob-check's `args.glob` to a SORTED match list. Returns { ok:true, glob, matched } on
210
+ * success, or { ok:false, result } (a verified-N) when the glob is invalid/empty — shared by every
211
+ * glob-based check so the invalid-glob guard exists once (mirrored in src/conformance/engine.ts).
212
+ * `getFiles` threads the per-evaluate memoized walk (#1600) so glob checks share one tree-walk; an
213
+ * empty glob short-circuits to null WITHOUT triggering the walk (K=0 ⇒ 0 walks).
214
+ */
215
+ function resolveGlobArg(args, root, getFiles) {
216
+ const glob = typeof args.glob === 'string' ? args.glob : ''
217
+ const matched = glob === '' ? null : expandGlob(root, glob, getFiles())
218
+ if (matched === null) {
219
+ return {
220
+ ok: false,
221
+ result: { verdict: 'N', evidence: { file: glob, detail: 'invalid or empty glob' } },
222
+ }
223
+ }
224
+ return { ok: true, glob, matched }
225
+ }
226
+
227
+ /**
228
+ * Whether git tracks the executable bit in this repo (core.fileMode). Reads `<root>/.git/config`
229
+ * deterministically (no spawn — INV-12); only an explicit `filemode = false` disables it. A missing
230
+ * or unreadable config (e.g. a worktree pointer file, or a non-git fixture) ⇒ treated as enabled, so
231
+ * the exec-bit check still runs. Mirrored byte-for-byte in src/conformance/engine.ts.
232
+ */
233
+ function gitFileModeEnabled(root) {
234
+ const cfg = readText(resolve(root, '.git/config'))
235
+ if (cfg === null) return true
236
+ let inCore = false
237
+ let lastValue = null
238
+ for (const raw of cfg.split('\n')) {
239
+ const line = raw.trim()
240
+ if (line.startsWith('[')) {
241
+ // Only the exact top-level [core] section — a `[core "subsection"]` is NOT [core].filemode.
242
+ inCore = /^\[core\]/i.test(line)
243
+ continue
244
+ }
245
+ if (!inCore) continue
246
+ // git honors the LAST value when a key is declared more than once — keep scanning, don't return.
247
+ const m = /^filemode\s*=\s*(\S+)/i.exec(line)
248
+ if (m !== null) lastValue = m[1].toLowerCase()
249
+ }
250
+ return lastValue === null ? true : lastValue !== 'false'
251
+ }
252
+
253
+ // ── #1413: deterministic report metric extraction (json/xml/regex) ──────────────
254
+ //
255
+ // A `value` check with an `args.format` reads a PRE-GENERATED tool report deterministically (no live
256
+ // spawn: that would break the determinism + parity + fail-closed contracts). It extracts a single
257
+ // numeric metric and compares it against a per-brownfield-class bar resolved via `threshold_ref`. A
258
+ // check whose report file is ABSENT resolves to NA (the tool did not run / does not apply) — never a
259
+ // false-N. This logic is byte-identical to src/conformance/engine.ts (engine-parity.test.ts).
260
+
261
+ /** Read a numeric metric from a JSON report via a dotted path (e.g. `total.lines.pct`), or null. */
262
+ function extractJson(text, select) {
263
+ let node
264
+ try {
265
+ node = JSON.parse(text)
266
+ } catch {
267
+ return null
268
+ }
269
+ const keys = select.split('.').filter((k) => k !== '')
270
+ for (const [i, key] of keys.entries()) {
271
+ // A null/absent collection has zero elements: a TERMINAL `length` selector over a null/undefined
272
+ // node resolves to 0, not "no metric". golangci-lint marshals a nil (zero-issue) issue slice to
273
+ // JSON `null`, so a CLEAN Go lint run reads `Issues.length` = 0 ≤ ceiling (Y) instead of N "no
274
+ // metric for json:Issues.length" — the asymmetry that only bit the passing case (#1569).
275
+ if (key === 'length' && i === keys.length - 1 && (node === null || node === undefined)) {
276
+ return 0
277
+ }
278
+ if (node === null || typeof node !== 'object') return null
279
+ node = node[key]
280
+ }
281
+ const n = typeof node === 'number' ? node : Number(node)
282
+ return Number.isFinite(n) ? n : null
283
+ }
284
+
285
+ // A real element open ends in whitespace, '>' or '/' (not another name char) — mirrors the TS
286
+ // engine's isXmlElementBoundary so neither `count:` nor `attr:` matches a tag PREFIX.
287
+ function isXmlElementBoundary(c) {
288
+ return (
289
+ c === undefined || c === ' ' || c === '\t' || c === '\n' || c === '\r' || c === '>' || c === '/'
290
+ )
291
+ }
292
+
293
+ // Index of the Nth `<tag` whose next char ends the element name (a real open, never a tag PREFIX),
294
+ // or -1. `nth < 0` ⇒ count all such opens instead. Shared by `count:` and `attr:` so the
295
+ // prefix-boundary guard lives once (mirrors src/conformance/engine.ts scanElementOpens).
296
+ function scanElementOpens(text, tag, nth) {
297
+ const needle = `<${tag}`
298
+ let count = 0
299
+ let from = 0
300
+ for (;;) {
301
+ const i = text.indexOf(needle, from)
302
+ if (i < 0) break
303
+ if (isXmlElementBoundary(text[i + needle.length])) {
304
+ if (nth >= 0 && count === nth) return i
305
+ count++
306
+ }
307
+ from = i + needle.length
308
+ }
309
+ return nth >= 0 ? -1 : count
310
+ }
311
+
312
+ function extractXmlAttr(text, spec) {
313
+ const at = spec.indexOf('@')
314
+ if (at < 0) return null
315
+ const tag = spec.slice(0, at)
316
+ const attr = spec.slice(at + 1)
317
+ if (tag === '' || attr === '') return null
318
+ // Read from the FIRST boundary-valid `<tag` open — never a tag prefix.
319
+ const open = scanElementOpens(text, tag, 0)
320
+ if (open < 0) return null
321
+ const close = text.indexOf('>', open)
322
+ const segment = close < 0 ? text.slice(open) : text.slice(open, close)
323
+ // Guard the attr RegExp build: a regex metachar in the attr name (`attr:a@(`) would otherwise
324
+ // throw — yield null (⇒ per-check N 'no metric') instead of crashing the whole audit. Reject the
325
+ // catastrophic-backtracking family FIRST (matching this file's 3 other dynamic-RegExp sites) lest
326
+ // a valid `(a+)+`-shaped attr name hang the audit instead of throwing (#1551).
327
+ if (hasNestedUnboundedQuantifier(attr)) return null
328
+ let m
329
+ try {
330
+ m = new RegExp(`${attr}\\s*=\\s*"([^"]*)"`).exec(segment)
331
+ } catch {
332
+ return null
333
+ }
334
+ if (m === null) return null
335
+ const n = Number(m[1])
336
+ return Number.isFinite(n) ? n : null
337
+ }
338
+
339
+ /** Parse one `<tag …>` element segment's `name="value"` pairs into a map (fixed regex, ReDoS-safe). */
340
+ function parseXmlAttrs(segment) {
341
+ const attrs = {}
342
+ const re = /([\w:.-]+)\s*=\s*"([^"]*)"/g
343
+ let m
344
+ while ((m = re.exec(segment)) !== null) attrs[m[1]] = m[2]
345
+ return attrs
346
+ }
347
+
348
+ /**
349
+ * Coverage PERCENT from a JaCoCo-style report's report-TOTAL counter: the LAST
350
+ * `<counter type="<TYPE>" missed="M" covered="C"/>` element (JaCoCo emits counters in
351
+ * method → class → package → report order, so the report total is last in document order),
352
+ * computed as C*100/(C+M). Selecting the LAST match — not the leftmost — and dividing covered by
353
+ * the total is what makes this a real coverage RATIO instead of one arbitrary method's raw
354
+ * covered-line COUNT (#1629). Null when no such counter exists, an attr is missing/non-numeric, or
355
+ * the total is 0 (0/0 asserts no coverage). `(C*100)/total` keeps clean integers exactly representable.
356
+ */
357
+ function extractCoveragePercent(text, counterType) {
358
+ const needle = '<counter'
359
+ let from = 0
360
+ let covered = 0
361
+ let missed = 0
362
+ let found = false
363
+ for (;;) {
364
+ const i = text.indexOf(needle, from)
365
+ if (i < 0) break
366
+ from = i + needle.length
367
+ if (!isXmlElementBoundary(text[i + needle.length])) continue
368
+ const close = text.indexOf('>', i)
369
+ const segment = close < 0 ? text.slice(i) : text.slice(i, close)
370
+ const attrs = parseXmlAttrs(segment)
371
+ if (attrs['type'] !== counterType) continue
372
+ const c = Number(attrs['covered'])
373
+ const m = Number(attrs['missed'])
374
+ if (!Number.isFinite(c) || !Number.isFinite(m)) continue
375
+ covered = c
376
+ missed = m
377
+ found = true
378
+ }
379
+ if (!found) return null
380
+ const total = covered + missed
381
+ if (total <= 0) return null
382
+ return (covered * 100) / total
383
+ }
384
+
385
+ /**
386
+ * Read a numeric metric from an XML report. Selectors (deterministic, dependency-free):
387
+ * `count:tag` → number of `<tag` occurrences (open or self-closing)
388
+ * `attr:tag@name` → the numeric `name="…"` attribute of the first `<tag …>` element
389
+ * `coverage:TYPE` → report-total coverage PERCENT of the LAST `<counter type="TYPE">` (#1629)
390
+ */
391
+ function extractXml(text, select) {
392
+ if (select.startsWith('count:')) {
393
+ const tag = select.slice('count:'.length)
394
+ if (tag === '') return null
395
+ return scanElementOpens(text, tag, -1)
396
+ }
397
+ if (select.startsWith('attr:')) {
398
+ return extractXmlAttr(text, select.slice('attr:'.length))
399
+ }
400
+ if (select.startsWith('coverage:')) {
401
+ const counterType = select.slice('coverage:'.length)
402
+ if (counterType === '') return null
403
+ return extractCoveragePercent(text, counterType)
404
+ }
405
+ return null
406
+ }
407
+
408
+ /** Read a numeric metric from text via a regex whose first capture group is the number. */
409
+ function extractRegex(text, select) {
410
+ // Reject catastrophic-backtracking patterns (#1525) — a valid-syntax ReDoS regex would otherwise
411
+ // hang on adversarial report text; the surrounding try/catch only catches invalid syntax.
412
+ if (hasNestedUnboundedQuantifier(select)) return null
413
+ let re
414
+ try {
415
+ re = new RegExp(select)
416
+ } catch {
417
+ return null
418
+ }
419
+ const m = re.exec(text)
420
+ if (m === null || m[1] === undefined) return null
421
+ const n = Number(m[1])
422
+ return Number.isFinite(n) ? n : null
423
+ }
424
+
425
+ /**
426
+ * Total statement (≈ line) coverage PERCENT from a Go `go test -coverprofile` profile — the real
427
+ * artifact arbiter emits (there is no Go coverage.json). Each data line is
428
+ * `file.go:sl.sc,el.ec numStmt hitCount`; sum numStmt over all blocks and over the hit subset, then
429
+ * coveredStmts*100/totalStmts — exactly what `go tool cover -func` reports as total (#1629).
430
+ * Deterministic + dependency-free. Null when the profile has no data block or zero total statements.
431
+ */
432
+ function extractGoCoverProfile(text) {
433
+ let total = 0
434
+ let covered = 0
435
+ let sawBlock = false
436
+ for (const raw of text.split('\n')) {
437
+ const m = /:\d+\.\d+,\d+\.\d+\s+(\d+)\s+(\d+)\s*$/.exec(raw)
438
+ if (m === null) continue
439
+ const numStmt = Number(m[1])
440
+ const hits = Number(m[2])
441
+ if (!Number.isFinite(numStmt) || !Number.isFinite(hits)) continue
442
+ total += numStmt
443
+ if (hits > 0) covered += numStmt
444
+ sawBlock = true
445
+ }
446
+ if (!sawBlock || total <= 0) return null
447
+ return (covered * 100) / total
448
+ }
449
+
450
+ /** Extract a numeric metric from a report's text for the given format + selector, or null. */
451
+ function extractMetric(text, format, select) {
452
+ if (format === 'json') return extractJson(text, select)
453
+ if (format === 'xml') return extractXml(text, select)
454
+ if (format === 'regex') return extractRegex(text, select)
455
+ if (format === 'go-coverprofile') return extractGoCoverProfile(text)
456
+ return null
457
+ }
458
+
459
+ /** Apply a comparison operator. Unknown op fails closed (false). */
460
+ function compareValue(actual, op, bar) {
461
+ if (op === 'gte') return actual >= bar
462
+ if (op === 'lte') return actual <= bar
463
+ if (op === 'eq') return actual === bar
464
+ return false
465
+ }
466
+
467
+ /**
468
+ * Resolve the comparison bar for a value check: `threshold_ref` row keyed by the active brownfield
469
+ * class, else the literal `args.expected`. Returns null when neither yields a finite number.
470
+ */
471
+ function resolveBar(check, options) {
472
+ const ref = check.threshold_ref
473
+ if (ref !== undefined && ref !== '') {
474
+ const table = options.thresholds ?? {}
475
+ const row = table[ref]
476
+ if (row === undefined) return null
477
+ const cls = options.brownfieldClass ?? 'gold'
478
+ const bar = Object.prototype.hasOwnProperty.call(row, cls) ? row[cls] : undefined
479
+ return typeof bar === 'number' && Number.isFinite(bar) ? bar : null
480
+ }
481
+ const lit = check.args?.expected
482
+ return typeof lit === 'number' && Number.isFinite(lit) ? lit : null
483
+ }
484
+
485
+ /**
486
+ * value-op report-extraction evaluator (#1413). Reads a pre-generated tool report; absent report ⇒
487
+ * NA (no false-N). Bar resolved per brownfield class via threshold_ref / args.expected.
488
+ */
489
+ function evalValueReport(abs, rel, check, options) {
490
+ // Absent report ⇒ NA: the tool did not run / does not apply for this stack (never a false-N).
491
+ if (!existsSync(abs)) return { verdict: 'NA', evidence: null }
492
+ const args = check.args || {}
493
+ const format = String(args.format ?? '')
494
+ const select = String(args.select ?? '')
495
+ const op = String(args.op ?? '')
496
+ const bar = resolveBar(check, options)
497
+ if (bar === null) {
498
+ return { verdict: 'N', evidence: { file: rel, detail: 'unresolved threshold' } }
499
+ }
500
+ // Capped read (#1525): a regex-format report is fed to a registry regex — bound the bytes it sees.
501
+ const read = readScanText(abs)
502
+ if (!read.ok) {
503
+ const detail = read.reason === 'oversize' ? 'report too large to scan' : 'unreadable'
504
+ return { verdict: 'N', evidence: { file: rel, detail } }
505
+ }
506
+ const text = read.text
507
+ const actual = extractMetric(text, format, select)
508
+ if (actual === null) {
509
+ return { verdict: 'N', evidence: { file: rel, detail: `no metric for ${format}:${select}` } }
510
+ }
511
+ const pass = compareValue(actual, op, bar)
512
+ return pass
513
+ ? { verdict: 'Y', evidence: { file: rel, detail: `${actual} ${op} ${bar}` } }
514
+ : { verdict: 'N', evidence: { file: rel, detail: `${actual} !${op} ${bar}` } }
515
+ }
516
+
517
+ // ── version_consistency: a VERSION file ↔ the latest CHANGELOG entry ─────────────
518
+ //
519
+ // Catches release-hygiene drift: the declared VERSION must equal the newest version recorded in
520
+ // the CHANGELOG. Pure text read + a single regex (no spawn, no wall-clock) — byte-deterministic.
521
+
522
+ /** First capture group of `pattern` (multiline) in `text` = the latest declared version, or null. */
523
+ function latestChangelogVersion(text, pattern) {
524
+ if (typeof pattern !== 'string' || pattern === '') return null
525
+ // Reject catastrophic-backtracking patterns (#1525) before running over the full changelog text.
526
+ if (hasNestedUnboundedQuantifier(pattern)) return null
527
+ let re
528
+ try {
529
+ re = new RegExp(pattern, 'm')
530
+ } catch {
531
+ return null
532
+ }
533
+ const m = re.exec(text)
534
+ // A falsy/empty capture is NOT a version — return null so the caller scores P (indeterminate),
535
+ // never a false Y (e.g. an empty trimmed VERSION === an empty capture).
536
+ return m !== null && m[1] ? m[1] : null
537
+ }
538
+
539
+ /**
540
+ * Read a STRING value from a JSON document via a dotted path (e.g. `version` in package.json), or
541
+ * null when the text is not JSON / the path is absent / the value is not a non-empty string. The
542
+ * string twin of extractJson — a non-string field is never coerced (so a missing version scores P,
543
+ * never a false Y).
544
+ */
545
+ function extractJsonString(text, select) {
546
+ let node
547
+ try {
548
+ node = JSON.parse(text)
549
+ } catch {
550
+ return null
551
+ }
552
+ for (const key of select.split('.')) {
553
+ if (key === '') continue
554
+ if (node === null || typeof node !== 'object') return null
555
+ node = node[key]
556
+ }
557
+ return typeof node === 'string' && node !== '' ? node : null
558
+ }
559
+
560
+ /** Classify an unusable declared version without letting an empty artifact earn partial credit. */
561
+ function invalidVersionResult(version, vFile, vSelect) {
562
+ if (version === null)
563
+ return { verdict: 'P', evidence: { file: vFile, detail: `no ${vSelect} in version file` } }
564
+ if (version === '')
565
+ return { verdict: 'N', evidence: { file: vFile, detail: 'empty version file' } }
566
+ return null
567
+ }
568
+
569
+ /**
570
+ * version_consistency evaluator. Y = the declared version equals the latest CHANGELOG entry; P =
571
+ * both present and substantively divergent OR no substantive changelog entry matches the pattern
572
+ * OR the version is indeterminate (indeterminate — never a false Y); N = a required file is
573
+ * missing, unreadable, or empty.
574
+ * The version comes from a plain-text file (trimmed) or, when `version_select` is set, from a
575
+ * dotted JSON path inside `version_file` (e.g. version_file: package.json, version_select: version).
576
+ */
577
+ function evalVersionConsistency(args, root) {
578
+ const vFile = typeof args.version_file === 'string' ? args.version_file : ''
579
+ const cFile = typeof args.changelog_file === 'string' ? args.changelog_file : ''
580
+ const vSelect = typeof args.version_select === 'string' ? args.version_select : ''
581
+ const vAbs = safeResolve(root, vFile)
582
+ const cAbs = safeResolve(root, cFile)
583
+ if (vAbs === null || cAbs === null) {
584
+ return { verdict: 'N', evidence: { file: vFile || cFile, detail: 'invalid path' } }
585
+ }
586
+ const vText = readText(vAbs)
587
+ if (vText === null)
588
+ return { verdict: 'N', evidence: { file: vFile, detail: 'missing version file' } }
589
+ // Capped read (#1525): the changelog is fed to a registry regex — bound the bytes it runs over.
590
+ const cRead = readScanText(cAbs)
591
+ if (!cRead.ok) {
592
+ const detail = cRead.reason === 'oversize' ? 'changelog too large to scan' : 'missing changelog'
593
+ return { verdict: 'N', evidence: { file: cFile, detail } }
594
+ }
595
+ const cText = cRead.text
596
+ if (cText.trim() === '')
597
+ return { verdict: 'N', evidence: { file: cFile, detail: 'present but empty' } }
598
+ const version = vSelect !== '' ? extractJsonString(vText, vSelect) : vText.trim()
599
+ const invalidVersion = invalidVersionResult(version, vFile, vSelect)
600
+ if (invalidVersion !== null) return invalidVersion
601
+ const latest = latestChangelogVersion(cText, args.changelog_pattern)
602
+ if (latest === null) {
603
+ return {
604
+ verdict: 'P',
605
+ evidence: { file: cFile, detail: 'no changelog entry matches the pattern' },
606
+ }
607
+ }
608
+ if (version === latest) {
609
+ return { verdict: 'Y', evidence: { file: vFile, detail: `${version} == ${latest}` } }
610
+ }
611
+ return {
612
+ verdict: 'P',
613
+ evidence: { file: cFile, detail: `VERSION ${version} != CHANGELOG ${latest}` },
614
+ }
615
+ }
616
+
617
+ // ── Per-type check evaluators (extracted to mirror src/conformance/engine.ts) ────
618
+ // The dispatch in evalCheck stays a thin if-chain over these handlers so the two engines keep an
619
+ // identical structure (parity) and no single function grows past the complexity ceiling as types
620
+ // are added. file_exists rejects directories, returns N for empty files, and returns identical
621
+ // evidence to the TS engine (removes previously-tolerated directory/evidence divergence).
622
+
623
+ function evalFileExists(abs, rel) {
624
+ if (!existsSync(abs)) return { verdict: 'N', evidence: { file: rel, detail: 'missing' } }
625
+ try {
626
+ if (statSync(abs).isDirectory())
627
+ return { verdict: 'N', evidence: { file: rel, detail: 'is a directory' } }
628
+ } catch {
629
+ return { verdict: 'N', evidence: { file: rel, detail: 'unreadable' } }
630
+ }
631
+ const text = readText(abs)
632
+ if (text === null) return { verdict: 'N', evidence: { file: rel, detail: 'unreadable' } }
633
+ // A presence check asks whether content exists: empty artifacts earn nothing. P is reserved for
634
+ // version_consistency's real-but-divergent substance, not an empty file that touch can create.
635
+ if (text.trim() === '')
636
+ return { verdict: 'N', evidence: { file: rel, detail: 'present but empty' } }
637
+ return { verdict: 'Y', evidence: { file: rel } }
638
+ }
639
+
640
+ function evalFileContains(abs, rel, pattern) {
641
+ // An empty/missing pattern is a registry authoring error, never a satisfied property: `''.indexOf`
642
+ // matches at index 0 of any readable file, so an omitted `pattern` would fake-green a verified-Y
643
+ // with zero evidence that anything is present. Refuse it — mirrors evalForbiddenPattern's
644
+ // empty-pattern N so the anti-fake-green contract is uniform across check types (#1591).
645
+ if (pattern === '') {
646
+ return { verdict: 'N', evidence: { file: rel, detail: 'empty or missing pattern' } }
647
+ }
648
+ const text = readText(abs)
649
+ if (text === null) return { verdict: 'N', evidence: { file: rel, detail: 'missing' } }
650
+ const line = lineOf(text, pattern)
651
+ return line !== null
652
+ ? { verdict: 'Y', evidence: { file: rel, line } }
653
+ : { verdict: 'N', evidence: { file: rel, detail: `pattern not found: ${pattern}` } }
654
+ }
655
+
656
+ /** Count non-overlapping occurrences of `pattern` in `text` (0 for an empty pattern). */
657
+ function countOccurrences(text, pattern) {
658
+ if (pattern.length === 0) return 0
659
+ let count = 0
660
+ let from = 0
661
+ for (;;) {
662
+ const i = text.indexOf(pattern, from)
663
+ if (i < 0) break
664
+ count++
665
+ from = i + pattern.length
666
+ }
667
+ return count
668
+ }
669
+
670
+ function evalCountMatches(abs, rel, pattern, want) {
671
+ const text = readText(abs)
672
+ if (text === null) return { verdict: 'N', evidence: { file: rel, detail: 'missing' } }
673
+ const count = countOccurrences(text, pattern)
674
+ if (count >= want) return { verdict: 'Y', evidence: { file: rel, detail: `count=${count}` } }
675
+ if (count > 0) return { verdict: 'P', evidence: { file: rel, detail: `count=${count}/${want}` } }
676
+ return { verdict: 'N', evidence: { file: rel, detail: `count=0/${want}` } }
677
+ }
678
+
679
+ function evalValue(abs, rel, expected) {
680
+ // Same empty-needle hole as evalFileContains: `''.indexOf` matches line 1 of any readable file, so
681
+ // a value check that omits `equals` would fake-green to Y with no evidence. Refuse it (#1591).
682
+ if (expected === '') {
683
+ return { verdict: 'N', evidence: { file: rel, detail: 'empty or missing pattern' } }
684
+ }
685
+ const text = readText(abs)
686
+ if (text === null) return { verdict: 'N', evidence: { file: rel, detail: 'missing' } }
687
+ const line = lineOf(text, expected)
688
+ return line !== null
689
+ ? { verdict: 'Y', evidence: { file: rel, line } }
690
+ : { verdict: 'N', evidence: { file: rel, detail: `value not present: ${expected}` } }
691
+ }
692
+
693
+ // ── forbidden_pattern: a regex that must NOT appear in any file under a glob (#1470) ──
694
+ //
695
+ // Scores an ABSENCE property with anti-fake-green hardening (red-team): an empty scan must never
696
+ // fake-green. Verdict ladder (order matters — evaluated top-down, byte-identical to engine.ts):
697
+ // empty/non-string pattern ⇒ N · invalid regex ⇒ N · invalid/empty glob ⇒ N ·
698
+ // exclude entry with a glob char ⇒ N (literal-only) · exclude_paths set w/o rationale ⇒ N ·
699
+ // glob matched 0 files ⇒ NA (nothing of this kind exists) ·
700
+ // excludes removed ALL matched files ⇒ N (refuse to fake-green an emptied scan) ·
701
+ // pattern found ⇒ N (first SORTED file + line) · absent across every scanned file ⇒ Y.
702
+
703
+ /** True if a string contains any glob metacharacter (so it is NOT a literal path). */
704
+ function hasGlobChar(s) {
705
+ return /[*?[\]]/.test(s)
706
+ }
707
+
708
+ function evalForbiddenPattern(args, root, getFiles) {
709
+ const pattern = typeof args.pattern === 'string' ? args.pattern : ''
710
+ if (pattern === '') {
711
+ return { verdict: 'N', evidence: { file: '', detail: 'empty or non-string pattern' } }
712
+ }
713
+ let re
714
+ try {
715
+ re = new RegExp(pattern)
716
+ } catch {
717
+ return { verdict: 'N', evidence: { file: '', detail: `invalid regex: ${pattern}` } }
718
+ }
719
+ // Reject catastrophic-backtracking patterns (#1525): a valid-syntax ReDoS regex like (a+)+$ would
720
+ // otherwise hang the scan over an adversarial matched file. Deterministic ⇒ engine-parity.
721
+ if (hasNestedUnboundedQuantifier(pattern)) {
722
+ return { verdict: 'N', evidence: { file: '', detail: `unsafe regex (ReDoS risk): ${pattern}` } }
723
+ }
724
+ const g = resolveGlobArg(args, root, getFiles)
725
+ if (!g.ok) return g.result
726
+ const { glob, matched } = g
727
+ const excludeRaw = Array.isArray(args.exclude_paths) ? args.exclude_paths : []
728
+ for (const ex of excludeRaw) {
729
+ if (typeof ex !== 'string' || hasGlobChar(ex)) {
730
+ return {
731
+ verdict: 'N',
732
+ evidence: { file: glob, detail: `exclude_paths must be literal: ${ex}` },
733
+ }
734
+ }
735
+ }
736
+ if (excludeRaw.length > 0) {
737
+ const rationale = typeof args.rationale === 'string' ? args.rationale.trim() : ''
738
+ if (rationale === '') {
739
+ return {
740
+ verdict: 'N',
741
+ evidence: { file: glob, detail: 'exclude_paths requires a rationale' },
742
+ }
743
+ }
744
+ }
745
+ if (matched.length === 0) {
746
+ return { verdict: 'NA', evidence: null }
747
+ }
748
+ const exclude = new Set(excludeRaw)
749
+ const remaining = matched.filter((f) => !exclude.has(f))
750
+ if (remaining.length === 0) {
751
+ return {
752
+ verdict: 'N',
753
+ evidence: { file: glob, detail: 'all matched files excluded (refusing fake-green)' },
754
+ }
755
+ }
756
+ for (const rel of remaining) {
757
+ const abs = safeResolve(root, rel)
758
+ // Capped read (#1525): an over-cap matched file fails closed — we cannot assert ABSENCE over
759
+ // bytes we refuse to read, so it is N (never a fake-green Y), with the cap noted in the evidence.
760
+ const read = abs === null ? null : readScanText(abs)
761
+ if (read === null || !read.ok) {
762
+ // A matched, non-excluded file we cannot read (or that exceeds the scan cap) ⇒ we cannot assert
763
+ // the pattern is ABSENT over it. Fail-closed N (never a silent skip → a chmod-000 file holding
764
+ // the marker must not fake-green to Y). Deterministic: `remaining` is sorted, first anomaly wins.
765
+ const detail =
766
+ read !== null && read.reason === 'oversize'
767
+ ? `too large to scan (> ${MAX_SCAN_BYTES} bytes) — cannot verify absence`
768
+ : 'unreadable — cannot verify absence'
769
+ return { verdict: 'N', evidence: { file: rel, detail } }
770
+ }
771
+ const text = read.text
772
+ const m = re.exec(text)
773
+ if (m !== null) {
774
+ return {
775
+ verdict: 'N',
776
+ evidence: {
777
+ file: rel,
778
+ line: lineAtIndex(text, m.index),
779
+ detail: 'forbidden pattern present',
780
+ },
781
+ }
782
+ }
783
+ }
784
+ // Y only when EVERY remaining file was actually read — the count is honest, never inflated.
785
+ return {
786
+ verdict: 'Y',
787
+ evidence: { file: glob, detail: `absent across ${remaining.length} file(s)` },
788
+ }
789
+ }
790
+
791
+ // ── file_stat: the executable bit on a glob of files (#1470) ─────────────────────
792
+ //
793
+ // Only the executable bit (mode & 0o111) is portable — git tracks 0o111 but read/write depend on
794
+ // the umask (non-deterministic), so a readable/writable request is a config error (N). Gated behind
795
+ // core.fileMode: when git does not track the exec bit the property is unmeasurable ⇒ NA. Symlinks
796
+ // are evaluated by their OWN mode (lstat), never their target's. Verdict: all exec ⇒ Y, some ⇒ P,
797
+ // none ⇒ N; a valid glob matching 0 files ⇒ NA; a malformed glob ⇒ N.
798
+
799
+ function evalFileStat(args, root, getFiles) {
800
+ const bit = typeof args.bit === 'string' ? args.bit.toLowerCase() : 'executable'
801
+ if (bit !== 'executable') {
802
+ return {
803
+ verdict: 'N',
804
+ evidence: { file: '', detail: `only the executable bit is deterministic, got: ${bit}` },
805
+ }
806
+ }
807
+ const g = resolveGlobArg(args, root, getFiles)
808
+ if (!g.ok) return g.result
809
+ const { glob, matched } = g
810
+ if (matched.length === 0) {
811
+ return { verdict: 'NA', evidence: null }
812
+ }
813
+ if (!gitFileModeEnabled(root)) {
814
+ return { verdict: 'NA', evidence: null }
815
+ }
816
+ let withBit = 0
817
+ let firstMissing = null
818
+ for (const rel of matched) {
819
+ const abs = safeResolve(root, rel)
820
+ let exec = false
821
+ try {
822
+ const st = abs === null ? null : lstatSync(abs)
823
+ // A symlink is NOT a tracked executable regular file (git stores it as a symlink, not 0o755),
824
+ // and its OWN lstat mode is always 0o777 — trusting it would fake-green the exec bit. Treat a
825
+ // symlink as not-executable: deterministic (symlink-ness is stable) and anti-fake-green.
826
+ exec = st !== null && !st.isSymbolicLink() && (st.mode & 0o111) !== 0
827
+ } catch {
828
+ exec = false
829
+ }
830
+ if (exec) withBit++
831
+ else if (firstMissing === null) firstMissing = rel
832
+ }
833
+ if (withBit === matched.length) {
834
+ return {
835
+ verdict: 'Y',
836
+ evidence: { file: glob, detail: `executable across ${matched.length} file(s)` },
837
+ }
838
+ }
839
+ if (withBit === 0) {
840
+ return { verdict: 'N', evidence: { file: firstMissing ?? glob, detail: 'not executable' } }
841
+ }
842
+ return {
843
+ verdict: 'P',
844
+ evidence: { file: firstMissing ?? glob, detail: `executable ${withBit}/${matched.length}` },
845
+ }
846
+ }
847
+
848
+ /**
849
+ * Evaluate a single check against the repo. Thin dispatch over the per-type handlers above; returns
850
+ * { verdict, evidence } where evidence is { file, line?, detail? } for code-verifiable checks, or
851
+ * null for NA/NV. Glob/pair checks (version_consistency, forbidden_pattern, file_stat) dispatch
852
+ * BEFORE the single-file `args.path` resolve, since they own their own path handling.
853
+ */
854
+ function evalCheck(check, root, options, getFiles) {
855
+ const type = check.type
856
+ if (type === 'manual') return { verdict: 'NV', evidence: null }
857
+ const args = check.args || {}
858
+ if (type === 'version_consistency') return evalVersionConsistency(args, root)
859
+ if (type === 'forbidden_pattern') return evalForbiddenPattern(args, root, getFiles)
860
+ if (type === 'file_stat') return evalFileStat(args, root, getFiles)
861
+
862
+ const abs = safeResolve(root, args.path)
863
+ if (abs === null) {
864
+ // An unresolvable / traversal path is a verified failure, with a detail note.
865
+ return { verdict: 'N', evidence: { file: String(args.path ?? ''), detail: 'invalid path' } }
866
+ }
867
+ const rel = args.path
868
+
869
+ if (type === 'file_exists') return evalFileExists(abs, rel)
870
+ if (type === 'file_contains') return evalFileContains(abs, rel, String(args.pattern ?? ''))
871
+ if (type === 'count_matches')
872
+ return evalCountMatches(abs, rel, String(args.pattern ?? ''), Number(args.min ?? 1))
873
+ if (type === 'value') {
874
+ // A value check with a report `format` reads a pre-generated tool report (#1413); without one it
875
+ // keeps the legacy single-line `equals`-contains behavior (back-compat — same verdicts as before).
876
+ return args.format
877
+ ? evalValueReport(abs, rel, check, options || {})
878
+ : evalValue(abs, rel, String(args.equals ?? ''))
879
+ }
880
+
881
+ // Unknown check type — treat as not verified (never a silent pass).
882
+ return { verdict: 'N', evidence: { file: rel || '', detail: `unknown check type: ${type}` } }
883
+ }
884
+
885
+ // ── applies_if conditional gating (G1) ──────────────────────────────────────────
886
+ // Mirror of src/conformance/engine.ts (parity contract). A precondition that is MET ⇒ the check is
887
+ // evaluated; UNMET ⇒ NA. FAIL-SAFE: a malformed / uninterpretable precondition (unknown type,
888
+ // missing required field, invalid path) ⇒ the check APPLIES — a silent skip would be a false-green.
889
+
890
+ /** capability precondition: met when `name` is in the overlay set. Missing name ⇒ APPLIES. */
891
+ function capabilityMet(cond, overlays) {
892
+ const name = typeof cond.name === 'string' ? cond.name : ''
893
+ if (name === '') return true
894
+ return overlays.has(name)
895
+ }
896
+
897
+ /** file_exists precondition: met when `path` resolves to an existing file. Bad path ⇒ APPLIES. */
898
+ function fileExistsMet(cond, root) {
899
+ const p = typeof cond.path === 'string' ? cond.path : ''
900
+ if (p === '') return true
901
+ const abs = safeResolve(root, p)
902
+ if (abs === null) return true
903
+ return existsSync(abs)
904
+ }
905
+
906
+ /** file_contains precondition: met when the file at `path` contains `pattern`. Malformed ⇒ APPLIES. */
907
+ function fileContainsMet(cond, root) {
908
+ const p = typeof cond.path === 'string' ? cond.path : ''
909
+ const pattern = typeof cond.pattern === 'string' ? cond.pattern : ''
910
+ if (p === '' || pattern === '') return true
911
+ const abs = safeResolve(root, p)
912
+ if (abs === null) return true
913
+ const text = readText(abs)
914
+ if (text === null) return false // marker file absent/unreadable ⇒ precondition UNMET ⇒ NA
915
+ return text.includes(pattern)
916
+ }
917
+
918
+ /** count_matches precondition: met when `path` contains `pattern` ≥ `min` times. Malformed ⇒ APPLIES. */
919
+ function countMatchesMet(cond, root) {
920
+ const p = typeof cond.path === 'string' ? cond.path : ''
921
+ const pattern = typeof cond.pattern === 'string' ? cond.pattern : ''
922
+ if (p === '' || pattern === '') return true
923
+ const min = Number(cond.min ?? 1)
924
+ if (!Number.isFinite(min)) return true
925
+ const abs = safeResolve(root, p)
926
+ if (abs === null) return true
927
+ const text = readText(abs)
928
+ if (text === null) return false // marker file absent/unreadable ⇒ precondition UNMET ⇒ NA
929
+ return countOccurrences(text, pattern) >= min
930
+ }
931
+
932
+ /** Dispatch an object-form precondition. Unknown `type` ⇒ APPLIES (fail-safe, never a silent skip). */
933
+ function preconditionApplies(cond, overlays, root) {
934
+ const type = typeof cond.type === 'string' ? cond.type : ''
935
+ if (type === 'capability') return capabilityMet(cond, overlays)
936
+ if (type === 'file_exists') return fileExistsMet(cond, root)
937
+ if (type === 'file_contains') return fileContainsMet(cond, root)
938
+ if (type === 'count_matches') return countMatchesMet(cond, root)
939
+ return true
940
+ }
941
+
942
+ /**
943
+ * True if a check applies (applies_if absent ⇒ always). A string is an overlay/capability name
944
+ * (legacy). An object is a generic precondition (G1). Any other type ⇒ APPLIES (fail-safe).
945
+ */
946
+ function isApplicable(check, overlays, root) {
947
+ const cond = check.applies_if
948
+ if (!cond || cond === 'always') return true
949
+ if (typeof cond === 'string') return overlays.has(cond)
950
+ if (typeof cond === 'object') return preconditionApplies(cond, overlays, root)
951
+ return true
952
+ }
953
+
954
+ /** Numeric verdict weight: Y=1, P=0.5, N=0; NA/NV excluded from the denominator. */
955
+ function verdictPoints(verdict) {
956
+ if (verdict === 'Y') return 1
957
+ if (verdict === 'P') return 0.5
958
+ return 0
959
+ }
960
+
961
+ /**
962
+ * Evaluate the whole registry. Deterministic: checks sorted by id, no timestamps.
963
+ * @returns scored payload: { registryVersion, score, yCount, riskyCount, totals, dimensions, checks }
964
+ */
965
+ function evaluateInner(registry, overlays, root, options = {}) {
966
+ const overlaySet = overlays instanceof Set ? overlays : new Set(overlays || [])
967
+ const opts = options && typeof options === 'object' ? options : {}
968
+ // Drop non-object entries (a stray `-`/commented item in a templated YAML list parses to null) so
969
+ // the valid checks are still scored and neither engine throws — shared fail-closed shape with the TS.
970
+ const rawChecks = (Array.isArray(registry?.checks) ? registry.checks : []).filter(
971
+ (c) => c && typeof c === 'object',
972
+ )
973
+ const sorted = [...rawChecks].sort((a, b) => cmpCodeUnit(String(a.id), String(b.id)))
974
+
975
+ const checks = []
976
+ const dims = new Map()
977
+ let yCount = 0
978
+ let riskyCount = 0
979
+ let earned = 0
980
+ let possible = 0
981
+
982
+ // Walk the repo tree at most ONCE for the whole evaluate() (#1600): the first glob check
983
+ // (forbidden_pattern / file_stat) triggers the walk and every later glob check reuses the cached
984
+ // list. A registry with no glob check never walks (K=0 ⇒ 0 walks). Per-evaluate state only — no
985
+ // global cache — so determinism + the fail-closed contract are untouched. Mirrors engine.ts EvalRun.
986
+ let walkedFiles = null
987
+ const getFiles = () => (walkedFiles ??= walkRepo(root))
988
+
989
+ for (const check of sorted) {
990
+ const applicable = isApplicable(check, overlaySet, root)
991
+ let verdict
992
+ let evidence
993
+ if (!applicable) {
994
+ verdict = 'NA'
995
+ evidence = null
996
+ } else {
997
+ const r = evalCheck(check, root, opts, getFiles)
998
+ verdict = r.verdict
999
+ evidence = r.evidence
1000
+ }
1001
+
1002
+ // A non-numeric/non-finite/negative weight (a registry typo like `weight: high`) yields NaN, and
1003
+ // a single NaN poisons the summed earned/possible accumulators for the WHOLE registry → a silent
1004
+ // overall score of 0. Coerce any such weight to the default 1 (mirrors src/conformance/engine.ts)
1005
+ // so one bad cell can never zero every sibling's score.
1006
+ const rawWeight = Number(check.weight ?? 1)
1007
+ const weight = Number.isFinite(rawWeight) && rawWeight >= 0 ? rawWeight : 1
1008
+ const risk = check.risk === 'RISKY' ? 'RISKY' : 'SAFE'
1009
+ if (risk === 'RISKY') riskyCount++
1010
+ if (verdict === 'Y') yCount++
1011
+
1012
+ // Scoring denominator: applicable, code-verifiable checks only (exclude NA + NV).
1013
+ if (verdict !== 'NA' && verdict !== 'NV') {
1014
+ earned += verdictPoints(verdict) * weight
1015
+ possible += weight
1016
+ }
1017
+
1018
+ const dimId = String(check.dimension ?? 'D-UNCLASSIFIED')
1019
+ if (!dims.has(dimId)) dims.set(dimId, { earned: 0, possible: 0, y: 0 })
1020
+ const d = dims.get(dimId)
1021
+ if (verdict === 'Y') d.y++
1022
+ if (verdict !== 'NA' && verdict !== 'NV') {
1023
+ d.earned += verdictPoints(verdict) * weight
1024
+ d.possible += weight
1025
+ }
1026
+
1027
+ checks.push({
1028
+ id: String(check.id),
1029
+ dimension: dimId,
1030
+ title: String(check.title ?? ''),
1031
+ type: String(check.type ?? ''),
1032
+ verdict,
1033
+ weight,
1034
+ risk,
1035
+ anchor: check.anchor ? String(check.anchor) : null,
1036
+ evidence,
1037
+ })
1038
+ }
1039
+
1040
+ const score = possible > 0 ? Math.round((earned / possible) * 1000) / 10 : 0
1041
+ const dimensions = {}
1042
+ for (const [id, d] of [...dims.entries()].sort((a, b) => cmpCodeUnit(a[0], b[0]))) {
1043
+ dimensions[id] = {
1044
+ score: d.possible > 0 ? Math.round((d.earned / d.possible) * 1000) / 10 : 0,
1045
+ y: d.y,
1046
+ }
1047
+ }
1048
+
1049
+ return {
1050
+ registryVersion: String(registry?.version ?? '0'),
1051
+ score,
1052
+ yCount,
1053
+ riskyCount,
1054
+ totals: {
1055
+ checks: checks.length,
1056
+ y: yCount,
1057
+ p: checks.filter((c) => c.verdict === 'P').length,
1058
+ n: checks.filter((c) => c.verdict === 'N').length,
1059
+ na: checks.filter((c) => c.verdict === 'NA').length,
1060
+ nv: checks.filter((c) => c.verdict === 'NV').length,
1061
+ },
1062
+ dimensions,
1063
+ checks,
1064
+ }
1065
+ }
1066
+
1067
+ /**
1068
+ * Evaluate the whole registry, fail-closed: any uncaught error returns a zero-score payload rather
1069
+ * than throwing (byte-identical fail-closed shape to src/conformance/engine.ts — engine-parity).
1070
+ */
1071
+ export function evaluate(registry, overlays, root, options = {}) {
1072
+ try {
1073
+ return evaluateInner(registry, overlays, root, options)
1074
+ } catch {
1075
+ return {
1076
+ registryVersion: '0',
1077
+ score: 0,
1078
+ yCount: 0,
1079
+ riskyCount: 0,
1080
+ totals: { checks: 0, y: 0, p: 0, n: 0, na: 0, nv: 0 },
1081
+ dimensions: {},
1082
+ checks: [],
1083
+ }
1084
+ }
1085
+ }
1086
+
1087
+ /** Compare current vs baseline; return { ok, reasons } — ok=false on score/Y regression. */
1088
+ export function checkNoRegress(current, baseline) {
1089
+ const reasons = []
1090
+ if (!baseline || typeof baseline !== 'object') return { ok: true, reasons }
1091
+ if (typeof baseline.score === 'number' && current.score < baseline.score) {
1092
+ reasons.push(`score regressed: ${current.score} < baseline ${baseline.score}`)
1093
+ }
1094
+ if (typeof baseline.yCount === 'number' && current.yCount < baseline.yCount) {
1095
+ reasons.push(`Y-count regressed: ${current.yCount} < baseline ${baseline.yCount}`)
1096
+ }
1097
+ return { ok: reasons.length === 0, reasons }
1098
+ }
1099
+
1100
+ /** Monotonic ratchet: per field, keep max(current, baseline) — can only tighten. */
1101
+ export function ratchet(current, baseline) {
1102
+ const prev = baseline && typeof baseline === 'object' ? baseline : {}
1103
+ const dimensions = {}
1104
+ const ids = new Set([
1105
+ ...Object.keys(current.dimensions || {}),
1106
+ ...Object.keys(prev.dimensions || {}),
1107
+ ])
1108
+ for (const id of [...ids].sort()) {
1109
+ const c = current.dimensions?.[id] || { score: 0, y: 0 }
1110
+ const p = prev.dimensions?.[id] || { score: 0, y: 0 }
1111
+ dimensions[id] = { score: Math.max(c.score, p.score ?? 0), y: Math.max(c.y, p.y ?? 0) }
1112
+ }
1113
+ return {
1114
+ score: Math.max(current.score, typeof prev.score === 'number' ? prev.score : 0),
1115
+ yCount: Math.max(current.yCount, typeof prev.yCount === 'number' ? prev.yCount : 0),
1116
+ dimensions,
1117
+ }
1118
+ }
1119
+
1120
+ /** Baseline-shaped snapshot of a scored payload (the ratchet-compared subset, no timestamp). */
1121
+ export function baselineOf(current) {
1122
+ return { score: current.score, yCount: current.yCount, dimensions: current.dimensions }
1123
+ }
1124
+
1125
+ // ── #1414: deterministic gold-LEVEL band + "what's missing" gap report ──────────────────────────
1126
+ //
1127
+ // The level band maps a code-computed score (0–100) to a Target Maturity Level (L0–L3) keyed by
1128
+ // the repo's brownfieldClass: a heavy legacy repo reaches a given band at a LOWER score than a
1129
+ // greenfield gold repo (the realistic target differs by starting condition). Thresholds are the
1130
+ // minimum score required to ENTER each level; L0 is the floor (always entered).
1131
+
1132
+ /** Per-class entry thresholds: [L1, L2, L3] minimum scores. gold = strictest, heavy = most lenient. */
1133
+ const LEVEL_THRESHOLDS = {
1134
+ gold: [50, 75, 95],
1135
+ light: [45, 70, 90],
1136
+ medium: [40, 60, 85],
1137
+ heavy: [30, 50, 75],
1138
+ }
1139
+ const LEVELS = ['L0', 'L1', 'L2', 'L3']
1140
+
1141
+ /**
1142
+ * Map a score to a level band for a brownfieldClass. Pure + deterministic.
1143
+ * @returns { level, nextLevel, toNextLevel, brownfieldClass, thresholds }
1144
+ */
1145
+ export function levelBand(score, brownfieldClass) {
1146
+ const cls = Object.prototype.hasOwnProperty.call(LEVEL_THRESHOLDS, brownfieldClass)
1147
+ ? brownfieldClass
1148
+ : 'gold' // unknown class → strictest band (fail-safe, never over-credits)
1149
+ const thresholds = LEVEL_THRESHOLDS[cls]
1150
+ const s = Number.isFinite(score) ? score : 0
1151
+ let idx = 0
1152
+ for (let i = 0; i < thresholds.length; i++) if (s >= thresholds[i]) idx = i + 1
1153
+ const level = LEVELS[idx]
1154
+ const nextLevel = idx < LEVELS.length - 1 ? LEVELS[idx + 1] : null
1155
+ const toNextLevel =
1156
+ nextLevel === null ? 0 : Math.max(0, Math.round((thresholds[idx] - s) * 10) / 10)
1157
+ return { level, nextLevel, toNextLevel, brownfieldClass: cls, thresholds }
1158
+ }
1159
+
1160
+ /**
1161
+ * "What's missing" report: the checks with verdict N or P (the actionable gaps), grouped by
1162
+ * dimension (family), each with its evidence. Y/NA/NV are excluded — you cannot close an NV (code
1163
+ * can't verify it) or an NA (it doesn't apply) by working on it. Deterministic: dimensions sorted
1164
+ * by id, checks in the already-stable id order from evaluate().
1165
+ * @returns Array<{ dimension, title, checks: Array<{id,title,verdict,evidence,anchor}> }>
1166
+ */
1167
+ export function gapReport(result) {
1168
+ const checks = Array.isArray(result?.checks) ? result.checks : []
1169
+ const byDim = new Map()
1170
+ for (const c of checks) {
1171
+ if (c.verdict !== 'N' && c.verdict !== 'P') continue
1172
+ const dim = String(c.dimension ?? 'D-UNCLASSIFIED')
1173
+ if (!byDim.has(dim)) byDim.set(dim, [])
1174
+ byDim.get(dim).push({
1175
+ id: c.id,
1176
+ title: c.title,
1177
+ verdict: c.verdict,
1178
+ anchor: c.anchor ?? null,
1179
+ evidence: c.evidence ?? null,
1180
+ })
1181
+ }
1182
+ return [...byDim.entries()]
1183
+ .sort((a, b) => cmpCodeUnit(a[0], b[0]))
1184
+ .map(([dimension, gaps]) => ({ dimension, checks: gaps }))
1185
+ }
1186
+
1187
+ // ── #1473: fail-closed freshness banner (out-of-band, NEVER in the scored payload) ───────────────
1188
+ //
1189
+ // A `value` check with an `args.format` reads a PRE-GENERATED tool report; an absent report scores
1190
+ // NA silently (the tool may not apply OR may simply never have run). freshness() is the out-of-band
1191
+ // signal that distinguishes those: it stats every value-check report path and reports FRESH/PARTIAL/
1192
+ // STALE. The wall-clock (mtime / now) lives ONLY here — it never enters evaluate()'s byte-deterministic
1193
+ // payload — so the scored artifact stays reproducible while the banner stays honest (fail-closed:
1194
+ // a report that is absent, or older than the window, is never counted as FRESH).
1195
+
1196
+ /**
1197
+ * Classify the freshness of the value-check report files declared by `registry`. Pure given (now).
1198
+ * FRESH — every declared report present AND within staleHours (or no reports declared at all)
1199
+ * PARTIAL — at least one report present, but some missing or older than the window
1200
+ * STALE — reports are declared but NONE are present (the tools never ran)
1201
+ * @param {{checks?: unknown[]}} registry
1202
+ * @param {string} root
1203
+ * @param {{ staleHours?: number, now?: number, overlays?: Set<string>|Iterable<string> }} [options]
1204
+ * @returns {{ status: 'FRESH'|'PARTIAL'|'STALE', staleHours: number,
1205
+ * counts: { total: number, present: number, fresh: number },
1206
+ * reports: Array<{ path: string, present: boolean, ageHours: number|null, fresh: boolean }> }}
1207
+ */
1208
+ export function freshness(registry, root, options = {}) {
1209
+ const staleHours =
1210
+ Number.isFinite(options.staleHours) && options.staleHours >= 0 ? options.staleHours : 24
1211
+ const now = typeof options.now === 'number' ? options.now : Date.now()
1212
+ // #1580: gate freshness on the SAME overlay set evaluate() uses. A report check the engine would
1213
+ // score NA (its applies_if overlay is UNMET — capability off / cross-language audit) is not a
1214
+ // freshness concern: counting its absent report toward STALE/PARTIAL conflates "the tool never ran"
1215
+ // with "the capability does not apply" — the exact distinction freshness exists to make. Without a
1216
+ // gate, --check-fresh (a fail-closed exit-1 gate) hard-fails demanding reports the engine never scores.
1217
+ const overlaySet =
1218
+ options.overlays instanceof Set ? options.overlays : new Set(options.overlays || [])
1219
+ const checks = Array.isArray(registry?.checks) ? registry.checks : []
1220
+ const reports = []
1221
+ for (const c of checks) {
1222
+ if (!c || typeof c !== 'object') continue
1223
+ if (c.type !== 'value') continue
1224
+ const args = c.args || {}
1225
+ // Only a value check with a report `format` reads a pre-generated tool report; a legacy value
1226
+ // check (args.equals, no format) reads a tracked source file and is not a freshness concern.
1227
+ if (!args.format) continue
1228
+ // Skip a report check whose applies_if overlay is UNMET — the engine NA's it, so its report is
1229
+ // neither expected nor counted (isApplicable's fail-safe: a malformed precondition ⇒ APPLIES).
1230
+ if (!isApplicable(c, overlaySet, root)) continue
1231
+ const p = typeof args.path === 'string' ? args.path : null
1232
+ if (p === null) continue
1233
+ const abs = safeResolve(root, p)
1234
+ let present = false
1235
+ let ageHours = null
1236
+ if (abs !== null) {
1237
+ try {
1238
+ const st = statSync(abs)
1239
+ if (st.isFile()) {
1240
+ present = true
1241
+ ageHours = Math.max(0, (now - st.mtimeMs) / (3600 * 1000))
1242
+ }
1243
+ } catch {
1244
+ present = false
1245
+ }
1246
+ }
1247
+ const fresh = present && ageHours !== null && ageHours <= staleHours
1248
+ reports.push({ path: p, present, ageHours, fresh })
1249
+ }
1250
+ // Stable banner order (code-unit), deterministic across runs.
1251
+ reports.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0))
1252
+ const total = reports.length
1253
+ const present = reports.filter((r) => r.present).length
1254
+ const fresh = reports.filter((r) => r.fresh).length
1255
+ let status
1256
+ if (total === 0 || fresh === total) status = 'FRESH'
1257
+ else if (present === 0) status = 'STALE'
1258
+ else status = 'PARTIAL'
1259
+ return { status, staleHours, counts: { total, present, fresh }, reports }
1260
+ }