@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,1746 @@
1
+ #!/usr/bin/env node
2
+ // <%= projectName %> quality gate
3
+ // Usage: node scripts/check-all.mjs [L1|L2] [--json [path]]
4
+ // L1: format + lint + unit tests (fast, pre-commit)
5
+ // L2: L1 + coverage + audit (full, pre-push)
6
+ // --json [path]: emit gate result JSON (schema arbiter-gate-v1) to path
7
+ import { createHash } from 'node:crypto';
8
+ import { spawnSync } from 'node:child_process';
9
+ import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
10
+ import { dirname, join, resolve } from 'node:path';
11
+ import { fileURLToPath } from 'node:url';
12
+ // Helper trinity (#351, CANON-01) — runCheck (HARD), runWarnCheck (info),
13
+ // runToolCheck (CI-aware tool gate). pushResult/getResults/getFailed power the
14
+ // inline ad-hoc gates that classify status outside of spawnSync.
15
+ import {
16
+ runCheck,
17
+ runWarnCheck,
18
+ runToolCheck,
19
+ pushResult,
20
+ getResults,
21
+ getFailed,
22
+ setMode,
23
+ setOrphanGuard,
24
+ resolveTmpfsTmpdir,
25
+ gateFileState,
26
+ } from './lib/run-helpers.mjs';
27
+ import { GATE_MUTEX_HELD_ENV, gateLockPathFor } from './lib/gate-mutex.mjs';
28
+
29
+ // ─── TMPDIR on tmpfs (dominant wall-clock lever for fsync-bound suites) ──────
30
+ // Set BEFORE any child spawns so every one of them inherits it; see
31
+ // resolveTmpfsTmpdir for the measurement and why the guard is free space, not
32
+ // existence. An explicit TMPDIR always wins — note it also partitions Go's test
33
+ // cache (TMPDIR is hashed into the test-input ID), so anyone running tests by
34
+ // hand should export the same value the gate picks or they warm a second,
35
+ // separate cache.
36
+ if (!process.env.TMPDIR) {
37
+ const _tmpfs = resolveTmpfsTmpdir();
38
+ if (_tmpfs) process.env.TMPDIR = _tmpfs;
39
+ }
40
+ <% const packageManager = locals.packageManager ?? 'npm'; if (language === 'typescript' && enableDebtGates && coverageEnabled) { %>
41
+ // #1319.8 — greenfield-aware coverage decision (PASS on 0 executable statements;
42
+ // enforce threshold otherwise; FAIL on missing/malformed summary).
43
+ import { evaluateCoverageGate } from './lib/coverage-gate.mjs';
44
+ <% } %>
45
+
46
+ const IS_CI = process.env.CI === 'true' || process.env.GITHUB_ACTIONS === 'true';
47
+ <% if (language === 'typescript' && enableDebtGates && coverageEnabled) { %>
48
+ let _coverageRunStartedAt = null;
49
+ <% } %>
50
+
51
+ // Guard an arbiter-emitted gate artifact using the emission-time manifest rather
52
+ // than filesystem existence alone. A missing optional artifact that was never
53
+ // emitted stays a normal skip; a delivered guard later deleted is a gate failure.
54
+ function gateFilePresent(_path, _label, _neverEmittedLine = null, _alternatePaths = []) {
55
+ if (_alternatePaths.some((_alternatePath) => existsSync(_alternatePath))) return true;
56
+ const _state = gateFileState(_path);
57
+ if (_state === 'present') return true;
58
+ if (_state === 'never-emitted') {
59
+ if (_neverEmittedLine) console.log(_neverEmittedLine);
60
+ return false;
61
+ }
62
+ if (_state === 'deleted') {
63
+ // Keep the prerequisite in the same inspection contract as runCheck: a
64
+ // single-check rerun ignores another check's guard, and a dry-run reports
65
+ // what a normal gate would do without changing its result to FAIL.
66
+ if (only !== null && only !== _label) return false;
67
+ if (dryRun) {
68
+ console.log(
69
+ `[CHECK] ${_label} ... DRY-RUN (would FAIL — ${_path} was emitted by arbiter and is now missing)`,
70
+ );
71
+ return false;
72
+ }
73
+ console.error(
74
+ `[CHECK] ${_label} ... FAIL — ${_path} was emitted by arbiter and is now missing; run arbiter update or restore it from git.`,
75
+ );
76
+ pushResult(_label, 'FAIL', 0);
77
+ return false;
78
+ }
79
+ console.error(
80
+ `[CHECK] ${_label} ... DEGRADED — cannot determine whether ${_path} was emitted because .arbiter-generated-manifest.json is unavailable or invalid; run arbiter update or restore the manifest.`,
81
+ );
82
+ return false;
83
+ }
84
+
85
+ // >>> ARG-PARSE-START (#1504) — robust, fail-loud gate-arg parsing.
86
+ // Accept EVERY invocation form arbiter emits: the positional level
87
+ // (`check-all.mjs L2`), the subcommand aliases the Makefile + git hooks use
88
+ // (`check`/`gate`/`full`/`simulate-nightly`/`simulate-weekly`), the flag form
89
+ // (`--level L2` / `--level=L2`), and `--json [path]` — in any order. Plus the
90
+ // #2078 inspection flags: `--dry-run` (print which checks WOULD run at this level,
91
+ // spawn nothing) and `--gate <name>` (re-run one check by its display name).
92
+ // A garbage or missing level FAILS LOUD (exit 2). It must NEVER silently degrade
93
+ // to a weaker level than asked: a positional parser that reads the literal
94
+ // `--level` as the level string skips the L2 branch while the job stays green —
95
+ // that silent L2→L1 downgrade is the exact fake-green this gate exists to kill.
96
+ const _LEVELS = ['L1', 'L2', 'L3', 'L4'];
97
+ // Subcommand alias → level. `check` is the fast pre-commit lane; everything
98
+ // heavier maps to L2 (the strongest tier this generated gate implements).
99
+ const _SUBCOMMAND_LEVEL = {
100
+ check: 'L1',
101
+ gate: 'L2',
102
+ full: 'L2',
103
+ 'simulate-nightly': 'L2',
104
+ 'simulate-weekly': 'L2',
105
+ };
106
+ let level = 'L2';
107
+ let jsonPath = null;
108
+ let dryRun = false; // #2078: print-what-would-run, spawn nothing
109
+ let only = null; // #2078: --gate <name>, re-run a single check
110
+ const _rawArgs = process.argv.slice(2);
111
+ const _gateUsage =
112
+ 'Usage: node scripts/check-all.mjs [L1|L2|L3|L4 | check|gate|full|simulate-nightly|simulate-weekly] [--level <L1|L2|L3|L4>] [--json [path]] [--dry-run] [--gate <name>]';
113
+ function _gateFatal(_msg) {
114
+ console.error(`[GATE] FATAL: ${_msg}`);
115
+ console.error(`[GATE] ${_gateUsage}`);
116
+ process.exit(2);
117
+ }
118
+ for (let _i = 0; _i < _rawArgs.length; _i++) {
119
+ const _a = _rawArgs[_i];
120
+ if (_a === '--json') {
121
+ jsonPath = _i + 1 < _rawArgs.length && !_rawArgs[_i + 1].startsWith('-') ? _rawArgs[++_i] : '';
122
+ } else if (_a === '--level') {
123
+ const _val = _rawArgs[_i + 1];
124
+ if (!_LEVELS.includes(_val))
125
+ _gateFatal(`--level requires one of ${_LEVELS.join('|')}, got "${_val ?? '(missing)'}"`);
126
+ level = _val;
127
+ _i++;
128
+ } else if (_a.startsWith('--level=')) {
129
+ const _val = _a.slice('--level='.length);
130
+ if (!_LEVELS.includes(_val))
131
+ _gateFatal(`--level requires one of ${_LEVELS.join('|')}, got "${_val}"`);
132
+ level = _val;
133
+ } else if (_a === '--dry-run') {
134
+ dryRun = true;
135
+ } else if (_a === '--gate') {
136
+ const _g = _rawArgs[_i + 1];
137
+ if (!_g || _g.startsWith('-')) _gateFatal('--gate requires a check name');
138
+ only = _g;
139
+ _i++;
140
+ } else if (_a.startsWith('--gate=')) {
141
+ const _g = _a.slice('--gate='.length);
142
+ if (!_g) _gateFatal('--gate= requires a check name');
143
+ only = _g;
144
+ } else if (_LEVELS.includes(_a)) {
145
+ level = _a;
146
+ } else if (Object.prototype.hasOwnProperty.call(_SUBCOMMAND_LEVEL, _a)) {
147
+ level = _SUBCOMMAND_LEVEL[_a];
148
+ } else {
149
+ _gateFatal(`unrecognized argument "${_a}"`);
150
+ }
151
+ }
152
+ // <<< ARG-PARSE-END (#1504)
153
+
154
+ // ─── one gate per repo, and never an orphan (#2427) ─────────────────────────
155
+ // Two gates in one checkout trample each other's temp files and starve each
156
+ // other's test workers, and the orphan of a killed `git push` once ran to
157
+ // completion and stamped a green marker for a tree it had never finished
158
+ // testing. Both are fixed here.
159
+ //
160
+ // The mutex is the per-repo flock `arbiter gate-exec` takes, keyed off
161
+ // ARBITER_HOOK_GIT_CWD when set — the pre-push '#'-in-path branch runs this file
162
+ // from an rsync'd copy under /tmp, and keying off cwd there would mint a fresh
163
+ // key per push, i.e. a null mutex. A synchronous gate cannot hold a kernel lock
164
+ // across its own lifetime, so it re-execs itself once under the wrapper; the
165
+ // wrapper publishes ARBITER_GATE_MUTEX_HELD so this branch runs exactly once.
166
+ //
167
+ // FAIL-OPEN-INTENT (narrow): a checkout git cannot answer for has no per-repo
168
+ // mutex to take at all. The start/end evidence binding below is what actually
169
+ // prevents a false green there.
170
+ const _mutexRoot = process.env.ARBITER_HOOK_GIT_CWD ?? process.cwd();
171
+ {
172
+ let _lockPath = null;
173
+ try {
174
+ _lockPath = gateLockPathFor(_mutexRoot);
175
+ } catch { _lockPath = null; }
176
+ // #2427: compare the held lock by EXACT PATH, never by mere presence. Only
177
+ // THIS repo's lock path may skip the relay — the env var exists to stop a
178
+ // process re-acquiring the flock it already owns. Any other non-empty value
179
+ // (a stale export, or another repo's lock) would otherwise skip the mutex
180
+ // and run the gate unserialised, silently. gate-mutex.mjs compares by
181
+ // equality; these two must agree.
182
+ const _alreadyHeld = _lockPath !== null && process.env[GATE_MUTEX_HELD_ENV] === _lockPath;
183
+ if (_lockPath !== null && !_alreadyHeld) {
184
+ const _wrapper = resolve(dirname(fileURLToPath(import.meta.url)), 'lib/gate-mutex.mjs');
185
+ const _relayed = spawnSync(
186
+ process.execPath,
187
+ [_wrapper, 'run', '--dir', _mutexRoot, '--', process.execPath, ...process.argv.slice(1)],
188
+ { stdio: 'inherit' },
189
+ );
190
+ process.exit(_relayed.status ?? 1);
191
+ }
192
+ }
193
+
194
+ // Between every two checks, confirm the process this gate was launched to serve
195
+ // is still alive. SIGKILL cannot be trapped or forwarded, so this is the only
196
+ // thing that stops a gate whose parent was killed by pid.
197
+ setOrphanGuard();
198
+
199
+ // #2427 AC-1: the identity of the tree this gate is about to measure, sampled
200
+ // BEFORE the first check. buildGateEvidence re-measures at the end and refuses
201
+ // to stamp anything if the two disagree.
202
+ const _gateStart = await (async () => {
203
+ try {
204
+ const { captureGateStart } = await import('./lib/gate-evidence.mjs');
205
+ return captureGateStart(_mutexRoot);
206
+ } catch { return null; }
207
+ })();
208
+ <%// #1127/#1330: FE-signal helpers used by the registry's generation-time emitIf
209
+ // expressions — declared BEFORE the effective-gate set (which evals them).
210
+ const _isSubtreeFE = typeof archetype !== 'undefined' && archetype !== 'frontend-spa'
211
+ && Array.isArray(lanes) && lanes.includes('frontend')
212
+ const _isFE = archetype === 'frontend-spa' || (Array.isArray(lanes) && lanes.includes('frontend'))
213
+ // #2257: whether this project HAS GitHub workflows on disk — the predicate the
214
+ // 12 workflow gates (ci-tiers, action-pins, workflow-perms/runners/docs-sync/
215
+ // test-integrity/sha-pinning/job-naming, secret-presence, continue-on-error,
216
+ // pr-size-gate, merge-method-ff-only) must follow. They used to key off bare
217
+ // `useGitHub`, which is NOT that: resolveProjectConfig sets it from the LIVE-API
218
+ // flag (`--github`), so a routine `arbiter update` resolved it false and STRIPPED
219
+ // all 12 from a project whose .github/workflows and guard scripts stayed on disk
220
+ // — REDding the project's own `unwired guards` gate on a day-2 no-op command.
221
+ // `permitGitHub` is the persisted capability, and is already the predicate
222
+ // registry.ts uses to EMIT those workflows and guards; the `?? useGitHub` tail
223
+ // (the same idiom registry.ts/ci-tier.ts/anti-drift-validators.ts use) covers the
224
+ // init path, which builds a ProjectConfig carrying only `useGitHub`. `typeof`
225
+ // because that init-path ProjectConfig leaves `permitGitHub` undefined and a bare
226
+ // reference would ReferenceError the generator for every non-GitHub project.
227
+ const _hasGitHubWorkflows = typeof permitGitHub !== 'undefined'
228
+ ? permitGitHub === true : useGitHub === true
229
+ const _feFramework = (typeof frontend !== 'undefined' && frontend && frontend.framework)
230
+ ? frontend.framework : 'react'
231
+ const _feGlob = _feFramework === 'vue'
232
+ ? 'src/**/*.{ts,vue}'
233
+ : _feFramework === 'svelte'
234
+ ? 'src/**/*.{ts,svelte}'
235
+ : 'src/**/*.{ts,tsx,jsx}'
236
+ %>
237
+ // ─── Embedded gate registry (#2041, AC-2041.4) ────────────────────────────────
238
+ // The declarative registry rendered from gate-registry.yml.ejs — consumed by
239
+ // `--dry-run` (manifest), `--gate <id|name>` (single-check re-run), and the
240
+ // emitted layering contract test (scripts/test-gate-layering.mjs). ORDER IS
241
+ // MEANINGFUL: gates run in registry order within each level.
242
+ <%
243
+ // The EFFECTIVE gate set — generation-time emitIf/language conditions resolved
244
+ // once here, then embedded below and iterated by the lanes. The embedded
245
+ // registry carries only the gates that will actually RUN (or render an
246
+ // explicit SKIP), so the --dry-run manifest, the layering contract test, and
247
+ // --gate <id> all see the same set the lanes execute. Conditions
248
+ // (gateFilePresent/existsSync) are runtime and stay runtime `if` wrappers in
249
+ // the emitted script. emitIf expressions are trusted repo-owned template data
250
+ // resolved at GENERATION time (never emitted into the consumer script), so
251
+ // eval here is safe — a typo'd emitIf key fails the generator LOUD
252
+ // (ReferenceError), never a silent gate drop.
253
+ //
254
+ // A gate carrying a GENERATION-time `else` (npm-ci drift — body-vs-else decided
255
+ // at render) is NEVER filtered: its emitIf decides body-vs-else in the inline
256
+ // branch (mirroring the pre-#2041 `if (cond) { body } else { SKIP }` emission),
257
+ // so a pnpm project still gets the SKIP-report line. A gate with a RUNTIME else
258
+ // (condition + else, e.g. the contract env-gates) is filtered by emitIf like any
259
+ // other gate — otherwise a Maven project's manifest would carry the ./gradlew
260
+ // Pact gate whose runtime else only fires when the gate is actually selected.
261
+ const _effectiveGates = gates.filter((g) => {
262
+ const _generationElse = g.else !== undefined && g.condition === undefined;
263
+ if (g.emitIf !== undefined && !_generationElse && !eval('(' + g.emitIf + ')')) return false;
264
+ if (g.language && g.language !== language) return false;
265
+ return true;
266
+ });
267
+ // #9003 — render-time set of gate ids that are the BASE of some promotion
268
+ // (i.e. some other gate declares `promotes_to: <this id>`), and the reverse
269
+ // map (base id -> promoting gate id) used for the SKIP-reason message below.
270
+ // Only gates in this set (and audit-tagged gates) get the runtime skip-wrap
271
+ // below — every other gate's emission is byte-identical to pre-#9003 output.
272
+ const _promotionBases = new Set(_effectiveGates.filter((g) => typeof g.promotes_to === 'string').map((g) => g.promotes_to));
273
+ const _promotedByMap = new Map(_effectiveGates.filter((g) => typeof g.promotes_to === 'string').map((g) => [g.promotes_to, g.id]));
274
+ const _hasAudit = _effectiveGates.some((g) => g.audit === true);
275
+ // #9003 — the `_promotedAway`/`_auditGatesOff` runtime machinery below is
276
+ // ONLY emitted when at least one gate in THIS consumer's registry opts in —
277
+ // a consumer whose gates never declare promotes_to/audit gets byte-identical
278
+ // output to pre-#9003 (no dead consts, no snapshot churn on `arbiter update`).
279
+ const _hasSkipMachinery = _promotionBases.size > 0 || _hasAudit;
280
+ %>
281
+ const GATE_REGISTRY = <%- JSON.stringify(_effectiveGates) %>;
282
+ <%_ if (_hasSkipMachinery) { _%>
283
+
284
+ // #9003 — promoted-gate suppression: a gate whose id is the `promotes_to`
285
+ // target of some OTHER gate is skipped once that promoting gate's own level
286
+ // is reached (same L1⊆L2⊆L3 ordinal comparison the --dry-run manifest uses
287
+ // above — e.g. an L3 `harness-full` gate declaring
288
+ // `promotes_to: harness-fast` suppresses `harness-fast` once L3 is reached).
289
+ const _promotedAway = new Set();
290
+ for (const _pg of GATE_REGISTRY) {
291
+ if (_pg.promotes_to && _LEVELS.indexOf(_pg.level) <= _LEVELS.indexOf(level)) {
292
+ _promotedAway.add(_pg.promotes_to);
293
+ }
294
+ }
295
+ // #9003 — audit-mode gating: a gate tagged `audit: true` is a second axis
296
+ // orthogonal to level. Default is ON (gates run); set
297
+ // ARBITER_AUDIT_MODE=off to skip them.
298
+ // Boolean-shaped (registered in env-registry.ts as type: 'boolean') — accept
299
+ // the same off-forms a boolean env read accepts (mirrors parseBooleanEnv;
300
+ // duplicated inline since this file ships to consumers with no arbiter dep).
301
+ const _auditGatesOff = ['off', 'false', '0', 'no'].includes(
302
+ (process.env.ARBITER_AUDIT_MODE ?? '').trim().toLowerCase(),
303
+ );
304
+ <%_ } _%>
305
+
306
+ // ─── #2078 (GATE-1 of #2041) — inspection modes, re-based on the registry.
307
+ // `--dry-run` prints the registry manifest without executing (exit 0, zero
308
+ // executions); `--gate <id|name>` re-runs a single check with timing. Both are
309
+ // diagnostic: they must NEVER stamp a gate-pass marker / result JSON (guarded
310
+ // at the write sites below), so a dry-run or single-check rerun cannot fake a
311
+ // green gate for the fail-closed Stop hook. `setMode` threads the mode into the
312
+ // runCheck/runWarnCheck/runToolCheck trio; a normal run leaves it a no-op.
313
+ if (dryRun) {
314
+ console.log('[DRY-RUN] gate manifest (registry):');
315
+ // #2257: the lanes are structurally contained (L1 ⊂ L2 ⊂ L3 — see the L3 lane
316
+ // below, which runs only AFTER the L1 and L2 bodies), so the manifest must use
317
+ // the same containment. The old `_g.level === level` equality omitted EVERY L2
318
+ // gate from `--dry-run L3`, i.e. the diagnostic that exists to answer "what
319
+ // does this lane run" under-reported the L3 lane by ~40 gates.
320
+ for (const _g of GATE_REGISTRY) {
321
+ if (_LEVELS.indexOf(_g.level) <= _LEVELS.indexOf(level)) {
322
+ <%_ if (_hasSkipMachinery) { _%>
323
+ // #9003: the manifest must reflect the SAME runtime skip a real run
324
+ // would apply — a promoted-away or audit-off gate is not silently
325
+ // listed as if it would execute (the #2257 "diagnostic that lies" class).
326
+ const _dryPromo = _promotedAway.has(_g.id) ? ' [SKIP: promoted]' : '';
327
+ const _dryAudit = (_g.audit === true && _auditGatesOff) ? ' [SKIP: ARBITER_AUDIT_MODE=off]' : '';
328
+ console.log(` ${_g.level} ${_g.id} ${_g.name}${_dryPromo}${_dryAudit}`);
329
+ <%_ } else { _%>
330
+ console.log(` ${_g.level} ${_g.id} ${_g.name}`);
331
+ <%_ } _%>
332
+ }
333
+ }
334
+ }
335
+ if (only !== null) {
336
+ // --gate accepts a registry id (re-based on the registry) OR the display name
337
+ // (#2078 backward compatibility — the trio matches by name).
338
+ const _regGate = GATE_REGISTRY.find((_g) => _g.id === only);
339
+ if (_regGate) only = _regGate.name;
340
+ }
341
+ const _inspect = dryRun || only !== null;
342
+ setMode({ dryRun, only });
343
+
344
+ // #2041 (AC-2041.5/6): inline gate bodies bypass the runCheck trio, so they must
345
+ // honour the inspection modes themselves — --dry-run prints and spawns NOTHING
346
+ // (the #2078 contract: "print-what-would-run, spawn nothing"; a direct spawn
347
+ // would also hang an offline consumer), and --gate <id|name> filters to the
348
+ // named gate. Returns true when the caller must skip.
349
+ function _inlineInspect(_name, _wouldRun) {
350
+ if (dryRun) {
351
+ console.log(`[CHECK] ${_name} ... DRY-RUN (would run: ${_wouldRun})`);
352
+ pushResult(_name, 'SKIP', 0);
353
+ return true;
354
+ }
355
+ if (only !== null && only !== _name) return true;
356
+ return false;
357
+ }
358
+
359
+ // ─── Grace Period Guard (ADR-028) ─────────────────────────────────────────────
360
+ // A freshly-upgraded L1→L2 project may run its new L2 gates WARN-only for a
361
+ // bounded settling window (set ONLY by `arbiter upgrade-level`, never by hand).
362
+ //
363
+ // Three guards close the L2 fake-green vector this mechanism could otherwise open:
364
+ // 1. CURRENT-LEVEL binding — grace is honored only while THIS project is at L2
365
+ // (the level baked into this script at generation time). Once it climbs to
366
+ // L3/L4, `arbiter update` re-renders this script at the higher level and the
367
+ // guard below evaluates false, so a stale L1→L2 graceEndsAt can no longer
368
+ // silence the (now higher) gate. A grace from L1 only ever softens L2.
369
+ // 2. BOUNDED window — graceEndsAt is honored only if it is at most
370
+ // GRACE_MAX_DAYS ahead of now. A hand-edited far-future date (the classic
371
+ // "neuter the gate forever" edit) exceeds the bound and is IGNORED, with a
372
+ // loud warning — it cannot make the gate advisory.
373
+ // 3. HARD checks stay hard — PII/secret early-fail (above) and the anti-fake-green
374
+ // aggregate are never softened by grace.
375
+ const GRACE_MAX_DAYS = 35; // upgrade-level default is 30; +5 slack for one --extend
376
+ const _projectLevel = '<%= governanceLevel %>';
377
+ let graceActive = false;
378
+ const _arbiterJsonPath = join(process.cwd(), 'arbiter.json');
379
+ if (existsSync(_arbiterJsonPath)) {
380
+ try {
381
+ const _cfg = JSON.parse(readFileSync(_arbiterJsonPath, 'utf-8'));
382
+ const _ends = _cfg.graceEndsAt ? Date.parse(_cfg.graceEndsAt) : NaN;
383
+ const _now = Date.now();
384
+ // Grace from L1 is only meaningful while the project is itself at L2.
385
+ const _levelOk = _cfg.graceFromLevel === 'L1' && _projectLevel === 'L2';
386
+ if (_levelOk && Number.isFinite(_ends) && _ends > _now) {
387
+ const _maxEnds = _now + GRACE_MAX_DAYS * 86400000;
388
+ if (_ends > _maxEnds) {
389
+ const _endsDate = _cfg.graceEndsAt.slice(0, 10);
390
+ console.log(`[GRACE] IGNORED — graceEndsAt (${_endsDate}) exceeds the ${GRACE_MAX_DAYS}-day bound; treating L2 gates as HARD. Re-run \`arbiter upgrade-level --extend\` instead of hand-editing arbiter.json.`);
391
+ } else {
392
+ graceActive = true;
393
+ const _daysLeft = Math.ceil((_ends - _now) / 86400000);
394
+ const _endsDate = _cfg.graceEndsAt.slice(0, 10);
395
+ console.log(`[GRACE] Grace period ends in ${_daysLeft} day(s) (${_endsDate}). L2 gates WARN-only until then.`);
396
+ }
397
+ } else if (_cfg.graceFromLevel === 'L1' && _projectLevel !== 'L2' && Number.isFinite(_ends) && _ends > _now) {
398
+ // Stale grace carried into a higher level — never soften the higher gate.
399
+ console.log(`[GRACE] IGNORED — graceFromLevel=L1 but project is now ${_projectLevel}; L2 grace does not apply to a ${_projectLevel} gate.`);
400
+ }
401
+ } catch { /* ignore parse errors — arbiter.json absent or malformed */ }
402
+ }
403
+
404
+ console.log('');
405
+ console.log(`=== <%= projectName %> Quality Gate: ${level} ===`);
406
+ console.log('');
407
+
408
+ <%
409
+ // #1720 (gap 4) RESOLVED by #2041 (AC-2041.1): L3/L4 are now executable LOCAL
410
+ // lanes — the L3 registry set runs below (solo-reactivation + nightly audits,
411
+ // mirroring 06-nightly-lite.yml). The pre-#2041 clamp is gone: an explicit
412
+ // L3/L4 request runs the full L1+L2+L3 body, never a silent downgrade.
413
+ const ratchetFlag = isL3Plus ? '--require-improvement' : '--gate';
414
+ %>
415
+
416
+ // ─── Gate execution (registry-driven, #2041) ─────────────────────────────────
417
+ // Each registry entry renders as a level-conditioned call: L1 gates always run,
418
+ // L2 gates run at L2+, L3 gates run at L3+ (the local nightly lane). Language
419
+ // and data conditions (emitIf) are resolved at GENERATION time; runtime guards
420
+ // (gateFilePresent/existsSync) remain runtime `if` wrappers. Inline gates keep
421
+ // their custom JS bodies (declared in the registry so --dry-run/--gate and the
422
+ // layering test see them). ORDER IS THE REGISTRY ORDER.
423
+
424
+ <% const l1 = _effectiveGates.filter(g => g.level === 'L1'); const l2 = _effectiveGates.filter(g => g.level === 'L2'); const l3 = _effectiveGates.filter(g => g.level === 'L3'); %>
425
+ // ── L1 (fast checks — pre-commit) ────────────────────────────────────────
426
+ if (true) {
427
+ <% for (const g of l1) { %>
428
+ <% if (g.emitIf !== undefined && !eval('(' + g.emitIf + ')')) continue; %>
429
+ <% if (g.language && g.language !== language) continue; %>
430
+ <%_ const _isPromoted = _promotionBases.has(g.id); const _isAudit = g.audit === true; const _promoterId = _isPromoted ? _promotedByMap.get(g.id) : null; const _skipCond = []; if (_isPromoted) _skipCond.push("_promotedAway.has('" + g.id + "')"); if (_isAudit) _skipCond.push('_auditGatesOff'); _%>
431
+ <%_ if (_skipCond.length) { _%>
432
+ if (<%- _skipCond.join(' || ') %>) {
433
+ <%_ if (_isPromoted && _isAudit) { _%>
434
+ console.log('[CHECK] <%= g.name %> ... SKIP (' + (_promotedAway.has('<%= g.id %>') ? 'promoted by <%- _promoterId %>' : 'ARBITER_AUDIT_MODE=off') + ')');
435
+ <%_ } else if (_isPromoted) { _%>
436
+ console.log('[CHECK] <%= g.name %> ... SKIP (promoted by <%- _promoterId %>)');
437
+ <%_ } else { _%>
438
+ console.log('[CHECK] <%= g.name %> ... SKIP (ARBITER_AUDIT_MODE=off)');
439
+ <%_ } _%>
440
+ pushResult('<%= g.name %>', 'SKIP', 0);
441
+ } else {
442
+ <%_ } _%>
443
+ <% if (g.kind === 'inline') { %>
444
+ <% if(g.id === 'npm-ci-drift') { %>
445
+ <% if (g.emitIf !== undefined && !eval('(' + g.emitIf + ')')) { %><%- g.else %><% } else { %>
446
+ if (_inlineInspect('npm-ci drift', 'npx -y npm@<pin> ci --dry-run')) {} else {
447
+ const _driftStart = Date.now();
448
+ process.stdout.write('[CHECK] npm-ci drift ... ');
449
+ let _driftStatus = 'PASS';
450
+ try {
451
+ const _pin = existsSync('package.json')
452
+ ? (JSON.parse(readFileSync('package.json', 'utf-8')).packageManager || '').match(/^npm@(\d+\.\d+\.\d+)/)
453
+ : null;
454
+ if (!_pin || !existsSync('package-lock.json')) {
455
+ console.log('SKIP (no packageManager npm pin or lockfile)');
456
+ } else {
457
+ const _r = spawnSync('npx', ['-y', 'npm@' + _pin[1], 'ci', '--dry-run'], { encoding: 'utf-8', shell: false });
458
+ if (_r.error) {
459
+ console.log('SKIP (pinned npm@' + _pin[1] + ' unavailable)');
460
+ } else if (_r.status === 0) {
461
+ console.log('PASS');
462
+ } else {
463
+ console.log('FAIL');
464
+ process.stdout.write('package-lock.json is out of sync under npm@' + _pin[1] + ' — relock: npx -y npm@' + _pin[1] + ' install --package-lock-only\n');
465
+ _driftStatus = 'FAIL';
466
+ }
467
+ }
468
+ } catch (_e) {
469
+ console.log('SKIP (' + (_e && _e.message ? _e.message : 'unreadable package.json') + ')');
470
+ }
471
+ pushResult('npm-ci drift', _driftStatus, Date.now() - _driftStart);
472
+ }
473
+ <% } %>
474
+ <% } else if(g.id === 'workflow-runners-inline') { %>
475
+ {
476
+ const _wrStart = Date.now();
477
+ process.stdout.write('[CHECK] workflow runners ... ');
478
+ const _wrWorkflowsDir = join(process.cwd(), '.github', 'workflows');
479
+ // Allowlisted runs-on forms (INV-89, ADR-023 amendment #959 — ubuntu-latest default):
480
+ // 1. ${{ vars.CI_BUILD_RUNNER_LABEL || 'ubuntu-latest' }} — global override (auxiliary jobs)
481
+ // 2. ${{ fromJSON(vars.RUNNER_LABELS_BUILD || '["ubuntu-latest"]') }} — build job-class pool
482
+ // 3. ${{ fromJSON(vars.RUNNER_LABELS_TEST || '["ubuntu-latest"]') }} — test job-class pool
483
+ // 4. ${{ fromJSON(vars.RUNNER_LABELS_DEPLOY || '["ubuntu-latest"]') }} — deploy job-class pool
484
+ // 5. ${{ matrix.os }} — explicit OS matrix, always allowed
485
+ // The per-job-class map lets a project point build/test/deploy at distinct runner
486
+ // pools (self-hosted / cloud / GitHub-hosted) via org vars without editing workflows;
487
+ // each var falls back to ubuntu-latest, so an unset var is never a broken runner.
488
+ const _wrAllowedPattern = /\$\{\{\s*(?:vars\.CI_BUILD_RUNNER_LABEL|fromJSON\(vars\.RUNNER_LABELS_(?:BUILD|TEST|DEPLOY)|matrix\.os)/;
489
+ const _wrRunsOnPattern = /^\s*runs-on:/;
490
+ let _wrViolations = 0;
491
+ let _wrFiles;
492
+ try {
493
+ _wrFiles = readdirSync(_wrWorkflowsDir).filter((f) => f.endsWith('.yml') || f.endsWith('.yaml'));
494
+ } catch (err) {
495
+ if (err.code !== 'ENOENT') {
496
+ console.log(`FAIL (cannot read .github/workflows — ${err.message})`);
497
+ pushResult('workflow runners', 'FAIL', Date.now() - _wrStart);
498
+ _wrFiles = null;
499
+ } else {
500
+ _wrFiles = [];
501
+ }
502
+ }
503
+ if (_wrFiles !== null) {
504
+ for (const _wrFile of _wrFiles) {
505
+ let _wrContent;
506
+ try {
507
+ _wrContent = readFileSync(join(_wrWorkflowsDir, _wrFile), 'utf-8');
508
+ } catch (err) { console.log(`FAIL (cannot read workflow file: ${_wrFile} — ${err.message})`); _wrViolations++; continue; }
509
+ const _wrLines = _wrContent.split('\n');
510
+ for (let _wi = 0; _wi < _wrLines.length; _wi++) {
511
+ if (_wrRunsOnPattern.test(_wrLines[_wi]) && !_wrAllowedPattern.test(_wrLines[_wi])) {
512
+ _wrViolations++;
513
+ }
514
+ }
515
+ }
516
+ if (_wrViolations > 0) {
517
+ console.log(`FAIL (${_wrViolations} violation(s) — use \${{ vars.CI_BUILD_RUNNER_LABEL || 'ubuntu-latest' }})`);
518
+ pushResult('workflow runners', 'FAIL', Date.now() - _wrStart);
519
+ } else {
520
+ console.log(`PASS (${Date.now() - _wrStart}ms)`);
521
+ pushResult('workflow runners', 'PASS', Date.now() - _wrStart);
522
+ }
523
+ }
524
+ }
525
+ <% } else if(g.id === 'ci-alignment') { %>
526
+ {
527
+ const _caStart = Date.now();
528
+ process.stdout.write('[CHECK] ci alignment ... ');
529
+ const _caManifestPath = join(process.cwd(), 'scripts', 'check-all.mjs');
530
+ const _caCiPath = join(process.cwd(), '.github', 'workflows', 'ci.yml');
531
+ const _caInfraPrefixes = ['npm ci', 'npm install', 'git fetch', 'git checkout', 'curl ', 'echo ', 'mkdir ', 'cp ', 'tar ', 'pip install', 'apt-get'];
532
+ const _caDesignExemptions = new Set(['npx:commitlint', 'npm:test']);
533
+ function _caIsInfra(cmd) { return _caInfraPrefixes.some((p) => cmd.startsWith(p)); }
534
+ function _caNormalizeKey(cmd, firstArg) {
535
+ switch (cmd) {
536
+ case 'node': return (firstArg && firstArg.startsWith('scripts/')) ? firstArg : null;
537
+ case 'npx': { if (!firstArg) return null; const tool = firstArg.replace(/@.+$/, ''); return 'npx:' + tool; }
538
+ case 'npm': if (firstArg === 'test') return 'npm:test'; if (firstArg === 'audit') return 'npm:audit'; return null;
539
+ default: return null;
540
+ }
541
+ }
542
+ function _caExtractManifestGates(text) {
543
+ const gates = new Set();
544
+ const re = /runCheck\(\s*"[^"]*"\s*,\s*"([^"]+)"\s*,\s*\[([^\]]*)\]/g;
545
+ let m;
546
+ while ((m = re.exec(text)) !== null) {
547
+ const k = _caNormalizeKey(m[1].trim(), (m[2].match(/"([^"]+)"/) || [])[1] || '');
548
+ if (k !== null) gates.add(k);
549
+ }
550
+ return gates;
551
+ }
552
+ function _caParseRun(cmd, gates) {
553
+ if (!cmd || _caIsInfra(cmd)) return;
554
+ const toks = cmd.split(/\s+/);
555
+ const c0 = toks[0]; const a0 = toks[1] ?? '';
556
+ const k = (c0 === 'node' && a0) ? _caNormalizeKey('node', a0) : _caNormalizeKey(c0, a0);
557
+ if (k !== null) gates.add(k);
558
+ }
559
+ function _caExtractCiGates(ciPath) {
560
+ const gates = new Set();
561
+ let ciText;
562
+ try { ciText = readFileSync(ciPath, 'utf-8'); } catch (e) { if (e.code === 'ENOENT') return gates; throw e; }
563
+ const ciLines = ciText.split('\n');
564
+ let _ci = 0;
565
+ while (_ci < ciLines.length) {
566
+ const cl = ciLines[_ci];
567
+ if (/^\s*-?\s*uses:\s*.+/.test(cl)) { _ci++; continue; }
568
+ const blk = cl.match(/^(\s*)-?\s*run:\s*[|>]\s*$/);
569
+ if (blk) {
570
+ const bi = blk[1].length; _ci++;
571
+ while (_ci < ciLines.length) {
572
+ const bl = ciLines[_ci];
573
+ if (bl.trim() === '') { _ci++; continue; }
574
+ if ((bl.match(/^(\s*)/)?.[1].length ?? 0) <= bi && bl.trim() !== '') break;
575
+ _caParseRun(bl.trim(), gates); _ci++;
576
+ }
577
+ continue;
578
+ }
579
+ const sl = cl.match(/^\s*-?\s*run:\s*(.+)/);
580
+ if (sl) { _caParseRun(sl[1].trim(), gates); }
581
+ _ci++;
582
+ }
583
+ return gates;
584
+ }
585
+ let _caStatus = 'PASS';
586
+ try {
587
+ const _caManifestText = readFileSync(_caManifestPath, 'utf-8');
588
+ const _caManifestGates = _caExtractManifestGates(_caManifestText);
589
+ const _caCiGates = _caExtractCiGates(_caCiPath);
590
+ for (const k of _caDesignExemptions) { _caManifestGates.delete(k); _caCiGates.delete(k); }
591
+ const _caManifestOnly = [..._caManifestGates].filter((k) => !_caCiGates.has(k));
592
+ const _caCiOnly = [..._caCiGates].filter((k) => !_caManifestGates.has(k));
593
+ if (_caManifestOnly.length > 0 || _caCiOnly.length > 0) {
594
+ console.log(`FAIL (${_caManifestOnly.length + _caCiOnly.length} alignment violation(s))`);
595
+ for (const k of _caManifestOnly) process.stderr.write(' manifest-only: ' + k + '\n');
596
+ for (const k of _caCiOnly) process.stderr.write(' ci-only: ' + k + '\n');
597
+ _caStatus = 'FAIL';
598
+ } else {
599
+ console.log(`PASS (${_caManifestGates.size} gates aligned, ${Date.now() - _caStart}ms)`);
600
+ }
601
+ } catch (_caErr) {
602
+ console.log('FAIL (error: ' + _caErr.message + ')');
603
+ _caStatus = 'FAIL';
604
+ }
605
+ pushResult('ci alignment', _caStatus, Date.now() - _caStart);
606
+ }
607
+ <% } else if(g.id === 'playwright-rules') { %>
608
+ if (existsSync(join('node_modules', 'eslint-plugin-playwright'))) {
609
+ // --no-error-on-unmatched-pattern: the glob is source-co-located-spec-oriented;
610
+ // a project whose specs live only under tests/e2e/ (arbiter's own scaffold
611
+ // convention) legitimately matches zero files here — that must stay a PASS,
612
+ // not a Day-1 RED (#1491-class fix).
613
+ runCheck('playwright rules', 'npx', ['eslint', '--config', 'eslint.config.playwright.mjs', '--no-config-lookup', '--no-error-on-unmatched-pattern', 'src/**/*.spec.ts']);
614
+ } else {
615
+ console.log('[CHECK] playwright rules ... SKIP (run: npm install -D eslint-plugin-playwright)');
616
+ }
617
+ <% } else if(g.id === 'playwright-e2e') { %>
618
+ // ─── L2: Playwright E2E (#348, CANON-02) — matrix proven for TS frontend-spa / backend-web-db.
619
+ // Wrapped via scripts/lib/ephemeral-server.mjs (#358): the runner starts the
620
+ // server, polls TCP readiness, runs Playwright, then tears the server down.
621
+ // runToolCheck SKIPs locally when the binary is missing, FAILs in CI.
622
+ const _e2eStartCommand = process.env.E2E_START_CMD?.trim();
623
+ let _hasStartTestScript = false;
624
+ try {
625
+ const _e2ePackage = JSON.parse(readFileSync('package.json', 'utf-8'));
626
+ _hasStartTestScript = typeof _e2ePackage.scripts?.['start:test'] === 'string'
627
+ && _e2ePackage.scripts['start:test'].trim().length > 0;
628
+ } catch {
629
+ // No package.json means arbiter cannot supply an application start command.
630
+ }
631
+ if (!_e2eStartCommand && !_hasStartTestScript) {
632
+ console.log('[CHECK] playwright e2e ... SKIP (E2E_START_CMD unset and package.json has no start:test script)');
633
+ pushResult('playwright e2e', 'SKIP', 0);
634
+ } else {
635
+ runToolCheck(
636
+ 'playwright e2e',
637
+ 'node',
638
+ [
639
+ 'scripts/lib/ephemeral-server.mjs',
640
+ '--start', _e2eStartCommand || 'npm run start:test',
641
+ '--test', 'npx playwright test',
642
+ '--port', process.env.E2E_PORT || '4173',
643
+ ],
644
+ { soft: graceActive },
645
+ );
646
+ }
647
+ <% } else if(g.id === 'pytest-playwright-e2e') { %>
648
+ // ─── L2: pytest-playwright E2E (#348, CANON-02) — matrix proven for Python web archetypes.
649
+ // Wrapped via scripts/lib/ephemeral-server.mjs (#358) so the server is up
650
+ // before pytest collects the e2e tests. runToolCheck SKIPs locally when
651
+ // pytest is missing, FAILs in CI.
652
+ runToolCheck(
653
+ 'pytest-playwright e2e',
654
+ 'node',
655
+ [
656
+ 'scripts/lib/ephemeral-server.mjs',
657
+ '--start', process.env.E2E_START_CMD || 'python -m uvicorn app.main:app --port 8000',
658
+ '--test', 'pytest tests/e2e',
659
+ '--port', process.env.E2E_PORT || '8000',
660
+ ],
661
+ { soft: graceActive },
662
+ );
663
+ <% } else if(g.id === 'npm-audit') { %>
664
+ <% if (enableSecurityScanning && packageManager === 'npm') { %>
665
+ runCheck('audit', 'npm', ['audit', '--omit=dev', '--audit-level=high'], { soft: graceActive });
666
+ <% } else if (enableSecurityScanning) { %>
667
+ console.log('[CHECK] audit ... SKIP (project uses <%= packageManager %>)');
668
+ pushResult('audit', 'SKIP', 0);
669
+ <% } %>
670
+ <% } else if(g.id === 'coverage-threshold') { %>
671
+ // ─── L2: coverage threshold (greenfield-aware, #1319.8) ─────────────────────
672
+ // Run vitest with the json-summary reporter so we can inspect the real coverage
673
+ // totals at runtime. A virgin project has zero executable statements ⇒ the
674
+ // greenfield guard PASSES instead of false-failing the line threshold. A coverage
675
+ // run that crashed (no summary) FAILS — never silently skipped.
676
+ if (_inlineInspect('coverage threshold', 'npx vitest run --coverage --coverage.reporter=json-summary')) {} else {
677
+ const _covStart = Date.now();
678
+ _coverageRunStartedAt = _covStart;
679
+ process.stdout.write('[CHECK] coverage threshold ... ');
680
+ const _covThreshold = <%= coverageThreshold %>;
681
+ const _cov = spawnSync(
682
+ 'npx',
683
+ ['vitest', 'run', '--coverage', '--coverage.reporter=json-summary'],
684
+ { encoding: 'utf-8', shell: false },
685
+ );
686
+ let _covStatus = 'PASS';
687
+ let _covReason = '';
688
+ let _covSummary = null;
689
+ const _covSummaryPath = resolve(process.cwd(), 'coverage', 'coverage-summary.json');
690
+ if (existsSync(_covSummaryPath)) {
691
+ try {
692
+ _covSummary = JSON.parse(readFileSync(_covSummaryPath, 'utf-8'));
693
+ } catch {
694
+ _covSummary = null; // malformed JSON ⇒ evaluateCoverageGate fails closed
695
+ }
696
+ }
697
+ const _covResult = evaluateCoverageGate(_covSummary, _covThreshold);
698
+ _covStatus = _covResult.status;
699
+ _covReason = _covResult.reason;
700
+ if (_covStatus === 'FAIL' && graceActive) {
701
+ console.log(`WARN (grace period — ${_covReason})`);
702
+ _covStatus = 'PASS';
703
+ } else if (_covStatus === 'FAIL') {
704
+ console.log(`FAIL (${_covReason})`);
705
+ } else {
706
+ console.log(`PASS (${_covReason})`);
707
+ }
708
+ pushResult('coverage threshold', _covStatus, Date.now() - _covStart);
709
+ }
710
+ <% } else if(g.id === 'bdd-ignore-check') { %>
711
+ // ─── L2: BDD gate (INV-40) ──────────────────────────────────────────────────
712
+ if (_inlineInspect('BDD @ignore check', 'grep -rql --include=*.feature @ignore .')) {} else {
713
+ // @ignore-tagged scenarios are HARD-fail (soft: false per INV-40 — never graced)
714
+ const _bddIgnoreStart = Date.now();
715
+ const _bddIgnore = spawnSync('grep', ['-rql', '--include=*.feature', '@ignore', '.'], { encoding: 'utf-8', shell: false });
716
+ process.stdout.write('[CHECK] BDD @ignore check ... ');
717
+ let _bddIgnoreStatus = 'PASS';
718
+ if (_bddIgnore.error?.code === 'ENOENT') {
719
+ console.log('FAIL (grep not found — cannot check @ignore tags)');
720
+ _bddIgnoreStatus = 'FAIL';
721
+ } else if (_bddIgnore.status === null || _bddIgnore.status === 2) {
722
+ console.log(`FAIL (grep error — exit ${_bddIgnore.status ?? 'signal'}: ${_bddIgnore.stderr ?? ''})`);
723
+ _bddIgnoreStatus = 'FAIL';
724
+ } else if (_bddIgnore.status === 0) {
725
+ console.log('FAIL (@ignore-tagged scenarios found — remove tags or move to issue tracker)');
726
+ _bddIgnoreStatus = 'FAIL';
727
+ } else {
728
+ console.log('PASS');
729
+ }
730
+ pushResult('BDD @ignore check', _bddIgnoreStatus, Date.now() - _bddIgnoreStart);
731
+ }
732
+ <% } else if(g.id === 'bdd-go') { %>
733
+ // direct parallel of Python's `pytest.importorskip("pytest_bdd")`.
734
+ {
735
+ const _goBddStart = Date.now();
736
+ const _goMod = existsSync('go.mod') ? readFileSync('go.mod', 'utf-8') : '';
737
+ if (_goMod.includes('github.com/cucumber/godog')) {
738
+ runCheck('bdd', 'go', ['test', '-tags', 'bdd', './internal/bdd/...'], { soft: graceActive });
739
+ } else {
740
+ console.log('[CHECK] bdd ... SKIP (github.com/cucumber/godog not in go.mod — run: go get github.com/cucumber/godog)');
741
+ pushResult('bdd', 'SKIP', Date.now() - _goBddStart);
742
+ }
743
+ }
744
+ <% } else if(g.id === 'bdd-rust') { %>
745
+ // graceful-skip contract as the Go/Python BDD gates above.
746
+ {
747
+ const _rustBddStart = Date.now();
748
+ const _cargoToml = existsSync('Cargo.toml') ? readFileSync('Cargo.toml', 'utf-8') : '';
749
+ if (/^\s*bdd\s*=/m.test(_cargoToml) || _cargoToml.includes('cucumber')) {
750
+ runCheck('bdd', 'cargo', ['test', '--features', 'bdd'], { soft: graceActive });
751
+ } else {
752
+ console.log('[CHECK] bdd ... SKIP (no `bdd` feature / cucumber dep in Cargo.toml — see tests/example_bdd_test.rs)');
753
+ pushResult('bdd', 'SKIP', Date.now() - _rustBddStart);
754
+ }
755
+ }
756
+ <% } else if(g.id === 'evidence-gate') { %>
757
+ // ─── L3/L4: Evidence gate (INV-33) ───────────────────────────────────────────
758
+ // Validates nightly evidence produced by evidence-collect.mjs.
759
+ // Warns when absent (first run); fails when present but obs_gate !== 'PASS'.
760
+ const _evidenceStart = Date.now();
761
+ let _evidenceStatus = 'PASS';
762
+ process.stdout.write('[CHECK] evidence gate (INV-33) ... ');
763
+ {
764
+ const _summaryPath = join(process.cwd(), '.evidence', 'SUMMARY.json');
765
+ if (!existsSync(_summaryPath)) {
766
+ console.log('WARN — .evidence/SUMMARY.json absent (run nightly pipeline first)');
767
+ } else {
768
+ try {
769
+ const _summary = JSON.parse(readFileSync(_summaryPath, 'utf-8'));
770
+ if (_summary.obs_gate === 'PASS') {
771
+ console.log('PASS');
772
+ } else {
773
+ console.log('FAIL (obs_gate=' + _summary.obs_gate + ')');
774
+ _evidenceStatus = 'FAIL';
775
+ }
776
+ } catch {
777
+ console.log('FAIL (could not parse .evidence/SUMMARY.json)');
778
+ _evidenceStatus = 'FAIL';
779
+ }
780
+ }
781
+ }
782
+ pushResult('evidence gate (INV-33)', _evidenceStatus, Date.now() - _evidenceStart);
783
+ <% } else if(g.id === 'gofmt') { %>
784
+ // #157: gofmt -l — FAIL on any unformatted file (Go ships no formatter in CI).
785
+ if (_inlineInspect('fmt (gofmt)', 'gofmt -l .')) {} else {
786
+ const _gofmtStart = Date.now();
787
+ process.stdout.write('[CHECK] fmt (gofmt) ... ');
788
+ const _gofmt = spawnSync('gofmt', ['-l', '.'], { encoding: 'utf-8', shell: false });
789
+ let _gofmtStatus = 'PASS';
790
+ if (_gofmt.error?.code === 'ENOENT') {
791
+ console.log('FAIL (binary not found: gofmt)');
792
+ _gofmtStatus = 'FAIL';
793
+ } else if (_gofmt.stdout?.trim()) {
794
+ console.log('FAIL');
795
+ process.stdout.write('Files need gofmt formatting:\n' + _gofmt.stdout);
796
+ _gofmtStatus = 'FAIL';
797
+ } else {
798
+ console.log('PASS');
799
+ }
800
+ pushResult('fmt (gofmt)', _gofmtStatus, Date.now() - _gofmtStart);
801
+ }
802
+ <% } %>
803
+ <% } else { %>
804
+ <% if (g.condition) { %>if (<%- g.condition %>) {<% } %>
805
+ <% const _fn = g.kind === 'warn' ? 'runWarnCheck' : g.kind === 'tool' ? 'runToolCheck' : 'runCheck'; %>
806
+ <%= _fn %>('<%= g.name %>', '<%= g.cmd[0] %>', [<% if (g.cmd.length > 1) { %>'<%- g.cmd.slice(1).join("', '") %>'<% } %>]<%= g.soft ? ', { soft: graceActive }' : '' %>);
807
+ <% if (g.else) { %>} else {
808
+ <%- g.else %>
809
+ }<% } else if (g.condition) { %>}<% } %>
810
+ <% } %>
811
+ <%_ if (_skipCond.length) { _%>
812
+ }
813
+ <%_ } _%>
814
+ <% } %>
815
+ }
816
+ // ── L2 (full checks — pre-push) ──────────────────────────────────────────
817
+ if (level !== 'L1') {
818
+ <% for (const g of l2) { %>
819
+ <% if (g.emitIf !== undefined && !eval('(' + g.emitIf + ')')) continue; %>
820
+ <% if (g.language && g.language !== language) continue; %>
821
+ <%_ const _isPromoted = _promotionBases.has(g.id); const _isAudit = g.audit === true; const _promoterId = _isPromoted ? _promotedByMap.get(g.id) : null; const _skipCond = []; if (_isPromoted) _skipCond.push("_promotedAway.has('" + g.id + "')"); if (_isAudit) _skipCond.push('_auditGatesOff'); _%>
822
+ <%_ if (_skipCond.length) { _%>
823
+ if (<%- _skipCond.join(' || ') %>) {
824
+ <%_ if (_isPromoted && _isAudit) { _%>
825
+ console.log('[CHECK] <%= g.name %> ... SKIP (' + (_promotedAway.has('<%= g.id %>') ? 'promoted by <%- _promoterId %>' : 'ARBITER_AUDIT_MODE=off') + ')');
826
+ <%_ } else if (_isPromoted) { _%>
827
+ console.log('[CHECK] <%= g.name %> ... SKIP (promoted by <%- _promoterId %>)');
828
+ <%_ } else { _%>
829
+ console.log('[CHECK] <%= g.name %> ... SKIP (ARBITER_AUDIT_MODE=off)');
830
+ <%_ } _%>
831
+ pushResult('<%= g.name %>', 'SKIP', 0);
832
+ } else {
833
+ <%_ } _%>
834
+ <% if (g.kind === 'inline') { %>
835
+ <% if(g.id === 'npm-ci-drift') { %>
836
+ <% if (g.emitIf !== undefined && !eval('(' + g.emitIf + ')')) { %><%- g.else %><% } else { %>
837
+ if (_inlineInspect('npm-ci drift', 'npx -y npm@<pin> ci --dry-run')) {} else {
838
+ const _driftStart = Date.now();
839
+ process.stdout.write('[CHECK] npm-ci drift ... ');
840
+ let _driftStatus = 'PASS';
841
+ try {
842
+ const _pin = existsSync('package.json')
843
+ ? (JSON.parse(readFileSync('package.json', 'utf-8')).packageManager || '').match(/^npm@(\d+\.\d+\.\d+)/)
844
+ : null;
845
+ if (!_pin || !existsSync('package-lock.json')) {
846
+ console.log('SKIP (no packageManager npm pin or lockfile)');
847
+ } else {
848
+ const _r = spawnSync('npx', ['-y', 'npm@' + _pin[1], 'ci', '--dry-run'], { encoding: 'utf-8', shell: false });
849
+ if (_r.error) {
850
+ console.log('SKIP (pinned npm@' + _pin[1] + ' unavailable)');
851
+ } else if (_r.status === 0) {
852
+ console.log('PASS');
853
+ } else {
854
+ console.log('FAIL');
855
+ process.stdout.write('package-lock.json is out of sync under npm@' + _pin[1] + ' — relock: npx -y npm@' + _pin[1] + ' install --package-lock-only\n');
856
+ _driftStatus = 'FAIL';
857
+ }
858
+ }
859
+ } catch (_e) {
860
+ console.log('SKIP (' + (_e && _e.message ? _e.message : 'unreadable package.json') + ')');
861
+ }
862
+ pushResult('npm-ci drift', _driftStatus, Date.now() - _driftStart);
863
+ }
864
+ <% } %>
865
+ <% } else if(g.id === 'workflow-runners-inline') { %>
866
+ {
867
+ const _wrStart = Date.now();
868
+ process.stdout.write('[CHECK] workflow runners ... ');
869
+ const _wrWorkflowsDir = join(process.cwd(), '.github', 'workflows');
870
+ // Allowlisted runs-on forms (INV-89, ADR-023 amendment #959 — ubuntu-latest default):
871
+ // 1. ${{ vars.CI_BUILD_RUNNER_LABEL || 'ubuntu-latest' }} — global override (auxiliary jobs)
872
+ // 2. ${{ fromJSON(vars.RUNNER_LABELS_BUILD || '["ubuntu-latest"]') }} — build job-class pool
873
+ // 3. ${{ fromJSON(vars.RUNNER_LABELS_TEST || '["ubuntu-latest"]') }} — test job-class pool
874
+ // 4. ${{ fromJSON(vars.RUNNER_LABELS_DEPLOY || '["ubuntu-latest"]') }} — deploy job-class pool
875
+ // 5. ${{ matrix.os }} — explicit OS matrix, always allowed
876
+ // The per-job-class map lets a project point build/test/deploy at distinct runner
877
+ // pools (self-hosted / cloud / GitHub-hosted) via org vars without editing workflows;
878
+ // each var falls back to ubuntu-latest, so an unset var is never a broken runner.
879
+ const _wrAllowedPattern = /\$\{\{\s*(?:vars\.CI_BUILD_RUNNER_LABEL|fromJSON\(vars\.RUNNER_LABELS_(?:BUILD|TEST|DEPLOY)|matrix\.os)/;
880
+ const _wrRunsOnPattern = /^\s*runs-on:/;
881
+ let _wrViolations = 0;
882
+ let _wrFiles;
883
+ try {
884
+ _wrFiles = readdirSync(_wrWorkflowsDir).filter((f) => f.endsWith('.yml') || f.endsWith('.yaml'));
885
+ } catch (err) {
886
+ if (err.code !== 'ENOENT') {
887
+ console.log(`FAIL (cannot read .github/workflows — ${err.message})`);
888
+ pushResult('workflow runners', 'FAIL', Date.now() - _wrStart);
889
+ _wrFiles = null;
890
+ } else {
891
+ _wrFiles = [];
892
+ }
893
+ }
894
+ if (_wrFiles !== null) {
895
+ for (const _wrFile of _wrFiles) {
896
+ let _wrContent;
897
+ try {
898
+ _wrContent = readFileSync(join(_wrWorkflowsDir, _wrFile), 'utf-8');
899
+ } catch (err) { console.log(`FAIL (cannot read workflow file: ${_wrFile} — ${err.message})`); _wrViolations++; continue; }
900
+ const _wrLines = _wrContent.split('\n');
901
+ for (let _wi = 0; _wi < _wrLines.length; _wi++) {
902
+ if (_wrRunsOnPattern.test(_wrLines[_wi]) && !_wrAllowedPattern.test(_wrLines[_wi])) {
903
+ _wrViolations++;
904
+ }
905
+ }
906
+ }
907
+ if (_wrViolations > 0) {
908
+ console.log(`FAIL (${_wrViolations} violation(s) — use \${{ vars.CI_BUILD_RUNNER_LABEL || 'ubuntu-latest' }})`);
909
+ pushResult('workflow runners', 'FAIL', Date.now() - _wrStart);
910
+ } else {
911
+ console.log(`PASS (${Date.now() - _wrStart}ms)`);
912
+ pushResult('workflow runners', 'PASS', Date.now() - _wrStart);
913
+ }
914
+ }
915
+ }
916
+ <% } else if(g.id === 'ci-alignment') { %>
917
+ {
918
+ const _caStart = Date.now();
919
+ process.stdout.write('[CHECK] ci alignment ... ');
920
+ const _caManifestPath = join(process.cwd(), 'scripts', 'check-all.mjs');
921
+ const _caCiPath = join(process.cwd(), '.github', 'workflows', 'ci.yml');
922
+ const _caInfraPrefixes = ['npm ci', 'npm install', 'git fetch', 'git checkout', 'curl ', 'echo ', 'mkdir ', 'cp ', 'tar ', 'pip install', 'apt-get'];
923
+ const _caDesignExemptions = new Set(['npx:commitlint', 'npm:test']);
924
+ function _caIsInfra(cmd) { return _caInfraPrefixes.some((p) => cmd.startsWith(p)); }
925
+ function _caNormalizeKey(cmd, firstArg) {
926
+ switch (cmd) {
927
+ case 'node': return (firstArg && firstArg.startsWith('scripts/')) ? firstArg : null;
928
+ case 'npx': { if (!firstArg) return null; const tool = firstArg.replace(/@.+$/, ''); return 'npx:' + tool; }
929
+ case 'npm': if (firstArg === 'test') return 'npm:test'; if (firstArg === 'audit') return 'npm:audit'; return null;
930
+ default: return null;
931
+ }
932
+ }
933
+ function _caExtractManifestGates(text) {
934
+ const gates = new Set();
935
+ const re = /runCheck\(\s*"[^"]*"\s*,\s*"([^"]+)"\s*,\s*\[([^\]]*)\]/g;
936
+ let m;
937
+ while ((m = re.exec(text)) !== null) {
938
+ const k = _caNormalizeKey(m[1].trim(), (m[2].match(/"([^"]+)"/) || [])[1] || '');
939
+ if (k !== null) gates.add(k);
940
+ }
941
+ return gates;
942
+ }
943
+ function _caParseRun(cmd, gates) {
944
+ if (!cmd || _caIsInfra(cmd)) return;
945
+ const toks = cmd.split(/\s+/);
946
+ const c0 = toks[0]; const a0 = toks[1] ?? '';
947
+ const k = (c0 === 'node' && a0) ? _caNormalizeKey('node', a0) : _caNormalizeKey(c0, a0);
948
+ if (k !== null) gates.add(k);
949
+ }
950
+ function _caExtractCiGates(ciPath) {
951
+ const gates = new Set();
952
+ let ciText;
953
+ try { ciText = readFileSync(ciPath, 'utf-8'); } catch (e) { if (e.code === 'ENOENT') return gates; throw e; }
954
+ const ciLines = ciText.split('\n');
955
+ let _ci = 0;
956
+ while (_ci < ciLines.length) {
957
+ const cl = ciLines[_ci];
958
+ if (/^\s*-?\s*uses:\s*.+/.test(cl)) { _ci++; continue; }
959
+ const blk = cl.match(/^(\s*)-?\s*run:\s*[|>]\s*$/);
960
+ if (blk) {
961
+ const bi = blk[1].length; _ci++;
962
+ while (_ci < ciLines.length) {
963
+ const bl = ciLines[_ci];
964
+ if (bl.trim() === '') { _ci++; continue; }
965
+ if ((bl.match(/^(\s*)/)?.[1].length ?? 0) <= bi && bl.trim() !== '') break;
966
+ _caParseRun(bl.trim(), gates); _ci++;
967
+ }
968
+ continue;
969
+ }
970
+ const sl = cl.match(/^\s*-?\s*run:\s*(.+)/);
971
+ if (sl) { _caParseRun(sl[1].trim(), gates); }
972
+ _ci++;
973
+ }
974
+ return gates;
975
+ }
976
+ let _caStatus = 'PASS';
977
+ try {
978
+ const _caManifestText = readFileSync(_caManifestPath, 'utf-8');
979
+ const _caManifestGates = _caExtractManifestGates(_caManifestText);
980
+ const _caCiGates = _caExtractCiGates(_caCiPath);
981
+ for (const k of _caDesignExemptions) { _caManifestGates.delete(k); _caCiGates.delete(k); }
982
+ const _caManifestOnly = [..._caManifestGates].filter((k) => !_caCiGates.has(k));
983
+ const _caCiOnly = [..._caCiGates].filter((k) => !_caManifestGates.has(k));
984
+ if (_caManifestOnly.length > 0 || _caCiOnly.length > 0) {
985
+ console.log(`FAIL (${_caManifestOnly.length + _caCiOnly.length} alignment violation(s))`);
986
+ for (const k of _caManifestOnly) process.stderr.write(' manifest-only: ' + k + '\n');
987
+ for (const k of _caCiOnly) process.stderr.write(' ci-only: ' + k + '\n');
988
+ _caStatus = 'FAIL';
989
+ } else {
990
+ console.log(`PASS (${_caManifestGates.size} gates aligned, ${Date.now() - _caStart}ms)`);
991
+ }
992
+ } catch (_caErr) {
993
+ console.log('FAIL (error: ' + _caErr.message + ')');
994
+ _caStatus = 'FAIL';
995
+ }
996
+ pushResult('ci alignment', _caStatus, Date.now() - _caStart);
997
+ }
998
+ <% } else if(g.id === 'playwright-rules') { %>
999
+ if (existsSync(join('node_modules', 'eslint-plugin-playwright'))) {
1000
+ // --no-error-on-unmatched-pattern: the glob is source-co-located-spec-oriented;
1001
+ // a project whose specs live only under tests/e2e/ (arbiter's own scaffold
1002
+ // convention) legitimately matches zero files here — that must stay a PASS,
1003
+ // not a Day-1 RED (#1491-class fix).
1004
+ runCheck('playwright rules', 'npx', ['eslint', '--config', 'eslint.config.playwright.mjs', '--no-config-lookup', '--no-error-on-unmatched-pattern', 'src/**/*.spec.ts']);
1005
+ } else {
1006
+ console.log('[CHECK] playwright rules ... SKIP (run: npm install -D eslint-plugin-playwright)');
1007
+ }
1008
+ <% } else if(g.id === 'playwright-e2e') { %>
1009
+ // ─── L2: Playwright E2E (#348, CANON-02) — matrix proven for TS frontend-spa / backend-web-db.
1010
+ // Wrapped via scripts/lib/ephemeral-server.mjs (#358): the runner starts the
1011
+ // server, polls TCP readiness, runs Playwright, then tears the server down.
1012
+ // runToolCheck SKIPs locally when the binary is missing, FAILs in CI.
1013
+ const _e2eStartCommand = process.env.E2E_START_CMD?.trim();
1014
+ let _hasStartTestScript = false;
1015
+ try {
1016
+ const _e2ePackage = JSON.parse(readFileSync('package.json', 'utf-8'));
1017
+ _hasStartTestScript = typeof _e2ePackage.scripts?.['start:test'] === 'string'
1018
+ && _e2ePackage.scripts['start:test'].trim().length > 0;
1019
+ } catch {
1020
+ // No package.json means arbiter cannot supply an application start command.
1021
+ }
1022
+ if (!_e2eStartCommand && !_hasStartTestScript) {
1023
+ console.log('[CHECK] playwright e2e ... SKIP (E2E_START_CMD unset and package.json has no start:test script)');
1024
+ pushResult('playwright e2e', 'SKIP', 0);
1025
+ } else {
1026
+ runToolCheck(
1027
+ 'playwright e2e',
1028
+ 'node',
1029
+ [
1030
+ 'scripts/lib/ephemeral-server.mjs',
1031
+ '--start', _e2eStartCommand || 'npm run start:test',
1032
+ '--test', 'npx playwright test',
1033
+ '--port', process.env.E2E_PORT || '4173',
1034
+ ],
1035
+ { soft: graceActive },
1036
+ );
1037
+ }
1038
+ <% } else if(g.id === 'pytest-playwright-e2e') { %>
1039
+ // ─── L2: pytest-playwright E2E (#348, CANON-02) — matrix proven for Python web archetypes.
1040
+ // Wrapped via scripts/lib/ephemeral-server.mjs (#358) so the server is up
1041
+ // before pytest collects the e2e tests. runToolCheck SKIPs locally when
1042
+ // pytest is missing, FAILs in CI.
1043
+ runToolCheck(
1044
+ 'pytest-playwright e2e',
1045
+ 'node',
1046
+ [
1047
+ 'scripts/lib/ephemeral-server.mjs',
1048
+ '--start', process.env.E2E_START_CMD || 'python -m uvicorn app.main:app --port 8000',
1049
+ '--test', 'pytest tests/e2e',
1050
+ '--port', process.env.E2E_PORT || '8000',
1051
+ ],
1052
+ { soft: graceActive },
1053
+ );
1054
+ <% } else if(g.id === 'npm-audit') { %>
1055
+ <% if (enableSecurityScanning && packageManager === 'npm') { %>
1056
+ runCheck('audit', 'npm', ['audit', '--omit=dev', '--audit-level=high'], { soft: graceActive });
1057
+ <% } else if (enableSecurityScanning) { %>
1058
+ console.log('[CHECK] audit ... SKIP (project uses <%= packageManager %>)');
1059
+ pushResult('audit', 'SKIP', 0);
1060
+ <% } %>
1061
+ <% } else if(g.id === 'coverage-threshold') { %>
1062
+ // ─── L2: coverage threshold (greenfield-aware, #1319.8) ─────────────────────
1063
+ // Run vitest with the json-summary reporter so we can inspect the real coverage
1064
+ // totals at runtime. A virgin project has zero executable statements ⇒ the
1065
+ // greenfield guard PASSES instead of false-failing the line threshold. A coverage
1066
+ // run that crashed (no summary) FAILS — never silently skipped.
1067
+ if (_inlineInspect('coverage threshold', 'npx vitest run --coverage --coverage.reporter=json-summary')) {} else {
1068
+ const _covStart = Date.now();
1069
+ _coverageRunStartedAt = _covStart;
1070
+ process.stdout.write('[CHECK] coverage threshold ... ');
1071
+ const _covThreshold = <%= coverageThreshold %>;
1072
+ const _cov = spawnSync(
1073
+ 'npx',
1074
+ ['vitest', 'run', '--coverage', '--coverage.reporter=json-summary'],
1075
+ { encoding: 'utf-8', shell: false },
1076
+ );
1077
+ let _covStatus = 'PASS';
1078
+ let _covReason = '';
1079
+ let _covSummary = null;
1080
+ const _covSummaryPath = resolve(process.cwd(), 'coverage', 'coverage-summary.json');
1081
+ if (existsSync(_covSummaryPath)) {
1082
+ try {
1083
+ _covSummary = JSON.parse(readFileSync(_covSummaryPath, 'utf-8'));
1084
+ } catch {
1085
+ _covSummary = null; // malformed JSON ⇒ evaluateCoverageGate fails closed
1086
+ }
1087
+ }
1088
+ const _covResult = evaluateCoverageGate(_covSummary, _covThreshold);
1089
+ _covStatus = _covResult.status;
1090
+ _covReason = _covResult.reason;
1091
+ if (_covStatus === 'FAIL' && graceActive) {
1092
+ console.log(`WARN (grace period — ${_covReason})`);
1093
+ _covStatus = 'PASS';
1094
+ } else if (_covStatus === 'FAIL') {
1095
+ console.log(`FAIL (${_covReason})`);
1096
+ } else {
1097
+ console.log(`PASS (${_covReason})`);
1098
+ }
1099
+ pushResult('coverage threshold', _covStatus, Date.now() - _covStart);
1100
+ }
1101
+ <% } else if(g.id === 'bdd-ignore-check') { %>
1102
+ // ─── L2: BDD gate (INV-40) ──────────────────────────────────────────────────
1103
+ if (_inlineInspect('BDD @ignore check', 'grep -rql --include=*.feature @ignore .')) {} else {
1104
+ // @ignore-tagged scenarios are HARD-fail (soft: false per INV-40 — never graced)
1105
+ const _bddIgnoreStart = Date.now();
1106
+ const _bddIgnore = spawnSync('grep', ['-rql', '--include=*.feature', '@ignore', '.'], { encoding: 'utf-8', shell: false });
1107
+ process.stdout.write('[CHECK] BDD @ignore check ... ');
1108
+ let _bddIgnoreStatus = 'PASS';
1109
+ if (_bddIgnore.error?.code === 'ENOENT') {
1110
+ console.log('FAIL (grep not found — cannot check @ignore tags)');
1111
+ _bddIgnoreStatus = 'FAIL';
1112
+ } else if (_bddIgnore.status === null || _bddIgnore.status === 2) {
1113
+ console.log(`FAIL (grep error — exit ${_bddIgnore.status ?? 'signal'}: ${_bddIgnore.stderr ?? ''})`);
1114
+ _bddIgnoreStatus = 'FAIL';
1115
+ } else if (_bddIgnore.status === 0) {
1116
+ console.log('FAIL (@ignore-tagged scenarios found — remove tags or move to issue tracker)');
1117
+ _bddIgnoreStatus = 'FAIL';
1118
+ } else {
1119
+ console.log('PASS');
1120
+ }
1121
+ pushResult('BDD @ignore check', _bddIgnoreStatus, Date.now() - _bddIgnoreStart);
1122
+ }
1123
+ <% } else if(g.id === 'bdd-go') { %>
1124
+ // direct parallel of Python's `pytest.importorskip("pytest_bdd")`.
1125
+ {
1126
+ const _goBddStart = Date.now();
1127
+ const _goMod = existsSync('go.mod') ? readFileSync('go.mod', 'utf-8') : '';
1128
+ if (_goMod.includes('github.com/cucumber/godog')) {
1129
+ runCheck('bdd', 'go', ['test', '-tags', 'bdd', './internal/bdd/...'], { soft: graceActive });
1130
+ } else {
1131
+ console.log('[CHECK] bdd ... SKIP (github.com/cucumber/godog not in go.mod — run: go get github.com/cucumber/godog)');
1132
+ pushResult('bdd', 'SKIP', Date.now() - _goBddStart);
1133
+ }
1134
+ }
1135
+ <% } else if(g.id === 'bdd-rust') { %>
1136
+ // graceful-skip contract as the Go/Python BDD gates above.
1137
+ {
1138
+ const _rustBddStart = Date.now();
1139
+ const _cargoToml = existsSync('Cargo.toml') ? readFileSync('Cargo.toml', 'utf-8') : '';
1140
+ if (/^\s*bdd\s*=/m.test(_cargoToml) || _cargoToml.includes('cucumber')) {
1141
+ runCheck('bdd', 'cargo', ['test', '--features', 'bdd'], { soft: graceActive });
1142
+ } else {
1143
+ console.log('[CHECK] bdd ... SKIP (no `bdd` feature / cucumber dep in Cargo.toml — see tests/example_bdd_test.rs)');
1144
+ pushResult('bdd', 'SKIP', Date.now() - _rustBddStart);
1145
+ }
1146
+ }
1147
+ <% } else if(g.id === 'evidence-gate') { %>
1148
+ // ─── L3/L4: Evidence gate (INV-33) ───────────────────────────────────────────
1149
+ // Validates nightly evidence produced by evidence-collect.mjs.
1150
+ // Warns when absent (first run); fails when present but obs_gate !== 'PASS'.
1151
+ const _evidenceStart = Date.now();
1152
+ let _evidenceStatus = 'PASS';
1153
+ process.stdout.write('[CHECK] evidence gate (INV-33) ... ');
1154
+ {
1155
+ const _summaryPath = join(process.cwd(), '.evidence', 'SUMMARY.json');
1156
+ if (!existsSync(_summaryPath)) {
1157
+ console.log('WARN — .evidence/SUMMARY.json absent (run nightly pipeline first)');
1158
+ } else {
1159
+ try {
1160
+ const _summary = JSON.parse(readFileSync(_summaryPath, 'utf-8'));
1161
+ if (_summary.obs_gate === 'PASS') {
1162
+ console.log('PASS');
1163
+ } else {
1164
+ console.log('FAIL (obs_gate=' + _summary.obs_gate + ')');
1165
+ _evidenceStatus = 'FAIL';
1166
+ }
1167
+ } catch {
1168
+ console.log('FAIL (could not parse .evidence/SUMMARY.json)');
1169
+ _evidenceStatus = 'FAIL';
1170
+ }
1171
+ }
1172
+ }
1173
+ pushResult('evidence gate (INV-33)', _evidenceStatus, Date.now() - _evidenceStart);
1174
+ <% } else if(g.id === 'go-coverage') { %>
1175
+ // #2104: Go keeps the default covermode (set) — a lost counter increment under
1176
+ // `set` can only under-report, and the threshold is a floor (see the registry).
1177
+ if (_inlineInspect('coverage profile', 'go test -coverprofile=coverage.out ./...')) {} else {
1178
+ runCheck('coverage profile', 'go', ['test', '-coverprofile=coverage.out', './...'], { soft: graceActive });
1179
+ const _coverThreshStart = Date.now();
1180
+ process.stdout.write('[CHECK] coverage threshold ... ');
1181
+ const coverResult = spawnSync('go', ['tool', 'cover', '-func=coverage.out'], { encoding: 'utf-8', shell: false });
1182
+ let _coverThreshStatus = 'PASS';
1183
+ if (coverResult.status !== 0) {
1184
+ console.log(graceActive ? 'WARN (grace period)' : 'FAIL (could not parse coverage)');
1185
+ if (!graceActive) { _coverThreshStatus = 'FAIL'; }
1186
+ } else {
1187
+ const match = coverResult.stdout.match(/total:\s+\(statements\)\s+([\d.]+)%/);
1188
+ const pct = match ? parseFloat(match[1]) : 0;
1189
+ if (pct < <%= coverageThreshold %>) {
1190
+ console.log(graceActive ? `WARN (grace period, ${pct.toFixed(1)}% < <%= coverageThreshold %>%)` : `FAIL (${pct.toFixed(1)}% < <%= coverageThreshold %>%)`);
1191
+ if (!graceActive) { _coverThreshStatus = 'FAIL'; }
1192
+ } else { console.log(`PASS (${pct.toFixed(1)}%)`); }
1193
+ }
1194
+ pushResult('coverage threshold', _coverThreshStatus, Date.now() - _coverThreshStart);
1195
+ if (existsSync('coverage.out')) {
1196
+ mkdirSync('coverage', { recursive: true });
1197
+ const _coverHtmlStart = Date.now();
1198
+ process.stdout.write('[CHECK] coverage HTML report ... ');
1199
+ const htmlResult = spawnSync('go', ['tool', 'cover', '-html=coverage.out', '-o', 'coverage/coverage.html'], { encoding: 'utf-8', shell: false });
1200
+ let _coverHtmlStatus = 'PASS';
1201
+ if (htmlResult.status !== 0) {
1202
+ console.log('FAIL (go tool cover -html)');
1203
+ if (htmlResult.stderr) process.stderr.write(htmlResult.stderr);
1204
+ if (!graceActive) { _coverHtmlStatus = 'FAIL'; }
1205
+ } else { console.log('PASS'); }
1206
+ pushResult('coverage HTML report', _coverHtmlStatus, Date.now() - _coverHtmlStart);
1207
+ }
1208
+ }
1209
+ <% } else if(g.id === 'binary-size') { %>
1210
+ // ── Binary size cap (#359, INV-60) ─────────────────────────────────────────
1211
+ // Asserts the release binary stays under the archetype budget. Only emitted
1212
+ // for archetypes that produce a binary (cli, embedded). Skips gracefully
1213
+ // when the binary has not yet been built — the size budget only matters at
1214
+ // release time, not pre-build.
1215
+ if (_inlineInspect('binary size', 'stat target/release binary')) {} else {
1216
+ {
1217
+ const _binSizeStart = Date.now();
1218
+ process.stdout.write('[CHECK] binary size ... ');
1219
+ const _maxBytes = <%= binarySizeBytes %>;
1220
+ let _binSizeStatus = 'PASS';
1221
+ let _binPath = null;
1222
+ try {
1223
+ const _releaseDir = resolve(process.cwd(), 'target/release');
1224
+ if (existsSync(_releaseDir)) {
1225
+ const _entries = readdirSync(_releaseDir);
1226
+ for (const _e of _entries) {
1227
+ const _full = join(_releaseDir, _e);
1228
+ const _st = statSync(_full);
1229
+ if (_st.isFile() && (_st.mode & 0o111)) {
1230
+ _binPath = _full;
1231
+ const _bytes = _st.size;
1232
+ if (_bytes > _maxBytes) {
1233
+ console.log(graceActive
1234
+ ? `WARN (grace period, ${_bytes} > ${_maxBytes})`
1235
+ : `FAIL (${_bytes} > ${_maxBytes})`);
1236
+ if (!graceActive) { _binSizeStatus = 'FAIL'; }
1237
+ } else {
1238
+ console.log(`PASS (${_bytes} bytes / ${_maxBytes} cap)`);
1239
+ }
1240
+ break;
1241
+ }
1242
+ }
1243
+ }
1244
+ if (_binPath === null) {
1245
+ console.log('SKIP (no release binary built yet)');
1246
+ }
1247
+ } catch (_e) {
1248
+ console.log(graceActive ? 'WARN (grace period, ' + _e.message + ')' : 'FAIL (' + _e.message + ')');
1249
+ if (!graceActive) { _binSizeStatus = 'FAIL'; }
1250
+ }
1251
+ pushResult('binary size', _binSizeStatus, Date.now() - _binSizeStart);
1252
+ }
1253
+ }
1254
+ <% } %>
1255
+ <% } else if (g.id === 'debt-ratchet' && language === 'typescript' && enableDebtGates && coverageEnabled) { %>
1256
+ const _debtRatchetArgs = _coverageRunStartedAt === null
1257
+ ? ['scripts/debt-report.mjs', '--gate']
1258
+ : ['scripts/debt-report.mjs', '--gate', '--coverage-summary', 'coverage/coverage-summary.json', '--coverage-started-at', String(_coverageRunStartedAt)];
1259
+ runCheck('debt ratchet', 'node', _debtRatchetArgs, { soft: graceActive });
1260
+ <% } else { %>
1261
+ <% if (g.condition) { %>if (<%- g.condition %>) {<% } %>
1262
+ <% const _fn = g.kind === 'warn' ? 'runWarnCheck' : g.kind === 'tool' ? 'runToolCheck' : 'runCheck'; %>
1263
+ <%= _fn %>('<%= g.name %>', '<%= g.cmd[0] %>', [<% if (g.cmd.length > 1) { %>'<%- g.cmd.slice(1).join("', '") %>'<% } %>]<%= g.soft ? ', { soft: graceActive }' : '' %>);
1264
+ <% if (g.else) { %>} else {
1265
+ <%- g.else %>
1266
+ }<% } else if (g.condition) { %>}<% } %>
1267
+ <% } %>
1268
+ <%_ if (_skipCond.length) { _%>
1269
+ }
1270
+ <%_ } _%>
1271
+ <% } %>
1272
+ // ── L2: gate-layering contract test (#2041, AC-2041.3) — asserts the
1273
+ // L1 ⊂ L2 ⊂ L3 containment from the embedded registry.
1274
+ if (gateFilePresent('scripts/test-gate-layering.mjs', 'gate layering')) {
1275
+ runCheck('gate layering', 'node', ['scripts/test-gate-layering.mjs'])
1276
+ }
1277
+ }
1278
+ // ── L3 (nightly lane — AC-2041.1, mirrors 06-nightly-lite.yml) ───────────
1279
+ if (level === 'L3' || level === 'L4') {
1280
+ <% for (const g of l3) { %>
1281
+ <% if (g.emitIf !== undefined && !eval('(' + g.emitIf + ')')) continue; %>
1282
+ <% if (g.language && g.language !== language) continue; %>
1283
+ <%_ const _isPromoted = _promotionBases.has(g.id); const _isAudit = g.audit === true; const _promoterId = _isPromoted ? _promotedByMap.get(g.id) : null; const _skipCond = []; if (_isPromoted) _skipCond.push("_promotedAway.has('" + g.id + "')"); if (_isAudit) _skipCond.push('_auditGatesOff'); _%>
1284
+ <%_ if (_skipCond.length) { _%>
1285
+ if (<%- _skipCond.join(' || ') %>) {
1286
+ <%_ if (_isPromoted && _isAudit) { _%>
1287
+ console.log('[CHECK] <%= g.name %> ... SKIP (' + (_promotedAway.has('<%= g.id %>') ? 'promoted by <%- _promoterId %>' : 'ARBITER_AUDIT_MODE=off') + ')');
1288
+ <%_ } else if (_isPromoted) { _%>
1289
+ console.log('[CHECK] <%= g.name %> ... SKIP (promoted by <%- _promoterId %>)');
1290
+ <%_ } else { _%>
1291
+ console.log('[CHECK] <%= g.name %> ... SKIP (ARBITER_AUDIT_MODE=off)');
1292
+ <%_ } _%>
1293
+ pushResult('<%= g.name %>', 'SKIP', 0);
1294
+ } else {
1295
+ <%_ } _%>
1296
+ <% if (g.kind === 'inline') { %>
1297
+ <% if(g.id === 'npm-ci-drift') { %>
1298
+ <% if (g.emitIf !== undefined && !eval('(' + g.emitIf + ')')) { %><%- g.else %><% } else { %>
1299
+ if (_inlineInspect('npm-ci drift', 'npx -y npm@<pin> ci --dry-run')) {} else {
1300
+ const _driftStart = Date.now();
1301
+ process.stdout.write('[CHECK] npm-ci drift ... ');
1302
+ let _driftStatus = 'PASS';
1303
+ try {
1304
+ const _pin = existsSync('package.json')
1305
+ ? (JSON.parse(readFileSync('package.json', 'utf-8')).packageManager || '').match(/^npm@(\d+\.\d+\.\d+)/)
1306
+ : null;
1307
+ if (!_pin || !existsSync('package-lock.json')) {
1308
+ console.log('SKIP (no packageManager npm pin or lockfile)');
1309
+ } else {
1310
+ const _r = spawnSync('npx', ['-y', 'npm@' + _pin[1], 'ci', '--dry-run'], { encoding: 'utf-8', shell: false });
1311
+ if (_r.error) {
1312
+ console.log('SKIP (pinned npm@' + _pin[1] + ' unavailable)');
1313
+ } else if (_r.status === 0) {
1314
+ console.log('PASS');
1315
+ } else {
1316
+ console.log('FAIL');
1317
+ process.stdout.write('package-lock.json is out of sync under npm@' + _pin[1] + ' — relock: npx -y npm@' + _pin[1] + ' install --package-lock-only\n');
1318
+ _driftStatus = 'FAIL';
1319
+ }
1320
+ }
1321
+ } catch (_e) {
1322
+ console.log('SKIP (' + (_e && _e.message ? _e.message : 'unreadable package.json') + ')');
1323
+ }
1324
+ pushResult('npm-ci drift', _driftStatus, Date.now() - _driftStart);
1325
+ }
1326
+ <% } %>
1327
+ <% } else if(g.id === 'workflow-runners-inline') { %>
1328
+ {
1329
+ const _wrStart = Date.now();
1330
+ process.stdout.write('[CHECK] workflow runners ... ');
1331
+ const _wrWorkflowsDir = join(process.cwd(), '.github', 'workflows');
1332
+ // Allowlisted runs-on forms (INV-89, ADR-023 amendment #959 — ubuntu-latest default):
1333
+ // 1. ${{ vars.CI_BUILD_RUNNER_LABEL || 'ubuntu-latest' }} — global override (auxiliary jobs)
1334
+ // 2. ${{ fromJSON(vars.RUNNER_LABELS_BUILD || '["ubuntu-latest"]') }} — build job-class pool
1335
+ // 3. ${{ fromJSON(vars.RUNNER_LABELS_TEST || '["ubuntu-latest"]') }} — test job-class pool
1336
+ // 4. ${{ fromJSON(vars.RUNNER_LABELS_DEPLOY || '["ubuntu-latest"]') }} — deploy job-class pool
1337
+ // 5. ${{ matrix.os }} — explicit OS matrix, always allowed
1338
+ // The per-job-class map lets a project point build/test/deploy at distinct runner
1339
+ // pools (self-hosted / cloud / GitHub-hosted) via org vars without editing workflows;
1340
+ // each var falls back to ubuntu-latest, so an unset var is never a broken runner.
1341
+ const _wrAllowedPattern = /\$\{\{\s*(?:vars\.CI_BUILD_RUNNER_LABEL|fromJSON\(vars\.RUNNER_LABELS_(?:BUILD|TEST|DEPLOY)|matrix\.os)/;
1342
+ const _wrRunsOnPattern = /^\s*runs-on:/;
1343
+ let _wrViolations = 0;
1344
+ let _wrFiles;
1345
+ try {
1346
+ _wrFiles = readdirSync(_wrWorkflowsDir).filter((f) => f.endsWith('.yml') || f.endsWith('.yaml'));
1347
+ } catch (err) {
1348
+ if (err.code !== 'ENOENT') {
1349
+ console.log(`FAIL (cannot read .github/workflows — ${err.message})`);
1350
+ pushResult('workflow runners', 'FAIL', Date.now() - _wrStart);
1351
+ _wrFiles = null;
1352
+ } else {
1353
+ _wrFiles = [];
1354
+ }
1355
+ }
1356
+ if (_wrFiles !== null) {
1357
+ for (const _wrFile of _wrFiles) {
1358
+ let _wrContent;
1359
+ try {
1360
+ _wrContent = readFileSync(join(_wrWorkflowsDir, _wrFile), 'utf-8');
1361
+ } catch (err) { console.log(`FAIL (cannot read workflow file: ${_wrFile} — ${err.message})`); _wrViolations++; continue; }
1362
+ const _wrLines = _wrContent.split('\n');
1363
+ for (let _wi = 0; _wi < _wrLines.length; _wi++) {
1364
+ if (_wrRunsOnPattern.test(_wrLines[_wi]) && !_wrAllowedPattern.test(_wrLines[_wi])) {
1365
+ _wrViolations++;
1366
+ }
1367
+ }
1368
+ }
1369
+ if (_wrViolations > 0) {
1370
+ console.log(`FAIL (${_wrViolations} violation(s) — use \${{ vars.CI_BUILD_RUNNER_LABEL || 'ubuntu-latest' }})`);
1371
+ pushResult('workflow runners', 'FAIL', Date.now() - _wrStart);
1372
+ } else {
1373
+ console.log(`PASS (${Date.now() - _wrStart}ms)`);
1374
+ pushResult('workflow runners', 'PASS', Date.now() - _wrStart);
1375
+ }
1376
+ }
1377
+ }
1378
+ <% } else if(g.id === 'ci-alignment') { %>
1379
+ {
1380
+ const _caStart = Date.now();
1381
+ process.stdout.write('[CHECK] ci alignment ... ');
1382
+ const _caManifestPath = join(process.cwd(), 'scripts', 'check-all.mjs');
1383
+ const _caCiPath = join(process.cwd(), '.github', 'workflows', 'ci.yml');
1384
+ const _caInfraPrefixes = ['npm ci', 'npm install', 'git fetch', 'git checkout', 'curl ', 'echo ', 'mkdir ', 'cp ', 'tar ', 'pip install', 'apt-get'];
1385
+ const _caDesignExemptions = new Set(['npx:commitlint', 'npm:test']);
1386
+ function _caIsInfra(cmd) { return _caInfraPrefixes.some((p) => cmd.startsWith(p)); }
1387
+ function _caNormalizeKey(cmd, firstArg) {
1388
+ switch (cmd) {
1389
+ case 'node': return (firstArg && firstArg.startsWith('scripts/')) ? firstArg : null;
1390
+ case 'npx': { if (!firstArg) return null; const tool = firstArg.replace(/@.+$/, ''); return 'npx:' + tool; }
1391
+ case 'npm': if (firstArg === 'test') return 'npm:test'; if (firstArg === 'audit') return 'npm:audit'; return null;
1392
+ default: return null;
1393
+ }
1394
+ }
1395
+ function _caExtractManifestGates(text) {
1396
+ const gates = new Set();
1397
+ const re = /runCheck\(\s*"[^"]*"\s*,\s*"([^"]+)"\s*,\s*\[([^\]]*)\]/g;
1398
+ let m;
1399
+ while ((m = re.exec(text)) !== null) {
1400
+ const k = _caNormalizeKey(m[1].trim(), (m[2].match(/"([^"]+)"/) || [])[1] || '');
1401
+ if (k !== null) gates.add(k);
1402
+ }
1403
+ return gates;
1404
+ }
1405
+ function _caParseRun(cmd, gates) {
1406
+ if (!cmd || _caIsInfra(cmd)) return;
1407
+ const toks = cmd.split(/\s+/);
1408
+ const c0 = toks[0]; const a0 = toks[1] ?? '';
1409
+ const k = (c0 === 'node' && a0) ? _caNormalizeKey('node', a0) : _caNormalizeKey(c0, a0);
1410
+ if (k !== null) gates.add(k);
1411
+ }
1412
+ function _caExtractCiGates(ciPath) {
1413
+ const gates = new Set();
1414
+ let ciText;
1415
+ try { ciText = readFileSync(ciPath, 'utf-8'); } catch (e) { if (e.code === 'ENOENT') return gates; throw e; }
1416
+ const ciLines = ciText.split('\n');
1417
+ let _ci = 0;
1418
+ while (_ci < ciLines.length) {
1419
+ const cl = ciLines[_ci];
1420
+ if (/^\s*-?\s*uses:\s*.+/.test(cl)) { _ci++; continue; }
1421
+ const blk = cl.match(/^(\s*)-?\s*run:\s*[|>]\s*$/);
1422
+ if (blk) {
1423
+ const bi = blk[1].length; _ci++;
1424
+ while (_ci < ciLines.length) {
1425
+ const bl = ciLines[_ci];
1426
+ if (bl.trim() === '') { _ci++; continue; }
1427
+ if ((bl.match(/^(\s*)/)?.[1].length ?? 0) <= bi && bl.trim() !== '') break;
1428
+ _caParseRun(bl.trim(), gates); _ci++;
1429
+ }
1430
+ continue;
1431
+ }
1432
+ const sl = cl.match(/^\s*-?\s*run:\s*(.+)/);
1433
+ if (sl) { _caParseRun(sl[1].trim(), gates); }
1434
+ _ci++;
1435
+ }
1436
+ return gates;
1437
+ }
1438
+ let _caStatus = 'PASS';
1439
+ try {
1440
+ const _caManifestText = readFileSync(_caManifestPath, 'utf-8');
1441
+ const _caManifestGates = _caExtractManifestGates(_caManifestText);
1442
+ const _caCiGates = _caExtractCiGates(_caCiPath);
1443
+ for (const k of _caDesignExemptions) { _caManifestGates.delete(k); _caCiGates.delete(k); }
1444
+ const _caManifestOnly = [..._caManifestGates].filter((k) => !_caCiGates.has(k));
1445
+ const _caCiOnly = [..._caCiGates].filter((k) => !_caManifestGates.has(k));
1446
+ if (_caManifestOnly.length > 0 || _caCiOnly.length > 0) {
1447
+ console.log(`FAIL (${_caManifestOnly.length + _caCiOnly.length} alignment violation(s))`);
1448
+ for (const k of _caManifestOnly) process.stderr.write(' manifest-only: ' + k + '\n');
1449
+ for (const k of _caCiOnly) process.stderr.write(' ci-only: ' + k + '\n');
1450
+ _caStatus = 'FAIL';
1451
+ } else {
1452
+ console.log(`PASS (${_caManifestGates.size} gates aligned, ${Date.now() - _caStart}ms)`);
1453
+ }
1454
+ } catch (_caErr) {
1455
+ console.log('FAIL (error: ' + _caErr.message + ')');
1456
+ _caStatus = 'FAIL';
1457
+ }
1458
+ pushResult('ci alignment', _caStatus, Date.now() - _caStart);
1459
+ }
1460
+ <% } else if(g.id === 'playwright-rules') { %>
1461
+ if (existsSync(join('node_modules', 'eslint-plugin-playwright'))) {
1462
+ // --no-error-on-unmatched-pattern: the glob is source-co-located-spec-oriented;
1463
+ // a project whose specs live only under tests/e2e/ (arbiter's own scaffold
1464
+ // convention) legitimately matches zero files here — that must stay a PASS,
1465
+ // not a Day-1 RED (#1491-class fix).
1466
+ runCheck('playwright rules', 'npx', ['eslint', '--config', 'eslint.config.playwright.mjs', '--no-config-lookup', '--no-error-on-unmatched-pattern', 'src/**/*.spec.ts']);
1467
+ } else {
1468
+ console.log('[CHECK] playwright rules ... SKIP (run: npm install -D eslint-plugin-playwright)');
1469
+ }
1470
+ <% } else if(g.id === 'playwright-e2e') { %>
1471
+ // ─── L2: Playwright E2E (#348, CANON-02) — matrix proven for TS frontend-spa / backend-web-db.
1472
+ // Wrapped via scripts/lib/ephemeral-server.mjs (#358): the runner starts the
1473
+ // server, polls TCP readiness, runs Playwright, then tears the server down.
1474
+ // runToolCheck SKIPs locally when the binary is missing, FAILs in CI.
1475
+ const _e2eStartCommand = process.env.E2E_START_CMD?.trim();
1476
+ let _hasStartTestScript = false;
1477
+ try {
1478
+ const _e2ePackage = JSON.parse(readFileSync('package.json', 'utf-8'));
1479
+ _hasStartTestScript = typeof _e2ePackage.scripts?.['start:test'] === 'string'
1480
+ && _e2ePackage.scripts['start:test'].trim().length > 0;
1481
+ } catch {
1482
+ // No package.json means arbiter cannot supply an application start command.
1483
+ }
1484
+ if (!_e2eStartCommand && !_hasStartTestScript) {
1485
+ console.log('[CHECK] playwright e2e ... SKIP (E2E_START_CMD unset and package.json has no start:test script)');
1486
+ pushResult('playwright e2e', 'SKIP', 0);
1487
+ } else {
1488
+ runToolCheck(
1489
+ 'playwright e2e',
1490
+ 'node',
1491
+ [
1492
+ 'scripts/lib/ephemeral-server.mjs',
1493
+ '--start', _e2eStartCommand || 'npm run start:test',
1494
+ '--test', 'npx playwright test',
1495
+ '--port', process.env.E2E_PORT || '4173',
1496
+ ],
1497
+ { soft: graceActive },
1498
+ );
1499
+ }
1500
+ <% } else if(g.id === 'pytest-playwright-e2e') { %>
1501
+ // ─── L2: pytest-playwright E2E (#348, CANON-02) — matrix proven for Python web archetypes.
1502
+ // Wrapped via scripts/lib/ephemeral-server.mjs (#358) so the server is up
1503
+ // before pytest collects the e2e tests. runToolCheck SKIPs locally when
1504
+ // pytest is missing, FAILs in CI.
1505
+ runToolCheck(
1506
+ 'pytest-playwright e2e',
1507
+ 'node',
1508
+ [
1509
+ 'scripts/lib/ephemeral-server.mjs',
1510
+ '--start', process.env.E2E_START_CMD || 'python -m uvicorn app.main:app --port 8000',
1511
+ '--test', 'pytest tests/e2e',
1512
+ '--port', process.env.E2E_PORT || '8000',
1513
+ ],
1514
+ { soft: graceActive },
1515
+ );
1516
+ <% } else if(g.id === 'npm-audit') { %>
1517
+ <% if (enableSecurityScanning && packageManager === 'npm') { %>
1518
+ runCheck('audit', 'npm', ['audit', '--omit=dev', '--audit-level=high'], { soft: graceActive });
1519
+ <% } else if (enableSecurityScanning) { %>
1520
+ console.log('[CHECK] audit ... SKIP (project uses <%= packageManager %>)');
1521
+ pushResult('audit', 'SKIP', 0);
1522
+ <% } %>
1523
+ <% } else if(g.id === 'coverage-threshold') { %>
1524
+ // ─── L2: coverage threshold (greenfield-aware, #1319.8) ─────────────────────
1525
+ // Run vitest with the json-summary reporter so we can inspect the real coverage
1526
+ // totals at runtime. A virgin project has zero executable statements ⇒ the
1527
+ // greenfield guard PASSES instead of false-failing the line threshold. A coverage
1528
+ // run that crashed (no summary) FAILS — never silently skipped.
1529
+ if (_inlineInspect('coverage threshold', 'npx vitest run --coverage --coverage.reporter=json-summary')) {} else {
1530
+ const _covStart = Date.now();
1531
+ _coverageRunStartedAt = _covStart;
1532
+ process.stdout.write('[CHECK] coverage threshold ... ');
1533
+ const _covThreshold = <%= coverageThreshold %>;
1534
+ const _cov = spawnSync(
1535
+ 'npx',
1536
+ ['vitest', 'run', '--coverage', '--coverage.reporter=json-summary'],
1537
+ { encoding: 'utf-8', shell: false },
1538
+ );
1539
+ let _covStatus = 'PASS';
1540
+ let _covReason = '';
1541
+ let _covSummary = null;
1542
+ const _covSummaryPath = resolve(process.cwd(), 'coverage', 'coverage-summary.json');
1543
+ if (existsSync(_covSummaryPath)) {
1544
+ try {
1545
+ _covSummary = JSON.parse(readFileSync(_covSummaryPath, 'utf-8'));
1546
+ } catch {
1547
+ _covSummary = null; // malformed JSON ⇒ evaluateCoverageGate fails closed
1548
+ }
1549
+ }
1550
+ const _covResult = evaluateCoverageGate(_covSummary, _covThreshold);
1551
+ _covStatus = _covResult.status;
1552
+ _covReason = _covResult.reason;
1553
+ if (_covStatus === 'FAIL' && graceActive) {
1554
+ console.log(`WARN (grace period — ${_covReason})`);
1555
+ _covStatus = 'PASS';
1556
+ } else if (_covStatus === 'FAIL') {
1557
+ console.log(`FAIL (${_covReason})`);
1558
+ } else {
1559
+ console.log(`PASS (${_covReason})`);
1560
+ }
1561
+ pushResult('coverage threshold', _covStatus, Date.now() - _covStart);
1562
+ }
1563
+ <% } else if(g.id === 'bdd-ignore-check') { %>
1564
+ // ─── L2: BDD gate (INV-40) ──────────────────────────────────────────────────
1565
+ if (_inlineInspect('BDD @ignore check', 'grep -rql --include=*.feature @ignore .')) {} else {
1566
+ // @ignore-tagged scenarios are HARD-fail (soft: false per INV-40 — never graced)
1567
+ const _bddIgnoreStart = Date.now();
1568
+ const _bddIgnore = spawnSync('grep', ['-rql', '--include=*.feature', '@ignore', '.'], { encoding: 'utf-8', shell: false });
1569
+ process.stdout.write('[CHECK] BDD @ignore check ... ');
1570
+ let _bddIgnoreStatus = 'PASS';
1571
+ if (_bddIgnore.error?.code === 'ENOENT') {
1572
+ console.log('FAIL (grep not found — cannot check @ignore tags)');
1573
+ _bddIgnoreStatus = 'FAIL';
1574
+ } else if (_bddIgnore.status === null || _bddIgnore.status === 2) {
1575
+ console.log(`FAIL (grep error — exit ${_bddIgnore.status ?? 'signal'}: ${_bddIgnore.stderr ?? ''})`);
1576
+ _bddIgnoreStatus = 'FAIL';
1577
+ } else if (_bddIgnore.status === 0) {
1578
+ console.log('FAIL (@ignore-tagged scenarios found — remove tags or move to issue tracker)');
1579
+ _bddIgnoreStatus = 'FAIL';
1580
+ } else {
1581
+ console.log('PASS');
1582
+ }
1583
+ pushResult('BDD @ignore check', _bddIgnoreStatus, Date.now() - _bddIgnoreStart);
1584
+ }
1585
+ <% } else if(g.id === 'bdd-go') { %>
1586
+ // direct parallel of Python's `pytest.importorskip("pytest_bdd")`.
1587
+ {
1588
+ const _goBddStart = Date.now();
1589
+ const _goMod = existsSync('go.mod') ? readFileSync('go.mod', 'utf-8') : '';
1590
+ if (_goMod.includes('github.com/cucumber/godog')) {
1591
+ runCheck('bdd', 'go', ['test', '-tags', 'bdd', './internal/bdd/...'], { soft: graceActive });
1592
+ } else {
1593
+ console.log('[CHECK] bdd ... SKIP (github.com/cucumber/godog not in go.mod — run: go get github.com/cucumber/godog)');
1594
+ pushResult('bdd', 'SKIP', Date.now() - _goBddStart);
1595
+ }
1596
+ }
1597
+ <% } else if(g.id === 'bdd-rust') { %>
1598
+ // graceful-skip contract as the Go/Python BDD gates above.
1599
+ {
1600
+ const _rustBddStart = Date.now();
1601
+ const _cargoToml = existsSync('Cargo.toml') ? readFileSync('Cargo.toml', 'utf-8') : '';
1602
+ if (/^\s*bdd\s*=/m.test(_cargoToml) || _cargoToml.includes('cucumber')) {
1603
+ runCheck('bdd', 'cargo', ['test', '--features', 'bdd'], { soft: graceActive });
1604
+ } else {
1605
+ console.log('[CHECK] bdd ... SKIP (no `bdd` feature / cucumber dep in Cargo.toml — see tests/example_bdd_test.rs)');
1606
+ pushResult('bdd', 'SKIP', Date.now() - _rustBddStart);
1607
+ }
1608
+ }
1609
+ <% } else if(g.id === 'evidence-gate') { %>
1610
+ // ─── L3/L4: Evidence gate (INV-33) ───────────────────────────────────────────
1611
+ // Validates nightly evidence produced by evidence-collect.mjs.
1612
+ // Warns when absent (first run); fails when present but obs_gate !== 'PASS'.
1613
+ const _evidenceStart = Date.now();
1614
+ let _evidenceStatus = 'PASS';
1615
+ process.stdout.write('[CHECK] evidence gate (INV-33) ... ');
1616
+ {
1617
+ const _summaryPath = join(process.cwd(), '.evidence', 'SUMMARY.json');
1618
+ if (!existsSync(_summaryPath)) {
1619
+ console.log('WARN — .evidence/SUMMARY.json absent (run nightly pipeline first)');
1620
+ } else {
1621
+ try {
1622
+ const _summary = JSON.parse(readFileSync(_summaryPath, 'utf-8'));
1623
+ if (_summary.obs_gate === 'PASS') {
1624
+ console.log('PASS');
1625
+ } else {
1626
+ console.log('FAIL (obs_gate=' + _summary.obs_gate + ')');
1627
+ _evidenceStatus = 'FAIL';
1628
+ }
1629
+ } catch {
1630
+ console.log('FAIL (could not parse .evidence/SUMMARY.json)');
1631
+ _evidenceStatus = 'FAIL';
1632
+ }
1633
+ }
1634
+ }
1635
+ pushResult('evidence gate (INV-33)', _evidenceStatus, Date.now() - _evidenceStart);
1636
+ <% } %>
1637
+ <% } else { %>
1638
+ <% if (g.condition) { %>if (<%- g.condition %>) { <% } %>
1639
+ <% const _fn = g.kind === 'warn' ? 'runWarnCheck' : g.kind === 'tool' ? 'runToolCheck' : 'runCheck'; %>
1640
+ <%= _fn %>('<%= g.name %>', '<%= g.cmd[0] %>', [<% if (g.cmd.length > 1) { %>'<%- g.cmd.slice(1).join("', '") %>'<% } %>]<%= g.soft ? ', { soft: graceActive }' : '' %>);
1641
+ <% if (g.else) { %>} else {
1642
+ <%- g.else %>
1643
+ }<% } else if (g.condition) { %>}<% } %>
1644
+ <% } %>
1645
+ <%_ if (_skipCond.length) { _%>
1646
+ }
1647
+ <%_ } _%>
1648
+ <% } %>
1649
+ }
1650
+
1651
+ // ─── Summary ─────────────────────────────────────────────────────────────────
1652
+ console.log('');
1653
+ console.log('=== Summary ===');
1654
+ console.log('');
1655
+
1656
+ const _allResults = getResults();
1657
+ const _failedCount = getFailed();
1658
+ const nameWidth = Math.max(6, ..._allResults.map((r) => r.name.length));
1659
+ const header = `${'Check'.padEnd(nameWidth)} Status Elapsed`;
1660
+ const divider = '-'.repeat(header.length);
1661
+ console.log(header);
1662
+ console.log(divider);
1663
+ let totalElapsed = 0;
1664
+ for (const r of _allResults) {
1665
+ totalElapsed += r.elapsed;
1666
+ console.log(`${r.name.padEnd(nameWidth)} ${r.status.padEnd(6)} ${r.elapsed}ms`);
1667
+ }
1668
+ console.log(divider);
1669
+ console.log(`${'Total'.padEnd(nameWidth)} ${totalElapsed}ms`);
1670
+ console.log('');
1671
+
1672
+ // ─── Gate result JSON (arbiter-gate-v1) ──────────────────────────────────────
1673
+ // #2078: never write gate evidence from an inspection run (--dry-run / --gate) —
1674
+ // its `pass` would be a lie (all-SKIP for dry-run, a partial run for --gate).
1675
+ if (!_inspect) {
1676
+ const _parityGates = _allResults
1677
+ .map((r) => ({ name: r.name, pass: r.status === 'PASS' }))
1678
+ .sort((a, b) => a.name.localeCompare(b.name));
1679
+ const _parityContentHash = createHash('sha256').update(JSON.stringify(_parityGates)).digest('hex');
1680
+ const _contentHash = createHash('sha256').update(JSON.stringify(_parityGates)).digest('hex');
1681
+ const _artifact = {
1682
+ schema: 'arbiter-gate-v1',
1683
+ node: process.version,
1684
+ level,
1685
+ gates: _allResults.map((r) => ({ name: r.name, pass: r.status === 'PASS', durationMs: r.elapsed })),
1686
+ parityGates: _parityGates,
1687
+ pass: _failedCount === 0,
1688
+ parityContentHash: _parityContentHash,
1689
+ contentHash: _contentHash,
1690
+ };
1691
+ const _outPath = (jsonPath !== null && jsonPath !== '') ? jsonPath : resolve(process.cwd(), '.arbiter/gate/local-result.json');
1692
+ try {
1693
+ mkdirSync(dirname(_outPath), { recursive: true });
1694
+ writeFileSync(_outPath, JSON.stringify(_artifact, null, 2) + '\n');
1695
+ } catch { /* non-fatal */ }
1696
+ }
1697
+
1698
+ if (_failedCount === 0 && !_inspect) {
1699
+ // #1441: stamp the task id so the Stop hook can reject a prior task's gate-pass (anti-replay).
1700
+ const _taskId = (() => {
1701
+ try {
1702
+ const _sp = resolve(process.cwd(), '.claude/.task/status.json');
1703
+ if (!existsSync(_sp)) return 'unknown';
1704
+ const _s = JSON.parse(readFileSync(_sp, 'utf-8'));
1705
+ return (typeof _s.taskId === 'string' && _s.taskId.length > 0) ? _s.taskId : 'unknown';
1706
+ } catch { return 'unknown'; }
1707
+ })();
1708
+ // The marker binds tree content, checkout identity, toolchain identity, level
1709
+ // and a TTL — not just head_sha + branch + a boolean. A fact that cannot be
1710
+ // resolved yields no marker at all: a green gate with no marker is honest, a
1711
+ // marker that cannot prove what it describes is not.
1712
+ try {
1713
+ // Loaded lazily so a project missing the verifier writes NO marker (fail
1714
+ // closed) instead of crashing an otherwise-green gate at import time.
1715
+ const { buildGateEvidence } = await import('./lib/gate-evidence.mjs');
1716
+ // #2427: `_gateStart` is the identity captured before the first check ran; a
1717
+ // marker is refused outright when it is missing, incomplete, or no longer
1718
+ // matches. A green gate with no marker is honest; a marker naming a tree the
1719
+ // gate did not measure end to end is not.
1720
+ const _evidence = buildGateEvidence({ root: process.cwd(), level, taskId: _taskId, start: _gateStart });
1721
+ if (_evidence === null) {
1722
+ process.stderr.write(
1723
+ 'check-all: warning: gate marker NOT written — HEAD, checkout root or tree hash ' +
1724
+ 'could not be resolved, or the commit/tree moved while the gate was running, ' +
1725
+ 'so nothing can bind this gate result to this tree (#2427)\n',
1726
+ );
1727
+ } else {
1728
+ const _markerPath = resolve(process.cwd(), '.arbiter/gate-pass.json');
1729
+ mkdirSync(dirname(_markerPath), { recursive: true });
1730
+ writeFileSync(_markerPath, JSON.stringify(_evidence, null, 2) + '\n');
1731
+ }
1732
+ } catch (_err) {
1733
+ process.stderr.write(`check-all: warning: could not write gate marker: ${_err.message}\n`);
1734
+ }
1735
+ }
1736
+
1737
+ if (_failedCount > 0) {
1738
+ const _failedResults = _allResults.filter((r) => r.status === 'FAIL' || r.status === 'TIMEOUT');
1739
+ console.error(`=== FAILED: ${_failedCount} check(s) ===`);
1740
+ console.error('Failed checks:');
1741
+ for (const r of _failedResults) console.error(`- ${r.name} (${r.status})`);
1742
+ console.error('');
1743
+ process.exit(1);
1744
+ } else {
1745
+ console.log('=== ALL PASSED ===\n');
1746
+ }