@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.
- package/CHANGELOG.md +713 -0
- package/LICENSE +202 -0
- package/NOTICE +31 -0
- package/PRIVACY.md +111 -0
- package/README.md +237 -0
- package/THIRD_PARTY_LICENSES.md +472 -0
- package/dist/.src-manifest.json +1 -0
- package/dist/capabilities/host-probe.js +3 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +87 -0
- package/dist/commands/adopt-policy.js +3 -0
- package/dist/commands/configure-interactive.js +3 -0
- package/dist/commands/configure.js +5 -0
- package/dist/commands/cross-model-review.js +3 -0
- package/dist/commands/diff.js +20 -0
- package/dist/commands/doc-set.js +3 -0
- package/dist/commands/doctor/clean.js +2 -0
- package/dist/commands/doctor/fail-open-census.js +12 -0
- package/dist/commands/doctor/health.js +11 -0
- package/dist/commands/doctor/prove-gates.js +13 -0
- package/dist/commands/doctor/recover-lock.js +12 -0
- package/dist/commands/doctor/repair-state.js +6 -0
- package/dist/commands/doctor/tool-pin-extract.js +4 -0
- package/dist/commands/doctor/tool-pins.js +9 -0
- package/dist/commands/doctor-interactive.js +2 -0
- package/dist/commands/doctor.js +2 -0
- package/dist/commands/explain.js +19 -0
- package/dist/commands/gate-exec.js +4 -0
- package/dist/commands/gold-audit.js +14 -0
- package/dist/commands/graph.js +5 -0
- package/dist/commands/init/build-arbiter-config.js +1 -0
- package/dist/commands/init/generate.js +42 -0
- package/dist/commands/init/github-setup.js +5 -0
- package/dist/commands/init/maturity-gates.js +10 -0
- package/dist/commands/init/resolve-config.js +3 -0
- package/dist/commands/init/toolchain.js +10 -0
- package/dist/commands/init/types.js +1 -0
- package/dist/commands/init.js +17 -0
- package/dist/commands/method-interactive.js +4 -0
- package/dist/commands/method.js +11 -0
- package/dist/commands/obsidian.js +2 -0
- package/dist/commands/plugin.js +8 -0
- package/dist/commands/pr-merged.js +2 -0
- package/dist/commands/retire-policy.js +3 -0
- package/dist/commands/review-diff.js +3 -0
- package/dist/commands/settings.js +7 -0
- package/dist/commands/ship-config.js +2 -0
- package/dist/commands/ship-profile.js +2 -0
- package/dist/commands/ship-review.js +1 -0
- package/dist/commands/ship-tier.js +2 -0
- package/dist/commands/ship-train.js +2 -0
- package/dist/commands/task-mark.js +2 -0
- package/dist/commands/task-note.js +3 -0
- package/dist/commands/task-record-red.js +2 -0
- package/dist/commands/task-record-tech-debt.js +4 -0
- package/dist/commands/task-ship.js +3 -0
- package/dist/commands/task-state.js +6 -0
- package/dist/commands/task.js +59 -0
- package/dist/commands/update.js +60 -0
- package/dist/commands/upgrade-level-interactive.js +2 -0
- package/dist/commands/upgrade-level.js +7 -0
- package/dist/commands/verify-plan.js +8 -0
- package/dist/commands/verify-tdd.js +2 -0
- package/dist/commands/verify.js +7 -0
- package/dist/commands/wizard/coherence.js +1 -0
- package/dist/commands/worktree-prune.js +4 -0
- package/dist/commands/worktree.js +39 -0
- package/dist/compatibility/cross-language-matrix.json +540 -0
- package/dist/compatibility/index.d.ts +4 -0
- package/dist/compatibility/index.js +2 -0
- package/dist/compatibility/matcher.js +1 -0
- package/dist/compatibility/matrix.json +27 -0
- package/dist/compatibility/parsers.d.ts +38 -0
- package/dist/compatibility/parsers.js +1 -0
- package/dist/compatibility/probe.d.ts +41 -0
- package/dist/compatibility/probe.js +4 -0
- package/dist/compatibility/report.js +2 -0
- package/dist/compatibility/schema.d.ts +80 -0
- package/dist/compatibility/schema.js +1 -0
- package/dist/compatibility/skills-matrix.json +98 -0
- package/dist/compatibility/skills-validator.d.ts +30 -0
- package/dist/compatibility/skills-validator.js +4 -0
- package/dist/compatibility/tooling-proposals.json +5 -0
- package/dist/config/arbiter-ignore.js +3 -0
- package/dist/config/collaboration-mode-defaults.js +1 -0
- package/dist/config/diff.js +1 -0
- package/dist/config/env-overrides.js +3 -0
- package/dist/config/env-registry.js +2 -0
- package/dist/config/levels.js +1 -0
- package/dist/config/migrations/index.js +2 -0
- package/dist/config/migrations/v0-to-v1.js +2 -0
- package/dist/config/migrations/v1-to-v2.js +2 -0
- package/dist/config/migrations/v2-to-v3.js +2 -0
- package/dist/config/migrations/v3-to-v4.js +2 -0
- package/dist/config/override-resolver.js +2 -0
- package/dist/config/resolve-project-config.js +2 -0
- package/dist/config/resolve-project-name.js +2 -0
- package/dist/config/schema.d.ts +480 -0
- package/dist/config/schema.js +1 -0
- package/dist/config/test-pyramid-profiles.js +1 -0
- package/dist/config/thresholds.js +2 -0
- package/dist/conformance/dimensions.js +3 -0
- package/dist/conformance/engine.js +7 -0
- package/dist/conformance/gate-proofs.js +4 -0
- package/dist/conformance/shared.js +2 -0
- package/dist/detectors/axis.js +1 -0
- package/dist/detectors/build.js +2 -0
- package/dist/detectors/existing.js +2 -0
- package/dist/detectors/external-model.js +2 -0
- package/dist/detectors/framework.js +2 -0
- package/dist/detectors/git.js +11 -0
- package/dist/detectors/github.js +2 -0
- package/dist/detectors/lanes.js +5 -0
- package/dist/detectors/language-hooks.js +117 -0
- package/dist/detectors/language.js +2 -0
- package/dist/detectors/package-manager.js +2 -0
- package/dist/detectors/package.js +2 -0
- package/dist/detectors/scaffold-wiring.js +2 -0
- package/dist/detectors/workflow-collision.js +4 -0
- package/dist/evidence/gate-binding.js +4 -0
- package/dist/evidence/git-checks.js +3 -0
- package/dist/evidence/load.js +2 -0
- package/dist/evidence/provenance.js +2 -0
- package/dist/evidence/summary.js +2 -0
- package/dist/evidence/tdd-reexecute.js +2 -0
- package/dist/evidence/tdd.js +4 -0
- package/dist/experimental/index.js +2 -0
- package/dist/experimental/parse-argv.js +2 -0
- package/dist/experimental/registry.js +2 -0
- package/dist/generators/agents-claude.js +2 -0
- package/dist/generators/agents-md.js +2 -0
- package/dist/generators/anti-drift-validators.js +3 -0
- package/dist/generators/anti-proforma.js +2 -0
- package/dist/generators/api-e2e.js +2 -0
- package/dist/generators/api-middleware.js +2 -0
- package/dist/generators/archunit.js +2 -0
- package/dist/generators/audit-toolchain.js +2 -0
- package/dist/generators/auth.js +2 -0
- package/dist/generators/behavioral-tests.js +2 -0
- package/dist/generators/boundaries.js +2 -0
- package/dist/generators/check-all.js +2 -0
- package/dist/generators/check-stack-conformity.js +2 -0
- package/dist/generators/ci-five-lane.js +2 -0
- package/dist/generators/ci-tier.js +2 -0
- package/dist/generators/claude.js +3 -0
- package/dist/generators/codex-hooks.js +2 -0
- package/dist/generators/codex-known-limitations.js +2 -0
- package/dist/generators/codex.js +15 -0
- package/dist/generators/commit-footer.js +2 -0
- package/dist/generators/compliance-menu.js +2 -0
- package/dist/generators/compliance.js +2 -0
- package/dist/generators/conformance.js +2 -0
- package/dist/generators/contract-testing.js +2 -0
- package/dist/generators/coverage.js +2 -0
- package/dist/generators/debt-gates.js +24 -0
- package/dist/generators/debt-ratchet.js +2 -0
- package/dist/generators/derived-class.js +2 -0
- package/dist/generators/doc-set.js +4 -0
- package/dist/generators/docs.js +2 -0
- package/dist/generators/duplication.js +2 -0
- package/dist/generators/e2e-constitution.js +2 -0
- package/dist/generators/env-template.js +2 -0
- package/dist/generators/evidence-retention.js +2 -0
- package/dist/generators/feature-matrix.js +2 -0
- package/dist/generators/frontend-governance.js +2 -0
- package/dist/generators/frontend-quality.js +2 -0
- package/dist/generators/gap.js +2 -0
- package/dist/generators/gdpr.js +2 -0
- package/dist/generators/githooks.js +2 -0
- package/dist/generators/github-setup.js +2 -0
- package/dist/generators/github.js +2 -0
- package/dist/generators/gitignore.js +2 -0
- package/dist/generators/global-invariants.js +3 -0
- package/dist/generators/go-boundaries.js +2 -0
- package/dist/generators/gold-kit.js +2 -0
- package/dist/generators/infra.js +2 -0
- package/dist/generators/integration-testing.js +7 -0
- package/dist/generators/iso27001.js +2 -0
- package/dist/generators/iso9001.js +2 -0
- package/dist/generators/labels.js +2 -0
- package/dist/generators/local-wrapper.js +2 -0
- package/dist/generators/modulith.js +2 -0
- package/dist/generators/mutation.js +2 -0
- package/dist/generators/observability.js +2 -0
- package/dist/generators/operations.js +2 -0
- package/dist/generators/perf-k6.js +2 -0
- package/dist/generators/pharma.js +2 -0
- package/dist/generators/playwright-python.js +2 -0
- package/dist/generators/playwright-ts.js +2 -0
- package/dist/generators/pr-tooling.js +2 -0
- package/dist/generators/python-boundaries.js +2 -0
- package/dist/generators/quality.js +2 -0
- package/dist/generators/registry.js +2 -0
- package/dist/generators/regulated.js +2 -0
- package/dist/generators/resilience.js +2 -0
- package/dist/generators/risk-register.js +2 -0
- package/dist/generators/root.js +2 -0
- package/dist/generators/rust-boundaries.js +2 -0
- package/dist/generators/safety-class.js +2 -0
- package/dist/generators/security.js +2 -0
- package/dist/generators/seed.js +2 -0
- package/dist/generators/self-validation.js +2 -0
- package/dist/generators/ship-driver.js +2 -0
- package/dist/generators/skill-names.json +17 -0
- package/dist/generators/skills.js +2 -0
- package/dist/generators/smoke-journeys.js +2 -0
- package/dist/generators/solo-exception.js +2 -0
- package/dist/generators/ssot.js +2 -0
- package/dist/generators/stride-enforcement.js +2 -0
- package/dist/generators/suppressions.js +2 -0
- package/dist/generators/test-pyramid-manifest.js +3 -0
- package/dist/generators/test-taxonomy.js +2 -0
- package/dist/generators/wiki.js +2 -0
- package/dist/github/branch-protection.js +2 -0
- package/dist/github/classify-gh-error.js +2 -0
- package/dist/github/labels.js +2 -0
- package/dist/github/project-board.js +2 -0
- package/dist/graph/builders/adr.js +4 -0
- package/dist/graph/builders/ast.js +3 -0
- package/dist/graph/builders/canon.js +2 -0
- package/dist/graph/builders/evidence.js +2 -0
- package/dist/graph/builders/inv.js +1 -0
- package/dist/graph/builders/req.js +3 -0
- package/dist/graph/builders/test-nodes.js +3 -0
- package/dist/graph/builders/utils.js +2 -0
- package/dist/graph/load.js +2 -0
- package/dist/graph/model.js +2 -0
- package/dist/graph/query.js +3 -0
- package/dist/graph/store.js +1 -0
- package/dist/i18n/en.json +293 -0
- package/dist/i18n/index.js +4 -0
- package/dist/i18n/types.js +2 -0
- package/dist/integrations/companions.js +2 -0
- package/dist/integrations/external-review.js +10 -0
- package/dist/integrations/plugin-schema.js +2 -0
- package/dist/integrations/skill-detector.js +7 -0
- package/dist/integrations/skills-matrix.js +2 -0
- package/dist/integrations/types.d.ts +23 -0
- package/dist/integrations/types.js +2 -0
- package/dist/internal/cli-deprecation-registry.js +2 -0
- package/dist/internal/deprecate.js +3 -0
- package/dist/internal/experimental-warn.js +3 -0
- package/dist/invariants/catalog.d.ts +2 -0
- package/dist/invariants/catalog.js +1 -0
- package/dist/invariants/filter.d.ts +28 -0
- package/dist/invariants/filter.js +1 -0
- package/dist/invariants/index.d.ts +4 -0
- package/dist/invariants/index.js +2 -0
- package/dist/invariants/tiers.js +1 -0
- package/dist/invariants/types.d.ts +1 -0
- package/dist/invariants/types.js +1 -0
- package/dist/kit/applicability.js +3 -0
- package/dist/kit/audit-report.js +3 -0
- package/dist/kit/brownfield-detect.js +4 -0
- package/dist/kit/canonical-mapping.json +1 -0
- package/dist/kit/catalog.js +2 -0
- package/dist/kit/catalog.json +1 -0
- package/dist/kit/derived.json +1 -0
- package/dist/kit/emit-issues.js +8 -0
- package/dist/kit/measure.js +5 -0
- package/dist/kit/redaction.js +3 -0
- package/dist/kit/schema.js +2 -0
- package/dist/kit/taxonomy.js +2 -0
- package/dist/kit/thresholds.d.ts +12 -0
- package/dist/kit/thresholds.js +2 -0
- package/dist/kit/wave-engine.js +1 -0
- package/dist/recipes/loader.js +6 -0
- package/dist/recipes/schema.js +2 -0
- package/dist/risk/classifier.js +2 -0
- package/dist/risk/sha-check.js +2 -0
- package/dist/state/backups.js +2 -0
- package/dist/state/envelope.js +2 -0
- package/dist/state/generated-manifest.js +3 -0
- package/dist/state/manifest-key.js +2 -0
- package/dist/state/migrations/index.js +1 -0
- package/dist/state/migrations/v0-to-v1.js +2 -0
- package/dist/state/retired-renders.js +2 -0
- package/dist/templates/.nvmrc.ejs +1 -0
- package/dist/templates/HANDOFF.template.md +45 -0
- package/dist/templates/__tests__/contract/error-shape.contract.test.ts.ejs +47 -0
- package/dist/templates/agents-md/AGENTS.md.ejs +517 -0
- package/dist/templates/api-e2e/README.md.ejs +33 -0
- package/dist/templates/api-e2e/go-httptest.go.ejs +43 -0
- package/dist/templates/api-e2e/java-restassured.java.ejs +28 -0
- package/dist/templates/api-e2e/kotlin-restassured.kt.ejs +30 -0
- package/dist/templates/api-e2e/manifest.json.ejs +8 -0
- package/dist/templates/api-e2e/postman.collection.json.ejs +30 -0
- package/dist/templates/api-e2e/python-httpx.py.ejs +17 -0
- package/dist/templates/api-e2e/run.sh.ejs +61 -0
- package/dist/templates/api-e2e/ts-supertest.test.ts.ejs +18 -0
- package/dist/templates/archunit/AntiCyclicTest.java.ejs +29 -0
- package/dist/templates/archunit/AntiProformaExempt.java.ejs +35 -0
- package/dist/templates/archunit/AntiProformaTest.java.ejs +104 -0
- package/dist/templates/archunit/ArchitectureTest.java.ejs +87 -0
- package/dist/templates/archunit/DependencyFlowTest.java.ejs +55 -0
- package/dist/templates/archunit/DomainPurityTest.java.ejs +47 -0
- package/dist/templates/archunit/NamingConventionsTest.java.ejs +54 -0
- package/dist/templates/archunit/NoH2ArchTest.java.ejs +30 -0
- package/dist/templates/archunit/NoMockMvcTest.java.ejs +71 -0
- package/dist/templates/archunit/PortsIndependenceTest.java.ejs +55 -0
- package/dist/templates/archunit/RestAssuredArchTest.java.ejs +32 -0
- package/dist/templates/archunit/RestAssuredBaseIT.java.ejs +66 -0
- package/dist/templates/archunit/TestCoverageArchTest.java.ejs +29 -0
- package/dist/templates/archunit/arch-test-deps-maven.md.ejs +42 -0
- package/dist/templates/archunit/arch-test-deps.gradle.ejs +14 -0
- package/dist/templates/audit/gdpr/check-gdpr-controls.mjs.ejs +102 -0
- package/dist/templates/audit/gdpr/controls-to-gates.md.ejs +42 -0
- package/dist/templates/audit/gdpr/data-flow-map.md.ejs +30 -0
- package/dist/templates/audit/gdpr/dpia.md.ejs +45 -0
- package/dist/templates/audit/generic/audit-gate-rules.md.ejs +24 -0
- package/dist/templates/audit/generic/audit-trail-policy.md.ejs +38 -0
- package/dist/templates/audit/iso27001/iso27001-controls-gate-map.md.ejs +60 -0
- package/dist/templates/auth/setup.md.ejs +661 -0
- package/dist/templates/behavioral-tests/ExampleBehavioralTest.java.ejs +67 -0
- package/dist/templates/behavioral-tests/TESTING_POLICY.md.ejs +116 -0
- package/dist/templates/behavioral-tests/bdd/ExampleBddIT.java.ejs +19 -0
- package/dist/templates/behavioral-tests/bdd/ExampleSteps.java.ejs +61 -0
- package/dist/templates/behavioral-tests/bdd/example.feature.ejs +14 -0
- package/dist/templates/behavioral-tests/bdd/example.steps.ts.ejs +37 -0
- package/dist/templates/behavioral-tests/bdd/example_bdd_test.rs.ejs +64 -0
- package/dist/templates/behavioral-tests/bdd/example_test.go.ejs +80 -0
- package/dist/templates/behavioral-tests/bdd/test_example_bdd.py.ejs +47 -0
- package/dist/templates/behavioral-tests/eslint-playwright.json.ejs +10 -0
- package/dist/templates/behavioral-tests/eslint.config.playwright.mjs.ejs +29 -0
- package/dist/templates/behavioral-tests/example.behavioral.test.ts.ejs +47 -0
- package/dist/templates/behavioral-tests/example_behavioral_test.go.ejs +51 -0
- package/dist/templates/behavioral-tests/example_behavioral_test.rs.ejs +47 -0
- package/dist/templates/behavioral-tests/test_example_behavioral.py.ejs +40 -0
- package/dist/templates/boundaries/.eslintrc-boundaries.cjs.ejs +81 -0
- package/dist/templates/boundaries/.eslintrc-frontend-spa.cjs.ejs +79 -0
- package/dist/templates/boundaries/.importlinter.ejs +48 -0
- package/dist/templates/boundaries/check-boundaries-go.mjs.ejs +94 -0
- package/dist/templates/boundaries/check-boundaries-python.mjs.ejs +118 -0
- package/dist/templates/boundaries/check-boundaries-rust.mjs.ejs +111 -0
- package/dist/templates/boundaries/check-boundaries.mjs.ejs +34 -0
- package/dist/templates/boundaries/clippy.toml.ejs +50 -0
- package/dist/templates/boundaries/deny.toml.ejs +56 -0
- package/dist/templates/boundaries/eslint.config.boundaries.mjs.ejs +112 -0
- package/dist/templates/boundaries/eslint.config.frontend-spa.mjs.ejs +93 -0
- package/dist/templates/boundaries/golangci-boundaries.yml.ejs +70 -0
- package/dist/templates/boundaries/ruff-boundaries.toml.ejs +32 -0
- package/dist/templates/claude/CLAUDE.md.ejs +78 -0
- package/dist/templates/claude/agents/agent-write-classes.json +30 -0
- package/dist/templates/claude/agents/bridge-reviewer.md.ejs +68 -0
- package/dist/templates/claude/agents/codebase-scanner.md.ejs +69 -0
- package/dist/templates/claude/agents/context-checker.md.ejs +73 -0
- package/dist/templates/claude/agents/red-team.md.ejs +134 -0
- package/dist/templates/claude/commands/close-gold-gap.md.ejs +43 -0
- package/dist/templates/claude/commands/drain.md.ejs +81 -0
- package/dist/templates/claude/commands/gold-audit.md.ejs +43 -0
- package/dist/templates/claude/commands/impact.md.ejs +39 -0
- package/dist/templates/claude/commands/levelup.md.ejs +61 -0
- package/dist/templates/claude/commands/ship.md.ejs +902 -0
- package/dist/templates/claude/commands/tabletop.md.ejs +46 -0
- package/dist/templates/claude/commands/task.md.ejs +87 -0
- package/dist/templates/claude/commands/wt-close.md.ejs +58 -0
- package/dist/templates/claude/commands/wt-list.md.ejs +48 -0
- package/dist/templates/claude/commands/wt-open.md.ejs +61 -0
- package/dist/templates/claude/commands/wt-prune.md.ejs +38 -0
- package/dist/templates/claude/hooks/check-circular-deps.mjs.ejs +56 -0
- package/dist/templates/claude/hooks/check-no-orphan-todo.mjs.ejs +45 -0
- package/dist/templates/claude/hooks/check-no-pii.mjs.ejs +121 -0
- package/dist/templates/claude/hooks/check-no-placeholders.mjs.ejs +71 -0
- package/dist/templates/claude/hooks/check-no-skipped-tests.mjs +80 -0
- package/dist/templates/claude/hooks/check-no-unused-exports.mjs +62 -0
- package/dist/templates/claude/hooks/closer-mode-guard.mjs.ejs +102 -0
- package/dist/templates/claude/hooks/debug-state-on-failure.mjs.ejs +98 -0
- package/dist/templates/claude/hooks/enforce-gate-before-pr.mjs +142 -0
- package/dist/templates/claude/hooks/enforce-read-only.mjs +28 -0
- package/dist/templates/claude/hooks/exitplanmode-banner.mjs.ejs +43 -0
- package/dist/templates/claude/hooks/guard-done-evidence.mjs.ejs +85 -0
- package/dist/templates/claude/hooks/guard-task-completion.mjs.ejs +56 -0
- package/dist/templates/claude/hooks/hooks.mjs.ejs +127 -0
- package/dist/templates/claude/hooks/lib.mjs.ejs +523 -0
- package/dist/templates/claude/hooks/post-brainstorm-stop.mjs +48 -0
- package/dist/templates/claude/hooks/post-commit-check.mjs.ejs +65 -0
- package/dist/templates/claude/hooks/post-commit-checklists/go/backend.ejs +4 -0
- package/dist/templates/claude/hooks/post-commit-checklists/go/docs.ejs +4 -0
- package/dist/templates/claude/hooks/post-commit-checklists/go/frontend.ejs +4 -0
- package/dist/templates/claude/hooks/post-commit-checklists/java/backend.ejs +4 -0
- package/dist/templates/claude/hooks/post-commit-checklists/java/docs.ejs +4 -0
- package/dist/templates/claude/hooks/post-commit-checklists/java/frontend.ejs +4 -0
- package/dist/templates/claude/hooks/post-commit-checklists/python/backend.ejs +4 -0
- package/dist/templates/claude/hooks/post-commit-checklists/python/docs.ejs +4 -0
- package/dist/templates/claude/hooks/post-commit-checklists/python/frontend.ejs +4 -0
- package/dist/templates/claude/hooks/post-commit-checklists/rust/backend.ejs +5 -0
- package/dist/templates/claude/hooks/post-commit-checklists/rust/docs.ejs +4 -0
- package/dist/templates/claude/hooks/post-commit-checklists/rust/frontend.ejs +4 -0
- package/dist/templates/claude/hooks/post-commit-checklists/ts/backend.ejs +4 -0
- package/dist/templates/claude/hooks/post-commit-checklists/ts/docs.ejs +4 -0
- package/dist/templates/claude/hooks/post-commit-checklists/ts/frontend.ejs +5 -0
- package/dist/templates/claude/hooks/post-edit-dispatch.mjs.ejs +73 -0
- package/dist/templates/claude/hooks/post-subagent-release.mjs +80 -0
- package/dist/templates/claude/hooks/pre-compact.mjs.ejs +79 -0
- package/dist/templates/claude/hooks/pre-edit-plan-anchor.mjs.ejs +225 -0
- package/dist/templates/claude/hooks/pre-edit-ssot-guard.mjs +204 -0
- package/dist/templates/claude/hooks/pre-spawn-worktree-guard.mjs +129 -0
- package/dist/templates/claude/hooks/skill-forced-eval.mjs.ejs +281 -0
- package/dist/templates/claude/hooks/stop-dangerous.mjs +86 -0
- package/dist/templates/claude/hooks/stop-evidence-guard.mjs.ejs +245 -0
- package/dist/templates/claude/hooks/stop-finding-loss.mjs.ejs +168 -0
- package/dist/templates/claude/hooks/wiki-on-commit.mjs.ejs +38 -0
- package/dist/templates/claude/knowledge-map.json.ejs +26 -0
- package/dist/templates/claude/rules/05-agent-lifecycle.md +7 -0
- package/dist/templates/claude/rules/25-todo-folder-policy.md +5 -0
- package/dist/templates/claude/rules/40-context-economy.md +32 -0
- package/dist/templates/claude/rules/45-mcp-fallback.md +25 -0
- package/dist/templates/claude/rules/50-batch-execution.md +75 -0
- package/dist/templates/claude/rules/55-brainstorm-terminal-state.md +44 -0
- package/dist/templates/claude/rules/60-incidental-capture.md +43 -0
- package/dist/templates/claude/rules/75-impact-vault-reading.md +43 -0
- package/dist/templates/claude/rules/90-exec-protocol.md.ejs +40 -0
- package/dist/templates/claude/rules/95-closer-mode.md +45 -0
- package/dist/templates/claude/settings.json.ejs +165 -0
- package/dist/templates/claude/skills/architect-review/SKILL.md.ejs +82 -0
- package/dist/templates/claude/skills/brainstorming/SKILL.md.ejs +86 -0
- package/dist/templates/claude/skills/clean-code/SKILL.md.ejs +39 -0
- package/dist/templates/claude/skills/close-gold-gap/SKILL.md.ejs +58 -0
- package/dist/templates/claude/skills/codebase-audit/SKILL.md.ejs +88 -0
- package/dist/templates/claude/skills/configure/SKILL.md.ejs +72 -0
- package/dist/templates/claude/skills/epic-decompose/SKILL.md.ejs +86 -0
- package/dist/templates/claude/skills/gold-audit/SKILL.md.ejs +91 -0
- package/dist/templates/claude/skills/impact/SKILL.md.ejs +104 -0
- package/dist/templates/claude/skills/levelup/SKILL.md.ejs +146 -0
- package/dist/templates/claude/skills/tabletop/SKILL.md.ejs +118 -0
- package/dist/templates/claude/skills/tdd/SKILL.md.ejs +157 -0
- package/dist/templates/claude/skills/understand-code/SKILL.md.ejs +93 -0
- package/dist/templates/claude/skills/verification/SKILL.md.ejs +68 -0
- package/dist/templates/claude/skills/wave-drain/SKILL.md.ejs +491 -0
- package/dist/templates/codex/CODEX.md.ejs +140 -0
- package/dist/templates/codex/codex-adapter.mjs +132 -0
- package/dist/templates/codex/config.toml.ejs +189 -0
- package/dist/templates/compliance/compliance-mapping.md.ejs +124 -0
- package/dist/templates/compliance/compliance-menu.md.ejs +89 -0
- package/dist/templates/contract-testing/CONTRACTS_POLICY.md.ejs +30 -0
- package/dist/templates/contract-testing/api-snapshots/config-keys-baseline.json.ejs +1 -0
- package/dist/templates/contract-testing/api-snapshots/config-response-baseline.json.ejs +1 -0
- package/dist/templates/contract-testing/api-snapshots/enum-values-baseline.json.ejs +1 -0
- package/dist/templates/contract-testing/api-snapshots/error-shape-baseline.json.ejs +6 -0
- package/dist/templates/contract-testing/api-snapshots/openapi-baseline.json.ejs +12 -0
- package/dist/templates/contract-testing/api-snapshots/openapi-content-types-baseline.json.ejs +3 -0
- package/dist/templates/contract-testing/api-snapshots/openapi-paths-baseline.json.ejs +1 -0
- package/dist/templates/contract-testing/api-snapshots/openapi-required-fields-baseline.json.ejs +1 -0
- package/dist/templates/contract-testing/api-snapshots/openapi-response-status-baseline.json.ejs +3 -0
- package/dist/templates/contract-testing/api-snapshots/test-snapshot.json.ejs +4 -0
- package/dist/templates/contract-testing/env/.env.pact.ejs +4 -0
- package/dist/templates/contract-testing/graphql/GraphqlSchemaTest.java.ejs +29 -0
- package/dist/templates/contract-testing/graphql/graphql-inspector.test.ts.ejs +20 -0
- package/dist/templates/contract-testing/graphql/graphql_schema_test.go.ejs +27 -0
- package/dist/templates/contract-testing/graphql/graphql_schema_test.rs.ejs +27 -0
- package/dist/templates/contract-testing/graphql/test_graphql_schema.py.ejs +21 -0
- package/dist/templates/contract-testing/grpc/GrpcContractTest.java.ejs +23 -0
- package/dist/templates/contract-testing/grpc/buf-breaking.yml.ejs +6 -0
- package/dist/templates/contract-testing/grpc/buf.yaml.ejs +11 -0
- package/dist/templates/contract-testing/grpc/grpc-contract.test.ts.ejs +16 -0
- package/dist/templates/contract-testing/grpc/grpc_contract_test.go.ejs +22 -0
- package/dist/templates/contract-testing/grpc/grpc_contract_test.rs.ejs +22 -0
- package/dist/templates/contract-testing/grpc/test_grpc_contract.py.ejs +18 -0
- package/dist/templates/contract-testing/message-queue/SchemaRegistryCheckIT.java.ejs +44 -0
- package/dist/templates/contract-testing/message-queue/schema-registry-check.ts.ejs +42 -0
- package/dist/templates/contract-testing/message-queue/schema_registry_test.go.ejs +71 -0
- package/dist/templates/contract-testing/message-queue/schema_registry_test.rs.ejs +61 -0
- package/dist/templates/contract-testing/message-queue/test_schema_registry.py.ejs +31 -0
- package/dist/templates/contract-testing/pact-samples/assignment-response.json.ejs +7 -0
- package/dist/templates/contract-testing/pact-samples/availability-response.json.ejs +13 -0
- package/dist/templates/contract-testing/pact-samples/availability-rule-response.json.ejs +8 -0
- package/dist/templates/contract-testing/pact-samples/capacity-response.json.ejs +11 -0
- package/dist/templates/contract-testing/pact-samples/fully-booked-response.json.ejs +6 -0
- package/dist/templates/contract-testing/pact-samples/schedule-override-response.json.ejs +7 -0
- package/dist/templates/contract-testing/rest-owned/PactVerificationIT.java.ejs +31 -0
- package/dist/templates/contract-testing/rest-owned/pact-consumer.test.ts.ejs +36 -0
- package/dist/templates/contract-testing/rest-owned/pact-deps.gradle.ejs +15 -0
- package/dist/templates/contract-testing/rest-owned/pact_consumer_test.go.ejs +48 -0
- package/dist/templates/contract-testing/rest-owned/pact_consumer_test.rs.ejs +23 -0
- package/dist/templates/contract-testing/rest-owned/test_pact_consumer.py.ejs +35 -0
- package/dist/templates/contract-testing/rest-public/OpenApiDiffIT.java.ejs +44 -0
- package/dist/templates/contract-testing/rest-public/export-openapi-java.gradle.ejs +13 -0
- package/dist/templates/contract-testing/rest-public/export-openapi.mjs.ejs +21 -0
- package/dist/templates/contract-testing/rest-public/export_openapi.go.ejs +30 -0
- package/dist/templates/contract-testing/rest-public/export_openapi.py.ejs +27 -0
- package/dist/templates/contract-testing/rest-public/export_openapi.rs.ejs +32 -0
- package/dist/templates/contract-testing/rest-public/openapi-diff.ts.ejs +42 -0
- package/dist/templates/contract-testing/rest-public/openapi_diff_test.go.ejs +35 -0
- package/dist/templates/contract-testing/rest-public/openapi_diff_test.rs.ejs +36 -0
- package/dist/templates/contract-testing/rest-public/test_openapi_diff.py.ejs +32 -0
- package/dist/templates/coverage/.coveragerc.ejs +26 -0
- package/dist/templates/coverage/.tarpaulin.toml.ejs +30 -0
- package/dist/templates/coverage/Cargo.toml.profile.release.ejs +24 -0
- package/dist/templates/coverage/jacoco-maven-setup.md.ejs +72 -0
- package/dist/templates/coverage/jacoco.gradle.ejs +49 -0
- package/dist/templates/coverage/kover.gradle.ejs +35 -0
- package/dist/templates/coverage/vitest.browser.config.ts.ejs +47 -0
- package/dist/templates/coverage/vitest.config.ts.ejs +47 -0
- package/dist/templates/docs/CODING_STANDARDS.md.ejs +81 -0
- package/dist/templates/docs/FEATURE_MATRIX.md.ejs +84 -0
- package/dist/templates/docs/GAP.md.ejs +58 -0
- package/dist/templates/docs/ISO27001_ANNEX_A.md.ejs +144 -0
- package/dist/templates/docs/MASTER_TEST_PLAN.md.ejs +52 -0
- package/dist/templates/docs/POST_MERGE_REVIEW_TEMPLATE.md.ejs +63 -0
- package/dist/templates/docs/SECURE_CODING_CHECKLIST.md.ejs +61 -0
- package/dist/templates/docs/adr/ADR-000_template.md.ejs +28 -0
- package/dist/templates/docs/bugs/bug-analysis.md.ejs +47 -0
- package/dist/templates/docs/bugs/bug-report.md.ejs +51 -0
- package/dist/templates/docs/bugs/bug-verification.md.ejs +42 -0
- package/dist/templates/docs/runbooks/deployment.md.ejs +119 -0
- package/dist/templates/docs/runbooks/prod-checklist.md.ejs +90 -0
- package/dist/templates/docs/runbooks/rollback.md.ejs +124 -0
- package/dist/templates/docs/runbooks/troubleshooting.md.ejs +130 -0
- package/dist/templates/docs/skeletons/arc42-canvas.md.ejs +46 -0
- package/dist/templates/docs/skeletons/arc42-full.md.ejs +59 -0
- package/dist/templates/docs/skeletons/decision-registry.md.ejs +38 -0
- package/dist/templates/docs/skeletons/er-model.md.ejs +19 -0
- package/dist/templates/docs/skeletons/glossary.md.ejs +8 -0
- package/dist/templates/docs/skeletons/governance.md.ejs +13 -0
- package/dist/templates/docs/skeletons/slo.md.ejs +21 -0
- package/dist/templates/docs/skeletons/technical-debt.md.ejs +8 -0
- package/dist/templates/docs/skeletons/test-strategy.md.ejs +14 -0
- package/dist/templates/docs/skeletons/threat-model-4q.md.ejs +20 -0
- package/dist/templates/docs/skeletons/threat-model-stride.md.ejs +22 -0
- package/dist/templates/docs/specs/atomic-task-list.md.ejs +57 -0
- package/dist/templates/docs/steering/product.md.ejs +44 -0
- package/dist/templates/docs/steering/structure.md.ejs +49 -0
- package/dist/templates/docs/steering/tech.md.ejs +53 -0
- package/dist/templates/documentation/cli-catalog.md.ejs +21 -0
- package/dist/templates/e2e/E2E_CONSTITUTION.md.ejs +77 -0
- package/dist/templates/e2e/playwright-python/conftest.py.ejs +22 -0
- package/dist/templates/e2e/playwright-python/run_axe.py.ejs +99 -0
- package/dist/templates/e2e/playwright-python/test_a11y.py.ejs +38 -0
- package/dist/templates/e2e/playwright-python/test_smoke.py.ejs +28 -0
- package/dist/templates/e2e/playwright-ts/a11y.spec.ts.ejs +20 -0
- package/dist/templates/e2e/playwright-ts/render-smoke.spec.ts.ejs +90 -0
- package/dist/templates/e2e/playwright-ts/run-axe.ts.ejs +74 -0
- package/dist/templates/e2e/smoke-journeys/journeys.spec.ts.ejs +53 -0
- package/dist/templates/evidence-files.json.ejs +13 -0
- package/dist/templates/frontend/FE_DESIGN_PRINCIPLES.md.ejs +115 -0
- package/dist/templates/frontend/FRONTEND_CONSTITUTION.md.ejs +170 -0
- package/dist/templates/frontend/design-tokens.json.ejs +107 -0
- package/dist/templates/frontend/vrt-setup.md.ejs +83 -0
- package/dist/templates/githooks/commit-msg.ejs +60 -0
- package/dist/templates/githooks/pre-commit.ejs +95 -0
- package/dist/templates/githooks/pre-push.ejs +251 -0
- package/dist/templates/githooks/setup-hooks.sh.ejs +8 -0
- package/dist/templates/github/PULL_REQUEST_TEMPLATE.md +37 -0
- package/dist/templates/github/PULL_REQUEST_TEMPLATE.md.ejs +52 -0
- package/dist/templates/github/actions/build-cache/action.yml.ejs +84 -0
- package/dist/templates/github/actions/nas-ssh/action.yml.ejs +53 -0
- package/dist/templates/github/actions/setup-java-maven/action.yml.ejs +53 -0
- package/dist/templates/github/actions/setup-node-pnpm/action.yml.ejs +39 -0
- package/dist/templates/github/actions/sign-and-attest/action.yml.ejs +53 -0
- package/dist/templates/github/dependabot.yml.ejs +66 -0
- package/dist/templates/github/extended-ci-paths.txt.ejs +38 -0
- package/dist/templates/github/issue-templates/bug-report.yml +72 -0
- package/dist/templates/github/issue-templates/compliance-item.yml.ejs +75 -0
- package/dist/templates/github/issue-templates/config.yml +2 -0
- package/dist/templates/github/issue-templates/epic.yml +66 -0
- package/dist/templates/github/issue-templates/feature-request.yml +24 -0
- package/dist/templates/github/issue-templates/task-brief.yml.ejs +125 -0
- package/dist/templates/github/labels.yml.ejs +161 -0
- package/dist/templates/github/scripts/sticky-failure-issue.sh.ejs +61 -0
- package/dist/templates/github/workflows/01-pr-fast.yml.ejs +1089 -0
- package/dist/templates/github/workflows/02-pr-extended.yml.ejs +512 -0
- package/dist/templates/github/workflows/03-human-approval.yml.ejs +76 -0
- package/dist/templates/github/workflows/04-deploy-test.yml.ejs +425 -0
- package/dist/templates/github/workflows/05-release.yml.ejs +813 -0
- package/dist/templates/github/workflows/06-nightly-lite.yml.ejs +125 -0
- package/dist/templates/github/workflows/06-nightly.yml.ejs +25 -0
- package/dist/templates/github/workflows/07-weekly-lite.yml.ejs +221 -0
- package/dist/templates/github/workflows/07-weekly.yml.ejs +23 -0
- package/dist/templates/github/workflows/08-monthly.yml.ejs +23 -0
- package/dist/templates/github/workflows/09-heartbeat.yml.ejs +268 -0
- package/dist/templates/github/workflows/10-deploy-prod.yml.ejs +144 -0
- package/dist/templates/github/workflows/11-k6-on-demand.yml.ejs +43 -0
- package/dist/templates/github/workflows/12-mutation-scheduled.yml.ejs +100 -0
- package/dist/templates/github/workflows/13-archunit-extended.yml.ejs +33 -0
- package/dist/templates/github/workflows/14-license-scan.yml.ejs +45 -0
- package/dist/templates/github/workflows/15-codeql.yml.ejs +117 -0
- package/dist/templates/github/workflows/16-frontend-quality.yml.ejs +154 -0
- package/dist/templates/github/workflows/17-ossf-scorecard.yml.ejs +46 -0
- package/dist/templates/github/workflows/18-frontend-lane.yml.ejs +50 -0
- package/dist/templates/github/workflows/_ai-draft-check.yml.ejs +44 -0
- package/dist/templates/github/workflows/_contract-postman.yml.ejs +93 -0
- package/dist/templates/github/workflows/_cosign-copy/aws-ecs.ejs +90 -0
- package/dist/templates/github/workflows/_cosign-copy/azure-container-app.ejs +97 -0
- package/dist/templates/github/workflows/_cosign-copy/gcp-cloud-run.ejs +102 -0
- package/dist/templates/github/workflows/_cosign-copy/ghcr.ejs +76 -0
- package/dist/templates/github/workflows/_cosign-copy/nas-compose.ejs +69 -0
- package/dist/templates/github/workflows/_deploy/aws-ecs.ejs +78 -0
- package/dist/templates/github/workflows/_deploy/azure-container-app.ejs +59 -0
- package/dist/templates/github/workflows/_deploy/gcp-cloud-run.ejs +71 -0
- package/dist/templates/github/workflows/_deploy/ghcr.ejs +19 -0
- package/dist/templates/github/workflows/_deploy/nas-compose.ejs +45 -0
- package/dist/templates/github/workflows/_k6-runner.yml.ejs +107 -0
- package/dist/templates/github/workflows/_label-on-approve.yml.ejs +44 -0
- package/dist/templates/github/workflows/_label-sync.yml.ejs +58 -0
- package/dist/templates/github/workflows/_monthly.yml.ejs +343 -0
- package/dist/templates/github/workflows/_nightly.yml.ejs +826 -0
- package/dist/templates/github/workflows/_notify.yml.ejs +96 -0
- package/dist/templates/github/workflows/_partials/macos-keychain-bypass.ejs +6 -0
- package/dist/templates/github/workflows/_partials/macos-keychain-restore.ejs +6 -0
- package/dist/templates/github/workflows/_partials/scheduled-heavy-jobs.ejs +117 -0
- package/dist/templates/github/workflows/_partials/sigstore-preflight.ejs +8 -0
- package/dist/templates/github/workflows/_partials/trivy-fs-java.ejs +27 -0
- package/dist/templates/github/workflows/_post-merge-notify.yml.ejs +228 -0
- package/dist/templates/github/workflows/_pr-staleness.yml.ejs +85 -0
- package/dist/templates/github/workflows/_shared-security.yml.ejs +132 -0
- package/dist/templates/github/workflows/_sigstore-retry-sign.yml.ejs +102 -0
- package/dist/templates/github/workflows/_weekly.yml.ejs +313 -0
- package/dist/templates/github/workflows/drift-shadow.yml.ejs +51 -0
- package/dist/templates/github/workflows/five-lane/ci.yml.ejs +54 -0
- package/dist/templates/github/workflows/five-lane/nightly.yml.ejs +70 -0
- package/dist/templates/github/workflows/five-lane/release.yml.ejs +53 -0
- package/dist/templates/github/workflows/five-lane/weekly.yml.ejs +72 -0
- package/dist/templates/github/workflows/issue-state.yml.ejs +93 -0
- package/dist/templates/global-invariants/GLOBAL_INVARIANTS.md.ejs +34 -0
- package/dist/templates/governance/RACI.md.ejs +21 -0
- package/dist/templates/governance/ci-mental-model.md.ejs +268 -0
- package/dist/templates/governance/contract-integrity-policy.md.ejs +116 -0
- package/dist/templates/governance/enterprise-compliance-baseline.md.ejs +111 -0
- package/dist/templates/governance/evidence-retention.md.ejs +43 -0
- package/dist/templates/governance/gdpr-erasure-runbook.md.ejs +165 -0
- package/dist/templates/governance/qa-audit-phases.md.ejs +129 -0
- package/dist/templates/governance/risk-assessment-template.md.ejs +105 -0
- package/dist/templates/governance/risk-register.md.ejs +76 -0
- package/dist/templates/governance/solo-dev-exception.md.ejs +96 -0
- package/dist/templates/governance/validation-evidence.md.ejs +131 -0
- package/dist/templates/infra/azure/containerapp.tpl.yaml.ejs +74 -0
- package/dist/templates/infra/nas-compose/deploy.sh.ejs +114 -0
- package/dist/templates/integration-testing/AbstractIntegrationTest.java.ejs +36 -0
- package/dist/templates/integration-testing/NoH2ArchTest.java.ejs +24 -0
- package/dist/templates/integration-testing/conftest.py.ejs +20 -0
- package/dist/templates/integration-testing/db_fixture.rs.ejs +48 -0
- package/dist/templates/integration-testing/eslint-no-fake-db.json.ejs +19 -0
- package/dist/templates/integration-testing/eslint.config.no-fake-db.mjs.ejs +46 -0
- package/dist/templates/integration-testing/main_test.go.ejs +53 -0
- package/dist/templates/integration-testing/main_test_sqlite.go.ejs +37 -0
- package/dist/templates/integration-testing/test-setup.ts.ejs +26 -0
- package/dist/templates/integration-testing/testcontainers-deps.gradle.ejs +9 -0
- package/dist/templates/java/DeprecationInterceptor.java.ejs +50 -0
- package/dist/templates/java/archunit-hexagonal.ejs +69 -0
- package/dist/templates/java/modulith/ApplicationModulesTest.java.ejs +21 -0
- package/dist/templates/java/modulith/modulith-deps.gradle.ejs +13 -0
- package/dist/templates/java/modulith/modulith-maven-setup.md.ejs +21 -0
- package/dist/templates/java/pharma/AuditEvent.java.ejs +138 -0
- package/dist/templates/java/pharma/AuditMapper.java.ejs +60 -0
- package/dist/templates/java/pharma/archunit-pharma.ejs +128 -0
- package/dist/templates/kit/GLOBAL_KIT.md.ejs +11 -0
- package/dist/templates/kit/dim.md.ejs +33 -0
- package/dist/templates/llms-txt.config.json.ejs +22 -0
- package/dist/templates/local-wrapper/.env.example.ejs +15 -0
- package/dist/templates/local-wrapper/Makefile.ejs +44 -0
- package/dist/templates/local-wrapper/run.sh.ejs +5 -0
- package/dist/templates/middleware/410-gone-handler.ts.ejs +41 -0
- package/dist/templates/middleware/correlation-id.ts.ejs +32 -0
- package/dist/templates/middleware/deprecation.ts.ejs +45 -0
- package/dist/templates/middleware/error-handler.ts.ejs +73 -0
- package/dist/templates/middleware/payload-size-limit.ts.ejs +51 -0
- package/dist/templates/mutation/README.md.ejs +67 -0
- package/dist/templates/mutation/cargo-mutants.toml.ejs +9 -0
- package/dist/templates/mutation/mutmut-config.toml.ejs +7 -0
- package/dist/templates/mutation/parse-mutants.mjs.ejs +44 -0
- package/dist/templates/mutation/parse-mutmut.py.ejs +44 -0
- package/dist/templates/mutation/pitest-l2-setup.md.ejs +66 -0
- package/dist/templates/mutation/pitest-maven-setup.md.ejs +64 -0
- package/dist/templates/mutation/pitest.gradle.ejs +23 -0
- package/dist/templates/mutation/stryker.conf.json.ejs +17 -0
- package/dist/templates/observability/setup.md.ejs +495 -0
- package/dist/templates/operations/handbook.md.ejs +217 -0
- package/dist/templates/perf/bundle-budget.json.ejs +29 -0
- package/dist/templates/perf/k6/reports/csv-report.py.ejs +91 -0
- package/dist/templates/perf/k6/reports/html-report.py.ejs +114 -0
- package/dist/templates/perf/k6/reports/json-report.py.ejs +74 -0
- package/dist/templates/perf/k6/scenarios/breakpoint.js.ejs +26 -0
- package/dist/templates/perf/k6/scenarios/burst.js.ejs +43 -0
- package/dist/templates/perf/k6/scenarios/endurance.js.ejs +36 -0
- package/dist/templates/perf/k6/scenarios/load.js.ejs +26 -0
- package/dist/templates/perf/k6/scenarios/ramp-down.js.ejs +29 -0
- package/dist/templates/perf/k6/scenarios/ramp-up.js.ejs +32 -0
- package/dist/templates/perf/k6/scenarios/smoke.js.ejs +23 -0
- package/dist/templates/perf/k6/scenarios/soak.js.ejs +26 -0
- package/dist/templates/perf/k6/scenarios/spike.js.ejs +29 -0
- package/dist/templates/perf/k6/scenarios/steady-state.js.ejs +31 -0
- package/dist/templates/perf/k6/scenarios/stress.js.ejs +29 -0
- package/dist/templates/perf/k6/scenarios/volume.js.ejs +36 -0
- package/dist/templates/perf/k6/seed/test-data.sql.ejs +59 -0
- package/dist/templates/perf/lighthouserc.json.ejs +41 -0
- package/dist/templates/quality/iso9001/CAPA_LOG.md.ejs +36 -0
- package/dist/templates/quality/iso9001/DOCUMENT_CONTROL.md.ejs +36 -0
- package/dist/templates/quality/iso9001/REQUIREMENTS_TRACEABILITY.md.ejs +61 -0
- package/dist/templates/regulated/overlay.json.ejs +26 -0
- package/dist/templates/regulated/regulated-overlay.md.ejs +40 -0
- package/dist/templates/resilience/RESILIENCE.md.ejs +310 -0
- package/dist/templates/root/.gitignore.ejs +85 -0
- package/dist/templates/root/CODEOWNERS.ejs +18 -0
- package/dist/templates/root/CONTRIBUTING.md.ejs +62 -0
- package/dist/templates/root/SECURITY.md.ejs +19 -0
- package/dist/templates/root/TEST_TAXONOMY.md.ejs +100 -0
- package/dist/templates/root/commitlint.config.js.ejs +4 -0
- package/dist/templates/root/docs/METHOD/CANONICAL_PATHS.md.ejs +24 -0
- package/dist/templates/root/docs/METHOD/ENGINEERING_DEFAULTS.md.ejs +136 -0
- package/dist/templates/root/docs/METHOD/KNOWLEDGE_MAP.md.ejs +90 -0
- package/dist/templates/root/docs/METHOD/PATTERNS_CATALOG.md.ejs +51 -0
- package/dist/templates/root/docs/METHOD/REUSE_REGISTRY.md.ejs +26 -0
- package/dist/templates/root/docs/METHOD/REUSE_REGISTRY_SPEC.md.ejs +80 -0
- package/dist/templates/root/docs/METHOD/SSOT_CORE_SET.md.ejs +62 -0
- package/dist/templates/root/docs/METHOD/TRACK_ROUTER.md.ejs +39 -0
- package/dist/templates/root/docs/SYSTEM/CANON.md.ejs +359 -0
- package/dist/templates/root/docs/SYSTEM/FAIL_CLOSED.md.ejs +171 -0
- package/dist/templates/root/editorconfig.ejs +39 -0
- package/dist/templates/root/tsconfig.json.ejs +21 -0
- package/dist/templates/rust/clippy.toml.ejs +14 -0
- package/dist/templates/schemas/milestone.schema.json.ejs +164 -0
- package/dist/templates/schemas/rtm-verdict.schema.json.ejs +52 -0
- package/dist/templates/schemas/source-record.schema.json.ejs +91 -0
- package/dist/templates/schemas/tabletop-evidence.schema.json.ejs +53 -0
- package/dist/templates/schemas/use-case.schema.json.ejs +59 -0
- package/dist/templates/scripts/apply-branch-protection.mjs.ejs +70 -0
- package/dist/templates/scripts/audit-toolchain.mjs.ejs +112 -0
- package/dist/templates/scripts/bg-run.sh.ejs +59 -0
- package/dist/templates/scripts/capacity-probe.mjs.ejs +147 -0
- package/dist/templates/scripts/capture-debt-baseline.mjs.ejs +99 -0
- package/dist/templates/scripts/check-acceptance.mjs.ejs +298 -0
- package/dist/templates/scripts/check-action-pins.mjs.ejs +215 -0
- package/dist/templates/scripts/check-agent-return.mjs.ejs +177 -0
- package/dist/templates/scripts/check-all.mjs.ejs +1746 -0
- package/dist/templates/scripts/check-anti-fake-green.mjs.ejs +80 -0
- package/dist/templates/scripts/check-anti-proforma.mjs.ejs +547 -0
- package/dist/templates/scripts/check-api-e2e.mjs.ejs +106 -0
- package/dist/templates/scripts/check-arc42-slots.mjs.ejs +25 -0
- package/dist/templates/scripts/check-assertion-delta.mjs.ejs +287 -0
- package/dist/templates/scripts/check-audit-dry-pass.mjs.ejs +176 -0
- package/dist/templates/scripts/check-bundle-size.mjs.ejs +151 -0
- package/dist/templates/scripts/check-canonical-paths.mjs.ejs +39 -0
- package/dist/templates/scripts/check-ci-tiers.mjs.ejs +111 -0
- package/dist/templates/scripts/check-claude-md-lint.mjs.ejs +142 -0
- package/dist/templates/scripts/check-collab-mode-wired.mjs.ejs +60 -0
- package/dist/templates/scripts/check-commit-footer-rationale.mjs.ejs +170 -0
- package/dist/templates/scripts/check-constraint-scan.mjs.ejs +681 -0
- package/dist/templates/scripts/check-consumer-audit.mjs.ejs +414 -0
- package/dist/templates/scripts/check-continue-on-error.mjs.ejs +227 -0
- package/dist/templates/scripts/check-cross-model-review.mjs.ejs +458 -0
- package/dist/templates/scripts/check-decision-registry.mjs.ejs +101 -0
- package/dist/templates/scripts/check-doc-freshness.mjs.ejs +17 -0
- package/dist/templates/scripts/check-doc-links.mjs.ejs +98 -0
- package/dist/templates/scripts/check-doc-set.mjs.ejs +18 -0
- package/dist/templates/scripts/check-docs.mjs.ejs +71 -0
- package/dist/templates/scripts/check-domain-api-surface.mjs.ejs +80 -0
- package/dist/templates/scripts/check-drift.mjs.ejs +167 -0
- package/dist/templates/scripts/check-duplication.mjs.ejs +45 -0
- package/dist/templates/scripts/check-e2e-escalation.mjs.ejs +199 -0
- package/dist/templates/scripts/check-e2e-quarantine.mjs.ejs +68 -0
- package/dist/templates/scripts/check-emission-parity.mjs.ejs +91 -0
- package/dist/templates/scripts/check-exit-code-contract.mjs.ejs +138 -0
- package/dist/templates/scripts/check-fail-closed-audit.mjs.ejs +389 -0
- package/dist/templates/scripts/check-fe-boundaries.mjs.ejs +184 -0
- package/dist/templates/scripts/check-feature-matrix.mjs.ejs +835 -0
- package/dist/templates/scripts/check-frontend-lane.mjs.ejs +73 -0
- package/dist/templates/scripts/check-grace-window.mjs.ejs +102 -0
- package/dist/templates/scripts/check-handoff-doc.mjs.ejs +192 -0
- package/dist/templates/scripts/check-hook-routing.mjs.ejs +224 -0
- package/dist/templates/scripts/check-image-pins.mjs.ejs +90 -0
- package/dist/templates/scripts/check-inline-suppressions.mjs.ejs +153 -0
- package/dist/templates/scripts/check-iso9001.mjs.ejs +146 -0
- package/dist/templates/scripts/check-knowledge-map.mjs.ejs +49 -0
- package/dist/templates/scripts/check-local-ci-parity.mjs.ejs +382 -0
- package/dist/templates/scripts/check-m16-handoff.mjs.ejs +112 -0
- package/dist/templates/scripts/check-merge-method.mjs.ejs +92 -0
- package/dist/templates/scripts/check-milestones.mjs.ejs +678 -0
- package/dist/templates/scripts/check-min-test-execution.mjs.ejs +225 -0
- package/dist/templates/scripts/check-muted-test.mjs.ejs +270 -0
- package/dist/templates/scripts/check-no-stub-redirects.mjs.ejs +154 -0
- package/dist/templates/scripts/check-no-tracked-artifacts.mjs.ejs +134 -0
- package/dist/templates/scripts/check-oracle-discrimination.mjs.ejs +244 -0
- package/dist/templates/scripts/check-pii-scan.mjs.ejs +67 -0
- package/dist/templates/scripts/check-pipe-tee-hazard.mjs.ejs +85 -0
- package/dist/templates/scripts/check-pr-size-gate.mjs.ejs +85 -0
- package/dist/templates/scripts/check-refutation-verdicts.mjs.ejs +256 -0
- package/dist/templates/scripts/check-regulated-overlay.mjs.ejs +158 -0
- package/dist/templates/scripts/check-render-smoke.mjs.ejs +100 -0
- package/dist/templates/scripts/check-reuse-registry.mjs.ejs +44 -0
- package/dist/templates/scripts/check-review-completion.mjs.ejs +708 -0
- package/dist/templates/scripts/check-safety-adopt-ratchet.mjs.ejs +142 -0
- package/dist/templates/scripts/check-secret-presence.mjs.ejs +191 -0
- package/dist/templates/scripts/check-secret-scan.mjs.ejs +82 -0
- package/dist/templates/scripts/check-skip-critical-e2e.mjs.ejs +142 -0
- package/dist/templates/scripts/check-smoke-journeys.mjs.ejs +272 -0
- package/dist/templates/scripts/check-solo-reactivation.mjs.ejs +102 -0
- package/dist/templates/scripts/check-sources.mjs.ejs +245 -0
- package/dist/templates/scripts/check-ssot-core.mjs.ejs +45 -0
- package/dist/templates/scripts/check-stack-conformity.mjs.ejs +160 -0
- package/dist/templates/scripts/check-stride-traceability.mjs.ejs +101 -0
- package/dist/templates/scripts/check-suppression-expiry.mjs.ejs +79 -0
- package/dist/templates/scripts/check-suppression-rationale.mjs.ejs +76 -0
- package/dist/templates/scripts/check-suppressions.mjs.ejs +150 -0
- package/dist/templates/scripts/check-tabletop-evidence.mjs.ejs +499 -0
- package/dist/templates/scripts/check-tdd-evidence.mjs.ejs +381 -0
- package/dist/templates/scripts/check-test-naming.mjs.ejs +151 -0
- package/dist/templates/scripts/check-test-pyramid.mjs.ejs +170 -0
- package/dist/templates/scripts/check-test-scope-tier.mjs.ejs +208 -0
- package/dist/templates/scripts/check-tier-coverage.mjs.ejs +71 -0
- package/dist/templates/scripts/check-todo-max-age.mjs.ejs +205 -0
- package/dist/templates/scripts/check-touched-vs-manifest.mjs.ejs +179 -0
- package/dist/templates/scripts/check-unwired-guards.mjs.ejs +367 -0
- package/dist/templates/scripts/check-use-cases.mjs.ejs +345 -0
- package/dist/templates/scripts/check-validator-helptext.mjs.ejs +80 -0
- package/dist/templates/scripts/check-wiki-lint.mjs.ejs +162 -0
- package/dist/templates/scripts/check-workflow-docs-sync.mjs.ejs +91 -0
- package/dist/templates/scripts/check-workflow-job-naming.mjs.ejs +102 -0
- package/dist/templates/scripts/check-workflow-perms.mjs.ejs +54 -0
- package/dist/templates/scripts/check-workflow-runners.mjs.ejs +87 -0
- package/dist/templates/scripts/check-workflow-sha-pinning.mjs.ejs +89 -0
- package/dist/templates/scripts/check-workflow-test-integrity.mjs.ejs +235 -0
- package/dist/templates/scripts/checks/check-rust-no-unsafe.mjs.ejs +58 -0
- package/dist/templates/scripts/checks/check-rust-no-unwrap.mjs.ejs +61 -0
- package/dist/templates/scripts/ci-classify-changes.mjs.ejs +119 -0
- package/dist/templates/scripts/conformance.mjs.ejs +37 -0
- package/dist/templates/scripts/constraint-map.json.ejs +25 -0
- package/dist/templates/scripts/debt-lib.mjs.ejs +1015 -0
- package/dist/templates/scripts/debt-report.mjs.ejs +199 -0
- package/dist/templates/scripts/domain-api-surface.json.ejs +23 -0
- package/dist/templates/scripts/done-evidence.mjs.ejs +359 -0
- package/dist/templates/scripts/evidence-collect.mjs.ejs +264 -0
- package/dist/templates/scripts/evidence-prune.mjs.ejs +96 -0
- package/dist/templates/scripts/evidence-rotate.mjs.ejs +53 -0
- package/dist/templates/scripts/evidence-writer.sh.ejs +220 -0
- package/dist/templates/scripts/gate-registry.yml.ejs +286 -0
- package/dist/templates/scripts/gen-doc-index.mjs.ejs +19 -0
- package/dist/templates/scripts/gen-gap.mjs.ejs +153 -0
- package/dist/templates/scripts/gen-llms-txt.mjs.ejs +18 -0
- package/dist/templates/scripts/gen-wiki.mjs.ejs +196 -0
- package/dist/templates/scripts/gh-cleanup-expired-artifacts.mjs.ejs +134 -0
- package/dist/templates/scripts/gold-audit.mjs.ejs +19 -0
- package/dist/templates/scripts/i18n-literal-scanner.mjs.ejs +175 -0
- package/dist/templates/scripts/ingest-zap-report.mjs.ejs +107 -0
- package/dist/templates/scripts/inject-pact-samples.sh.ejs +96 -0
- package/dist/templates/scripts/issue-readiness.mjs.ejs +84 -0
- package/dist/templates/scripts/knowledge-map-update.mjs.ejs +32 -0
- package/dist/templates/scripts/lib/acceptance-criteria.mjs.ejs +213 -0
- package/dist/templates/scripts/lib/agent-return-validate.mjs.ejs +481 -0
- package/dist/templates/scripts/lib/coverage-gate.mjs.ejs +54 -0
- package/dist/templates/scripts/lib/e2e-reliability.mjs.ejs +207 -0
- package/dist/templates/scripts/lib/ephemeral-server.mjs.ejs +148 -0
- package/dist/templates/scripts/lib/evidence-binding.mjs.ejs +140 -0
- package/dist/templates/scripts/lib/exact-sha-policy.mjs.ejs +233 -0
- package/dist/templates/scripts/lib/gate-args.mjs.ejs +20 -0
- package/dist/templates/scripts/lib/gate-evidence.mjs.ejs +622 -0
- package/dist/templates/scripts/lib/gate-mutex.mjs.ejs +340 -0
- package/dist/templates/scripts/lib/glob-walk.mjs.ejs +148 -0
- package/dist/templates/scripts/lib/run-helpers.mjs.ejs +416 -0
- package/dist/templates/scripts/lib/seed-common.sh.ejs +105 -0
- package/dist/templates/scripts/lib/waiter-count.mjs.ejs +62 -0
- package/dist/templates/scripts/muted-tests-baseline.json.ejs +4 -0
- package/dist/templates/scripts/optional-emissions.json.ejs +117 -0
- package/dist/templates/scripts/oracle-discrimination-baseline.json.ejs +6 -0
- package/dist/templates/scripts/pid-watch.sh.ejs +78 -0
- package/dist/templates/scripts/pii-scan.mjs.ejs +125 -0
- package/dist/templates/scripts/pr-merge-watch.mjs.ejs +445 -0
- package/dist/templates/scripts/record-agent-return.mjs.ejs +279 -0
- package/dist/templates/scripts/record-journey-evidence.mjs.ejs +69 -0
- package/dist/templates/scripts/rework-log.mjs.ejs +172 -0
- package/dist/templates/scripts/run-postman-tests.sh.ejs +112 -0
- package/dist/templates/scripts/schemas/agent-return-external.schema.json +74 -0
- package/dist/templates/scripts/schemas/agent-return.schema.json.ejs +181 -0
- package/dist/templates/scripts/schemas/cross-model-dispatch.schema.json +95 -0
- package/dist/templates/scripts/seed-test-data.sh.ejs +99 -0
- package/dist/templates/scripts/self-validation.mjs.ejs +126 -0
- package/dist/templates/scripts/setup-repo.sh.ejs +78 -0
- package/dist/templates/scripts/spotbugs-baseline.json.ejs +4 -0
- package/dist/templates/scripts/test-gate-layering.mjs.ejs +92 -0
- package/dist/templates/scripts/validate-api-snapshots.mjs.ejs +86 -0
- package/dist/templates/scripts/validate-k6-scenarios.mjs.ejs +96 -0
- package/dist/templates/scripts/validate-openapi-field-types.mjs.ejs +124 -0
- package/dist/templates/scripts/validate-postman-collection.mjs.ejs +159 -0
- package/dist/templates/scripts/verify-fe-coverage.mjs.ejs +138 -0
- package/dist/templates/scripts/verify-i18n-parity.mjs.ejs +114 -0
- package/dist/templates/scripts/verify-module-coverage.mjs.ejs +207 -0
- package/dist/templates/scripts/verify-mutation-baseline.mjs.ejs +209 -0
- package/dist/templates/scripts/verify-spotbugs.mjs.ejs +216 -0
- package/dist/templates/scripts/verify-tokens.mjs.ejs +177 -0
- package/dist/templates/security/RISK_ASSESSMENT.md.ejs +35 -0
- package/dist/templates/security/STRIDE.md.ejs +24 -0
- package/dist/templates/security/gitleaks.toml.ejs +21 -0
- package/dist/templates/security/zap/baseline-auth.context.ejs +58 -0
- package/dist/templates/security/zap/rules.tsv.ejs +100 -0
- package/dist/templates/ship/TICK_PROMPT.md.ejs +55 -0
- package/dist/templates/ship/supervisor.sh.ejs +46 -0
- package/dist/templates/smoke-journeys/manifest.json.ejs +1 -0
- package/dist/templates/sonar-project.properties.ejs +25 -0
- package/dist/templates/standards/doc-profile.ejs +27 -0
- package/dist/templates/standards/gold-doc-set.yml.ejs +528 -0
- package/dist/templates/standards/gold-registry.java.yml.ejs +266 -0
- package/dist/templates/standards/gold-registry.typescript.yml.ejs +104 -0
- package/dist/templates/standards/gold-registry.yml.ejs +119 -0
- package/dist/templates/standards/thresholds.yml.ejs +86 -0
- package/dist/templates/static-analysis/.dependency-cruiser.cjs.ejs +78 -0
- package/dist/templates/static-analysis/.golangci.yml.ejs +70 -0
- package/dist/templates/static-analysis/.shellcheckrc.ejs +13 -0
- package/dist/templates/static-analysis/.yamllint.yml.ejs +41 -0
- package/dist/templates/static-analysis/checkstyle.xml.ejs +83 -0
- package/dist/templates/static-analysis/detekt.yml.ejs +26 -0
- package/dist/templates/static-analysis/eslint.config.mjs.ejs +54 -0
- package/dist/templates/static-analysis/eslint.config.static.mjs.ejs +38 -0
- package/dist/templates/static-analysis/eslintrc-static.json.ejs +30 -0
- package/dist/templates/static-analysis/jscpd.json.ejs +25 -0
- package/dist/templates/static-analysis/knip.json.ejs +26 -0
- package/dist/templates/static-analysis/pmd-ruleset.xml.ejs +92 -0
- package/dist/templates/static-analysis/prettierignore.ejs +59 -0
- package/dist/templates/static-analysis/prettierrc.json.ejs +10 -0
- package/dist/templates/static-analysis/requirements-dev.txt.ejs +11 -0
- package/dist/templates/static-analysis/ruff.toml.ejs +40 -0
- package/dist/templates/static-analysis/rustfmt.toml.ejs +3 -0
- package/dist/templates/static-analysis/spotbugs-exclude.xml.ejs +120 -0
- package/dist/templates/static-analysis/spotbugs.gradle.ejs +18 -0
- package/dist/templates/static-analysis/spotless.gradle.ejs +38 -0
- package/dist/templates/static-analysis/stylelintrc.json.ejs +16 -0
- package/dist/templates/suppressions/archunit-baseline.json.ejs +1 -0
- package/dist/templates/suppressions/consumer-audit-allowlist.json.ejs +1 -0
- package/dist/templates/suppressions/gitleaksignore.ejs +20 -0
- package/dist/templates/suppressions/pii-allowlist.json.ejs +1 -0
- package/dist/templates/suppressions/suppressions-schema.json.ejs +39 -0
- package/dist/templates/suppressions/trivyignore.ejs +17 -0
- package/dist/templates/testing/test-taxonomy.md.ejs +120 -0
- package/dist/test/example.behavioral.test.js +2 -0
- package/dist/types/memory.d.ts +19 -0
- package/dist/types/memory.js +1 -0
- package/dist/types/plan.d.ts +102 -0
- package/dist/types/plan.js +2 -0
- package/dist/types/plugin.d.ts +45 -0
- package/dist/types/plugin.js +1 -0
- package/dist/utils/canon-loader.js +2 -0
- package/dist/utils/channel.js +1 -0
- package/dist/utils/config.d.ts +79 -0
- package/dist/utils/config.js +5 -0
- package/dist/utils/confirm-downgrade.js +5 -0
- package/dist/utils/env.js +2 -0
- package/dist/utils/error-catalog.js +2 -0
- package/dist/utils/errors.js +2 -0
- package/dist/utils/evidence-log.js +3 -0
- package/dist/utils/file-lock.js +2 -0
- package/dist/utils/first-run.js +8 -0
- package/dist/utils/fs.js +3 -0
- package/dist/utils/github-issue-helper.js +4 -0
- package/dist/utils/gradle.js +21 -0
- package/dist/utils/json-output.js +2 -0
- package/dist/utils/logger-config.js +4 -0
- package/dist/utils/logger.js +3 -0
- package/dist/utils/maturity-check.js +2 -0
- package/dist/utils/pkg.js +3 -0
- package/dist/utils/platform.js +2 -0
- package/dist/utils/plugin-loader.js +2 -0
- package/dist/utils/plugin-worker.js +2 -0
- package/dist/utils/prettier-format.js +2 -0
- package/dist/utils/profiler.js +2 -0
- package/dist/utils/render.js +2 -0
- package/dist/utils/replay.js +3 -0
- package/dist/utils/run-cli.js +6 -0
- package/dist/utils/run-id.js +3 -0
- package/dist/utils/safe-read.js +2 -0
- package/dist/utils/task-id.js +2 -0
- package/dist/utils/today.js +2 -0
- package/dist/utils/tty.js +2 -0
- package/dist/utils/walk-dir.js +2 -0
- package/dist/verify/rules/drive-by-scope.js +1 -0
- package/dist/verify/rules/orphan-todos.js +1 -0
- package/dist/verify/rules/registry.js +1 -0
- package/dist/verify/rules/skip-patterns.js +1 -0
- package/dist/verify/rules/types.d.ts +15 -0
- package/dist/verify/rules/types.js +1 -0
- package/dist/verify/rules/ui-language.js +1 -0
- package/dist/verify/run.js +3 -0
- package/dist/wizard/archetype-defaults.js +1 -0
- package/dist/wizard/presets.js +1 -0
- package/dist/wizard/prompts.js +44 -0
- package/dist/wizard/types.d.ts +637 -0
- package/dist/wizard/types.js +1 -0
- package/dist/worktree/harvest.js +2 -0
- package/dist/worktree/links.js +2 -0
- package/dist/worktree/paths.js +2 -0
- package/dist/worktree/validate.js +3 -0
- package/package.json +124 -0
- package/scripts/check-arc42-slots.mjs +738 -0
- package/scripts/check-doc-freshness.mjs +236 -0
- package/scripts/check-doc-set.mjs +243 -0
- package/scripts/check-doc-style.mjs +228 -0
- package/scripts/gold-audit.mjs +319 -0
- package/scripts/lib/doc-gate-allowlist.mjs +123 -0
- package/scripts/lib/doc-set-resolve.mjs +274 -0
- package/scripts/lib/glob-walk.mjs +148 -0
- package/scripts/lib/gold-audit-lib.mjs +1260 -0
- package/scripts/lib/run-helpers.mjs +413 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const INVARIANT_CATALOG=[{id:"INV-01",tier:"architectural",title:"No circular dependencies between modules",description:"Circular imports create tight coupling and make modules impossible to test in isolation. Every module must have a clear single direction of dependency. Detected by static analysis in CI.",alwaysActive:!0,enforcement:"CI (madge / go vet / cargo check / pylint)"},{id:"INV-02",tier:"architectural",title:"Public API surface must be intentional \u2014 no accidental exports",description:"Every publicly exported symbol is a commitment to callers. Exporting by default leaks implementation details and makes refactoring costly. Only intentionally designed symbols may be public.",alwaysActive:!0,enforcement:"CI (Knip / cargo doc / PMD / unused linters)"},{id:"INV-03",tier:"architectural",title:"Architecture boundary enforcement",description:"Layers must respect their dependency direction. Domain/business logic must not import from infrastructure or adapters. Boundary violations break testability and portability.",languageDetail:{typescript:"Layer boundaries enforced \u2014 domain code must not import from infrastructure layers",java:"Hexagonal architecture \u2014 domain must not import from adapters or infrastructure",rust:"Module visibility enforced \u2014 `pub(crate)` preferred over `pub` for internal APIs",go:"Package boundaries enforced \u2014 minimize `internal/` escapes and cross-layer imports",python:"Module boundaries enforced \u2014 no circular imports, layered architecture respected",unknown:"Architecture boundary enforcement \u2014 layers must respect their dependency direction"},alwaysActive:!0,enforcement:"CI (ArchUnit / architecture linters / manual)"},{id:"INV-04",tier:"architectural",title:"Language-specific type safety",description:"Each language has idioms for expressing and enforcing type safety at compile or lint time. Bypassing these mechanisms removes the safety net and allows runtime errors that the type system was designed to prevent.",languages:["typescript","java","rust","go","python"],languageDetail:{typescript:"No `any` type in TypeScript \u2014 use `unknown` and narrow, or create proper types",java:"No raw types \u2014 generics must be parameterized (e.g. `List<String>` not `List`)",rust:"No `.unwrap()` calls in library code \u2014 use `?` or explicit error handling",go:"Explicit error handling required \u2014 no silenced errors with blank identifier",python:"Type annotations required on all public function signatures"},alwaysActive:!0,enforcement:"hook + CI (ESLint no-explicit-any / cargo clippy / ruff / golangci-lint)"},{id:"INV-05",tier:"architectural",title:"Complexity limits enforced",description:"High cyclomatic complexity correlates with defect density and makes code harder to test. Functions and methods must stay within complexity bounds to ensure readability and testability.",languages:["typescript","java","rust","go","python"],languageDetail:{typescript:"Cyclomatic complexity \u2264 15 (ESLint `complexity` rule)",java:"Cyclomatic complexity \u2264 15, method length \u2264 65 LOC (PMD CyclomaticComplexity)",rust:"Cognitive complexity enforced via `clippy::pedantic` lint level",go:"Cyclomatic complexity \u2264 15 (`gocyclo` via golangci-lint)",python:"Cyclomatic complexity \u2264 15 (`ruff C901` rule)"},alwaysActive:!0,enforcement:"CI (ESLint / PMD / clippy / golangci-lint / ruff)"},{id:"INV-06",tier:"architectural",title:"No unused or dead code",description:"Dead code is a maintenance liability \u2014 it misleads readers, bloats binaries, and may hide latent bugs. All exported symbols and dependencies must be actively used.",languages:["typescript","java","rust","go","python"],languageDetail:{typescript:"No unused exports (Knip dead code analysis, zero findings)",java:"No unused code (PMD UnusedCode rules, zero violations)",rust:"No dead code (`#[warn(dead_code)]` via clippy, zero warnings)",go:"No unused code (`deadcode,unused` via golangci-lint, zero findings)",python:"No unused imports or variables (ruff `F401,F811`, zero findings)"},alwaysActive:!0,enforcement:"CI (Knip / PMD / clippy / golangci-lint / ruff)"},{id:"INV-07",tier:"data",title:"Schema changes via versioned migrations only \u2014 no manual DDL",description:"Database schema must evolve through versioned migration files (e.g. Flyway, Alembic, golang-migrate). Direct ALTER TABLE in application code, ORM auto-schema, or manual production changes are forbidden. Every schema state must be reproducible from migration history.",alwaysActive:!1,enforcement:"CI (migration lint / manual review)"},{id:"INV-08",tier:"data",title:"Input validation at system boundaries",description:"All data entering the system from external sources (HTTP requests, message queues, files, user input) must be validated and rejected if malformed before reaching domain logic. Never trust external input.",alwaysActive:!1,enforcement:"code review / integration tests"},{id:"INV-09",tier:"data",title:"Audit trail for mutable entities",description:"Every create, update, or delete on business-critical entities must be auditable. Changes must be traceable to the acting user, timestamp, and prior state.",alwaysActive:!1,minGovernanceLevel:"L2",enforcement:"code review / integration tests"},{id:"INV-10",tier:"data",title:"Soft delete preferred over hard delete",description:"Business data should be logically deleted (e.g. `deleted_at` timestamp, `is_active` flag) rather than physically removed. Hard delete is permitted only for explicit GDPR erasure flows. Queries must exclude soft-deleted records by default.",alwaysActive:!1,enforcement:"code review / ArchUnit or equivalent"},{id:"INV-11",tier:"security",title:"No secrets in source code",description:"API keys, passwords, tokens, and other secrets must never appear in source files, configuration files committed to version control, or log output. Use environment variables or secret managers (Vault, AWS Secrets Manager, etc.).",alwaysActive:!0,minGovernanceLevel:"L1",enforcement:"CI (gitleaks secrets scan \u2014 security-early-fail job, runs before lint-and-test); local gate: `node scripts/check-secret-scan.mjs` (L1 baseline) + `gitleaks detect --source . --baseline-path suppressions/.gitleaksignore` (L2 block)"},{id:"INV-12",tier:"security",title:"No PII in code, tests, or logs",description:"Personally identifiable information (emails, phone numbers, credit card numbers) must not appear in source code, test fixtures, or log output. Mask or redact PII before logging. Required for GDPR/NIS2 compliance. Enforced by static scan (pii-scan.mjs) as a HARD early-fail gate \u2014 no grace-period exception applies.",alwaysActive:!0,minGovernanceLevel:"L1",enforcement:"CI (pii-scan.mjs \u2014 security-early-fail job, HARD early-fail, no grace period); local gate: `node scripts/pii-scan.mjs` runs before all L1 checks; Claude hook: check-no-pii.mjs (PostToolUse, Edit|Write)"},{id:"INV-13",tier:"security",title:"Dependencies scanned for known vulnerabilities",description:"All third-party dependencies must be scanned for CVEs before each release. High-severity vulnerabilities (CVSS \u2265 7.0) block deployment. Dependency updates must be reviewed for breaking changes.",alwaysActive:!0,minGovernanceLevel:"L2",enforcement:"CI dep-audit step per stack \u2014 TypeScript: `npm audit --omit=dev --audit-level=high`; Rust: rustsec/audit-check action; Java/Kotlin: trivy fs --scanners vuln --severity HIGH,CRITICAL --exit-code 1 (suppressions: .trivyignore); Go: golang/govulncheck-action; Python: pip-audit. Local gate (L2 block): same commands, soft: graceActive"},{id:"INV-14",tier:"security",title:"No dynamic code execution with untrusted input",description:"Dynamic code or shell execution using untrusted input is a critical injection vulnerability. Never pass user-controlled data to dynamic evaluation or shell-execution mechanisms without sanitization.",languages:["typescript","java","rust","go","python"],languageDetail:{typescript:"No dynamic code evaluation (`eval` function or `Function` constructor) with untrusted input",java:"No shell command execution (Runtime, ProcessBuilder) with unsanitized user input",rust:"No unsafe FFI calls or shell execution with untrusted data",go:"No shell execution with unsanitized user input",python:"No dynamic code evaluation (`eval`/`exec` builtins) or subprocess calls with untrusted input"},alwaysActive:!0,minGovernanceLevel:"L2",enforcement:"code review / SAST (semgrep / CodeQL)"},{id:"INV-15",tier:"security",title:"Authentication required at every entry point",description:"Every API endpoint, message consumer, and job scheduler must authenticate the caller unless explicitly designated as public. Default-deny authentication; explicit opt-out requires ADR approval.",alwaysActive:!0,minGovernanceLevel:"L2",enforcement:"code review / integration tests"},{id:"INV-16",tier:"operational",title:"Structured logging only \u2014 no raw print statements in production",description:"Production code must use a structured logging library that emits machine-parseable output (JSON). Raw print statements bypass log level filtering, structured fields, and correlation IDs. Every log entry should include `traceId` where applicable.",languages:["typescript","java","rust","go","python"],languageDetail:{typescript:"No `console.log/warn/error` in production code \u2014 use structured logger",java:"No `System.out.println()` in production code \u2014 use SLF4J with structured output",rust:"No `println!` or `eprintln!` in library code \u2014 use `tracing` or `log` crate",go:"No `fmt.Print*` in production code \u2014 use `slog` or structured logger",python:"No `print()` in production code \u2014 use `logging` module with structured formatter"},alwaysActive:!1,enforcement:"hook + CI (lint rules / grep / ESLint no-console)"},{id:"INV-17",tier:"operational",title:"Explicit error handling \u2014 panics and unhandled errors are forbidden",description:"Unhandled errors and unexpected panics cause silent data corruption or opaque crashes. Every error path must be explicitly handled, logged, or propagated with context.",languages:["typescript","java","rust","go","python"],languageDetail:{typescript:"Unhandled Promise rejections forbidden \u2014 always `.catch()` or `await` in `try/catch`",java:"Empty catch blocks forbidden \u2014 exceptions must be handled or rethrown with context",rust:"`panic!` forbidden in library code \u2014 use `Result<T, E>` for error propagation",go:"`recover()` must log and reraise, never silently discard panics",python:"Bare `except:` clause forbidden \u2014 always specify exception type and log the error"},alwaysActive:!1,enforcement:"hook + CI (lint rules / clippy)"},{id:"INV-18",tier:"operational",title:"No hardcoded environment values",description:"Configuration values that differ between environments (URLs, ports, timeouts, feature flags) must be externalized via environment variables or configuration files, never hardcoded in source. This enables environment parity and safe deployments.",alwaysActive:!1,enforcement:"code review / SAST"},{id:"INV-19",tier:"operational",title:"Resilient external calls \u2014 circuit breaker or retry required",description:"Every outbound call to an external service (HTTP, database, queue) must be wrapped in a resilience pattern: retry with exponential backoff and/or circuit breaker. Unbounded blocking calls and missing timeouts cause cascading failures.",alwaysActive:!1,minGovernanceLevel:"L2",enforcement:"code review / integration tests"},{id:"INV-20",tier:"operational",title:"Health and readiness endpoints required for deployed services",description:"Every deployed service must expose `/health` (liveness) and `/ready` (readiness) endpoints that orchestrators (Kubernetes, ECS, etc.) can probe. These endpoints must reflect actual dependency health, not just process uptime.",alwaysActive:!1,minGovernanceLevel:"L2",enforcement:"integration tests / deployment checks"},{id:"INV-21",tier:"governance",title:"Every TODO comment must reference a task ID: `TODO(#NNN)`",description:"Orphan TODOs without a task ID cannot be tracked or prioritized and accumulate as invisible tech debt. Every TODO must link to a trackable issue so it can be scheduled or explicitly deferred.",alwaysActive:!0,enforcement:"hook (check-no-orphan-todo.mjs) + CI"},{id:"INV-22",tier:"governance",title:"Branch naming: `task/#NNN-description`",description:"Consistent branch naming enables automatic linking between code and issue trackers, makes CI filtering predictable, and allows automated branch cleanup.",alwaysActive:!0,enforcement:"pre-push hook / CI branch name check"},{id:"INV-23",tier:"governance",title:"No direct commits to `main` \u2014 all changes via task branches + PR",description:"Direct commits to the main branch bypass code review, CI validation, and the PR discussion record. All changes must flow through a PR from a task branch.",alwaysActive:!0,enforcement:"branch protection (GitHub) / pre-push hook"},{id:"INV-24",tier:"governance",title:"Gate must pass before commit: `node scripts/check-all.mjs L1`",description:"The L1 gate (lint + unit tests) is the minimum bar for any commit. Committing broken code wastes reviewer time and breaks other developers' workflows.",alwaysActive:!0,enforcement:"pre-commit hook / CI"},{id:"INV-25",tier:"governance",title:"Gate must pass before push: `node scripts/check-all.mjs L2`",description:"The L2 gate (L1 + coverage + integration tests) verifies that the feature works end-to-end before others are affected. Pushing broken code blocks the team.",alwaysActive:!0,enforcement:"pre-push hook / CI"},{id:"INV-26",tier:"governance",title:"TDD mandatory \u2014 test first, then implement",description:"Test-driven development forces explicit design thinking before coding and produces code that is testable by construction. Writing tests after the fact often results in tests written to pass rather than tests that document expected behavior. Evidence is recorded via `arbiter task record-red` and verified via `arbiter verify tdd`.",alwaysActive:!0,minGovernanceLevel:"L2",enforcement:"gate (scripts/check-all.mjs L2 \u2014 arbiter verify tdd) + task advance --to green gate"},{id:"INV-27",tier:"governance",title:"Evidence artifacts must be generated for all gate runs",description:"At L4 (compliance grade), every gate execution must produce machine-readable evidence artifacts (coverage reports, lint results, test output) that can be archived and reviewed by auditors. Gate runs without artifacts are non-compliant.",alwaysActive:!0,minGovernanceLevel:"L4",enforcement:"CI (evidence collection step)"},{id:"INV-28",tier:"governance",title:"SSOT documents must not contradict \u2014 run drift check before merge",description:"At L3, SSOT (Single Source of Truth) documents (AGENTS.md, architecture docs, API contracts) must stay consistent. Contradictions between governance documents create ambiguity for agents and humans alike.",alwaysActive:!0,minGovernanceLevel:"L3",enforcement:"CI (drift check / pre-merge hook)"},{id:"INV-29",tier:"architectural",title:"No MockMvc \u2014 use RestAssured for integration tests (Java)",description:"MockMvc tests the controller layer through a mock servlet container, bypassing real HTTP serialization, filter chains, exception handlers, and content negotiation. Bugs in those layers pass MockMvc and break in production. RestAssured tests the full HTTP stack with a real embedded server, providing genuine end-to-end confidence.",languages:["java"],alwaysActive:!0,enforcement:"hook (check-no-mockmvc.mjs) + ArchUnit (NoMockMvcTest.java) + policy"},{id:"INV-30",tier:"operational",title:"Mutation testing required \u2014 PIT/pitest (Java, L2+)",description:"Line coverage measures which lines execute but not whether tests verify behavior. A suite can reach 90% coverage with assertions that check nothing meaningful. Mutation testing (PIT/pitest) injects faults into production code and verifies tests fail \u2014 proving genuine fault-detection power. Thresholds: 80% mutation score, 85% line coverage. Scope: domain and application layers only (not adapters/controllers).",languages:["java"],alwaysActive:!1,minGovernanceLevel:"L2",enforcement:"generated: 05-release.yml mutation-blocking job + generated pitest config"},{id:"INV-31",tier:"governance",title:"Suppressions must have mandatory expiry",description:"Every suppression entry \u2014 both file-based (.trivyignore, .gitleaksignore, pii-allowlist.json, archunit-baseline.json) and inline comment directives (arbiter-suppress(INV-NN, until=YYYY-MM-DD, reason=..., owner=@handle)) \u2014 must carry mandatory metadata: reason (\u226510 chars), owner (@github-handle), and expiresAt/until (ISO date). Entries with a past expiry block the L1 gate. There are no permanent suppressions \u2014 waivers must be renewed or removed when the underlying issue is resolved.",alwaysActive:!0,enforcement:"CI gate (scripts/check-suppressions.mjs + scripts/check-inline-suppressions.mjs \u2014 L1) + pre-commit hook"},{id:"INV-32",tier:"governance",title:"Every 'proven' language must have a nightly real-project fixture",description:"Arbiter's cross-language-matrix.json tracks tool maturity per language. A 'proven' rating implies the tool chain works end-to-end on real projects. Every language that carries at least one 'proven' cell must have a corresponding fixture under __tests__/fixtures/real-projects/ so the nightly real-project-matrix workflow can exercise the full arbiter pipeline (init \u2192 verify \u2192 check-all) against it. Promoting a language to 'proven' without a fixture is rejected by the L1 gate.",alwaysActive:!0,selfOnly:!0,enforcement:"CI gate (scripts/check-matrix-fixtures.mjs \u2014 L1) + nightly real-project-matrix workflow"},{id:"INV-33",tier:"governance",title:"L4 merges require valid evidence with obs_gate == PASS",description:"L4 governance mandates structured, machine-checkable evidence of deep validation before merging. The evidence harness (scripts/evidence-collect.mjs) writes .evidence/SUMMARY.json carrying the required schema {head_sha, head_sha_short, obs_gate, tests, coverage, mutation, security} plus a canonical sha field. A merge is blocked when obs_gate !== 'PASS', which indicates that tests failed, coverage dropped below threshold, mutation score is insufficient, or critical security findings exist. The L4 gate runs `arbiter verify evidence` which: (1) validates the schema via src/evidence/summary.ts, (2) verifies the embedded sha, (3) confirms head_sha matches `git rev-parse HEAD`, and (4) requires obs_gate === 'PASS'. Any failure blocks merge.",alwaysActive:!0,minGovernanceLevel:"L4",enforcement:"check-all.mjs L3+ evidence-gate block (reads .evidence/SUMMARY.json) + generated scripts/evidence-collect.mjs producer (#2278) + src/evidence/summary.ts validator"},{id:"INV-34",tier:"data",title:"Integration tests must use real database (L2+)",description:"Integration tests must execute against a real database via Testcontainers at L2+. In-memory databases (H2, SQLite in-memory mode) are forbidden as a substitute for a real database engine. Tests that pass against an in-memory store may fail against the production database due to SQL dialect differences, constraint handling, and index behaviour.",alwaysActive:!1,minGovernanceLevel:"L2",enforcement:"check-all.mjs integration test step (L2+ when hasDatabase=true); anti-fake-DB gates: ArchUnit NoH2ArchTest (Java), ESLint no-restricted-imports (TypeScript), ruff F401-style check (Python); generated by src/generators/integration-testing.ts"},{id:"INV-35",tier:"operational",title:"Contract testing enforced when contractType is active",description:"When contractType !== 'none', contract tests must run in CI at L2+ and are a HARD gate. Failures block merge. Supported contract types: rest-owned, rest-public, graphql, grpc, message-queue. Each type generates the appropriate tooling (Pact, graphql-inspector, buf) and CI job.",alwaysActive:!1,minGovernanceLevel:"L2",enforcement:"check-all.mjs L2 contract gate + CI contract-verify job; generated by src/generators/contract-testing.ts"},{id:"INV-36",tier:"governance",title:"Hook hardness manifest \u2014 every hook must declare intent; HARD hooks must empirically block",description:"Every hook in src/templates/claude/hooks/ must be declared in .arbiter/hooks-manifest.json, and every hook in the project\u2019s own materialized .claude/hooks/ in .arbiter/self-hooks-manifest.json, with an explicit classification (HARD | ADVISORY). HARD hooks must empirically exit 2 \u2014 the only blocking code under the Claude Code hook protocol \u2014 on a canonical violation fixture. Any hook file without a manifest entry, or any HARD hook that fails to block, fails CI. This prevents silent ceremony regression \u2014 where a hook is declared hard but silently exits 0 (or exits 1, which prints without blocking). Both surfaces are required: #2324 was a defect present ONLY in the materialized copy, invisible to every template-scoped check (#2326).",alwaysActive:!0,selfOnly:!0,enforcement:"L1 gate (scripts/check-hardness-inventory.mjs) \u2014 drift and empirical exit-code assertions on every CI run"},{id:"INV-37",tier:"governance",title:"Generated githooks",description:"generateGithooks emits executable .githooks/{pre-commit,pre-push,commit-msg} for every supported language stack. Hooks must run L1/L2 gates respectively.",alwaysActive:!0,enforcement:"src/generators/githooks.ts (generator) + __tests__/generators/githooks.test.ts + __tests__/integration/githooks-generation.test.ts"},{id:"INV-38",tier:"governance",title:"Phase-tracked lifecycle enforcement",description:'Task lifecycle phase transitions are validated mechanically: completion guard exits 2 on premature claim (returns stderr to Claude as error context), pre-commit blocks commits during preflight/plan phases, and arbiter task advance validates forward-only transitions with audit log. Evidence guard (guard-done-evidence.mjs.ejs) additionally blocks done claims until SHA-pinned evidence (.claude/.last-done-evidence.json) is present, all_green, and SHAs match current tree. Evidence is captured by running node scripts/done-evidence.mjs (runs L2 gate + pins source SHAs). For archetypes that produce a running artifact (#1368): backend-web-db requires a reality_contact block with suite="live-api-e2e" and passed=true; frontend-spa requires suite in [render-smoke, visual-regression] with passed=true. A done/release claim without this archetype-appropriate reality-contact proof is blocked (exit 2) -- green unit tests alone are insufficient proof of working software for these archetypes.',alwaysActive:!0,enforcement:"src/templates/claude/hooks/guard-task-completion.mjs.ejs (exit 2) + src/templates/claude/hooks/guard-done-evidence.mjs.ejs (exit 2, SHA-pin + reality-contact validation) + src/templates/scripts/done-evidence.mjs.ejs (evidence capture CLI) + src/templates/githooks/pre-commit.ejs (phase guard) + src/commands/task.ts (advance validator)"},{id:"INV-39",tier:"governance",title:"Hook templates require empirical fire-tests",description:"Every Claude Code hook template in src/templates/claude/hooks/ must have at least one empirical fire-test in __tests__/hooks/empirical/. Adding a hook template without a corresponding test is a gate violation.",alwaysActive:!0,selfOnly:!0,enforcement:"__tests__/hooks/empirical/hook-fires.test.ts (22 tests covering all 14 hook templates)"},{id:"INV-40",tier:"operational",title:"BDD scenarios with @ignore tag are HARD-fail",description:"Generated check-all.mjs must scan feature files for the @ignore tag before running BDD scenarios. Any @ignore-tagged scenario causes the gate to exit non-zero immediately (soft: false), regardless of grace period. Ignored scenarios are dead specs \u2014 they silently pass and give false confidence about coverage.",alwaysActive:!1,enforcement:"src/templates/scripts/check-all.mjs.ejs (@ignore grep block, soft: false) + src/templates/behavioral-tests/bdd/example.feature.ejs (no @ignore in shipped example)"},{id:"INV-41",tier:"operational",title:"Message-queue contract tests must call Schema Registry testCompatibility",description:"Schema Registry contract tests must invoke testCompatibility() against the registered schema, not merely check reachability (HTTP-200 on /subjects). The compatibility level must be BACKWARD or FULL. A test that only GETs /subjects is not a contract test \u2014 it is a health check.",alwaysActive:!1,enforcement:"src/templates/contract-testing/message-queue/schema-registry-check.ts.ejs + src/templates/contract-testing/message-queue/SchemaRegistryCheckIT.java.ejs + src/templates/contract-testing/message-queue/schema_registry_test.go.ejs + src/templates/contract-testing/message-queue/test_schema_registry.py.ejs + src/templates/contract-testing/message-queue/schema_registry_test.rs.ejs"},{id:"INV-42",tier:"operational",title:"Pact broker glue must be env-gated; no silent runs against default URL",description:"Generated Pact contract gates in check-all.mjs and CI workflows must be wrapped in a PACT_BROKER_BASE_URL environment check. When the variable is unset the step skips with a visible log line. When set, PACT_BROKER_TOKEN is forwarded to the Gradle/Maven/npx process as a system property or env var. No hardcoded broker URL is permitted.",alwaysActive:!1,enforcement:"src/templates/scripts/check-all.mjs.ejs (env-gate block around Pact runCheck calls) + src/templates/github/workflows/01-pr-fast.yml.ejs (if: vars.PACT_BROKER_BASE_URL != '' + env: block) + src/templates/contract-testing/rest-owned/pact-deps.gradle.ejs (conditional system props)"},{id:"INV-43",tier:"operational",title:"OpenAPI exporter must run before diff; missing reference is HARD-fail",description:"Generated OpenAPI diff tests must not silently skip when spec files are missing. If contracts/openapi-current.yaml is absent, the test fails HARD (exporter was not run). If contracts/openapi-reference.yaml is absent, the test fails HARD unless ALLOW_OPENAPI_BOOTSTRAP=1 is set (first-run escape hatch). A test that silently passes with missing files is not a contract test \u2014 it is dead code.",alwaysActive:!1,enforcement:"src/templates/contract-testing/rest-public/openapi-diff.ts.ejs + src/templates/contract-testing/rest-public/OpenApiDiffIT.java.ejs + src/templates/contract-testing/rest-public/openapi_diff_test.go.ejs + src/templates/contract-testing/rest-public/test_openapi_diff.py.ejs + src/templates/contract-testing/rest-public/openapi_diff_test.rs.ejs"},{id:"INV-44",tier:"security",languages:["java"],title:"SpotBugs security-category bugs MUST NEVER be suppressed or baselined",description:"Java projects generated by arbiter receive scripts/verify-spotbugs.mjs which enforces a hard-block list of security bug types that fail the gate immediately, regardless of baseline state or --update-baseline flag. The hard-block list covers: SQL_INJECTION, SQL_INJECTION_SPRING_JDBC, XSS_REQUEST_WRAPPER, XSS_SERVLET, COMMAND_INJECTION, XXE_DOCUMENT, XXE_XMLREADER, LDAP_INJECTION, HARD_CODE_PASSWORD. Non-security findings may be baselined in spotbugs-baseline.json after review.",alwaysActive:!0,minGovernanceLevel:"L2",enforcement:"src/templates/scripts/verify-spotbugs.mjs.ejs (SECURITY_HARD_BLOCK set + exit 1) + src/templates/scripts/check-all.mjs.ejs (spotbugs baseline check at L2, Java)"},{id:"INV-45",tier:"governance",title:"Self-dogfood check \u2014 every EJS template must render to match its materialized self-repo file",description:"Every EJS template under src/templates/claude/ or src/templates/ship/ must render (with arbiter's own config) to content that matches the corresponding materialized .claude/ or .arbiter/ship file (fail-closed: a template with no materialized counterpart is drift, not a skip). Additionally, for src/templates/github/workflows/*.ejs vs .github/workflows/*.yml and src/templates/scripts/check-*.mjs.ejs vs scripts/check-*.mjs (R-02, #1900), every basename present on BOTH sides is diffed \u2014 these two families are emitted conditionally (archetype \xD7 governanceLevel \xD7 collaborationMode) so basename presence on only one side is not drift. Files listed in .dogfood-divergences.json are explicitly exempted (intentional arbiter-internal extensions not appropriate for target projects) \u2014 CANON-14: the entry pins the exact approved diff hash, so NEW drift inside an allowlisted file still fails. Config-gated templates are skipped when the relevant feature flag is disabled in arbiter.json. This invariant prevents arbiter from shipping stale template skeletons \u2014 or a stale self-CI \u2014 that diverge from its own governance without an explicit documented reason (the #1877/#1894 drift class).",alwaysActive:!0,selfOnly:!0,enforcement:"scripts/check-self-dogfood.mjs (L1 gate check, promoted from L2 by #1744) \u2014 exits 1 on unexpected drift"},{id:"INV-46",tier:"architectural",title:"Anti-bloat enforcement \u2014 Survey gate + duplication detector + LOC ratchet",description:"Before any new file is written under src/, a valid Existing Code Survey block must exist in the active plan (Target anchor, Decision keyword, \u22653 evidence rows, \u2265200-char Rationale). The pre-edit hook hard-blocks (exit 2) any Write that lacks the survey. L2: jscpd detects code duplication above 5% threshold. L1: check-bloat-ratchet.mjs enforces file-count and LOC ceilings per src/ bucket (default +10%/+5 files; src/templates tighter at +5%/+3 files). Bypass surfaces: ARBITER_PLAN_BYPASS=1 (Survey gate) and ALLOW_BLOAT=1 (ratchet), both session-scoped and documented in CONTRIBUTING.md.",alwaysActive:!0,selfOnly:!0,enforcement:".claude/hooks/pre-edit-plan-anchor.mjs (CANON-16 Survey gate, exit 2) + scripts/check-bloat-ratchet.mjs (L1 ratchet) + scripts/check-duplication.mjs (L2 duplication, fail-closed jscpd v5 wrapper, see .jscpd.json) + scripts/debt-report.mjs duplicationPercentage ratchet (CANON-22, see INV-109)"},{id:"INV-47",tier:"governance",title:"Matrix proven cell requires a gate invocation in check-all.mjs.ejs",description:"Every tool cell marked 'proven' in src/compatibility/cross-language-matrix.json must produce a concrete invocation step in src/templates/scripts/check-all.mjs.ejs at the correct gate level (L1, L2, or L3). Pre-existing gaps (e.g. mutation testing) are tracked in .matrix-proven-cells-exceptions.json with TODO references to the wiring issue.",alwaysActive:!0,selfOnly:!0,enforcement:"scripts/check-matrix-proven-cells.mjs (L1 gate)"},{id:"INV-48",tier:"governance",title:"EJS template render-test coverage must not regress",description:"Every template file under src/templates/ should be asserted by at least one test in __tests__/templates/ that renders the template and checks concrete output strings. Enforced via ratchet: the count of untested EJS files must not exceed the committed baseline (.template-tests-baseline.txt). Run with --update-baseline when adding tests.",alwaysActive:!0,selfOnly:!0,enforcement:"scripts/check-template-tests.mjs (L1 ratchet gate)"},{id:"INV-49",tier:"governance",title:"Every generator in src/generators/ must have a unit test",description:"Every file under src/generators/ requires a corresponding __tests__/generators/*.test.ts covering the happy path, idempotency, and at least one negative case. Untested generators can silently emit wrong governance content into target projects.",alwaysActive:!0,selfOnly:!0,enforcement:"scripts/check-generator-tests.mjs (L1 gate)"},{id:"INV-50",tier:"governance",title:"Every command in src/commands/ must have a test",description:"Every file under src/commands/ requires at least one corresponding __tests__/commands/*.test.ts (prefix-match: review.ts is covered by review-code.test.ts). CLI commands are the user entry point; untested commands cannot be refactored safely.",alwaysActive:!0,selfOnly:!0,enforcement:"scripts/check-command-tests.mjs (L1 gate)"},{id:"INV-51",tier:"governance",title:"Every catalog invariant must appear in AGENTS.md \xA7Invariants",description:"Every invariant in src/invariants/catalog.ts must have a matching entry in AGENTS.md \xA7Invariants. AGENTS.md is the canonical governance document read by all AI agents and new contributors. Invariants that exist only in code are invisible to the governance layer.",alwaysActive:!0,selfOnly:!0,enforcement:"scripts/check-catalog-agents-parity.mjs (L1 gate)"},{id:"INV-52",tier:"governance",title:"Catalog enforcement script citations must be wired in check-all.mjs",description:"If the enforcement field of a catalog invariant references a scripts/*.mjs file, that script must be called in scripts/check-all.mjs. Claimed enforcement that is not wired is a false guarantee \u2014 callers of the gate will believe it checks something it does not.",alwaysActive:!0,selfOnly:!0,enforcement:"scripts/check-inv-enforcement-wired.mjs (L1 gate)"},{id:"INV-53",tier:"governance",title:"Exit-code universal contract \u2014 every Arbiter-emitted script exits 0=PASS / 1=FAIL / 2=ERROR",description:`Every script emitted by Arbiter (scripts/*.mjs, src/templates/scripts/*.ejs) must use exactly three exit codes: 0=PASS, 1=FAIL, 2=ERROR. That is: 0 for success, 1 for detected failure, 2 for invocation error (bad arguments, missing required inputs, environment not ready). Decided for the whole family (#2553, #2593): a tracked SSOT the author controls that cannot be parsed is the author's artifact \u2014 1, reported as "unreadable SSOT"; a scan root that exists but resolves to zero files is a wiring defect \u2014 1; a root that is missing or not a directory, an unloadable schema, a missing tool or an unexpected throw are the gate's inability to run \u2014 2. Any other exit code is a violation. This contract makes every gate composable: callers can distinguish a clean run (0), a caught violation (1), and an unconfigured/broken environment (2) without parsing output. Enforced by scripts/check-exit-code-contract.mjs (L1 gate) which scans all emitted scripts and fails on any process.exit(N) where N \u2209 {0, 1, 2}. The self-validation drill (scripts/self-validation.mjs, L2) proves the contract holds by running each gate against clean, drift, and error fixtures and asserting the expected exit.`,alwaysActive:!0,enforcement:"scripts/check-exit-code-contract.mjs (L1 gate) \u2014 exits 1 on violation; scripts/self-validation.mjs (L2 A/B/C drill) \u2014 exits 1 if any gate fails its proof"},{id:"INV-54",tier:"governance",title:"SSOT core set integrity \u2014 all listed files must exist",description:"Every file listed in docs/METHOD/SSOT_CORE_SET.md must exist on disk. The gate exits 1 if any listed file is missing. Bootstrap mode: if SSOT_CORE_SET.md itself is absent, the gate exits 0 and skips.",alwaysActive:!0,enforcement:"scripts/check-ssot-core.mjs (L1 gate, #255)"},{id:"INV-55",tier:"governance",title:"Doc-links integrity \u2014 all markdown links must resolve",description:"Every local markdown link in docs/ must resolve to an existing file. Before failing, the gate checks CANONICAL_PATHS.md for a redirect alias. Links in .docs-links-ignore are exempt. Bootstrap mode: if no docs/ files are found, the gate exits 0.",alwaysActive:!0,enforcement:"scripts/check-doc-links.mjs (L1 gate, #255)"},{id:"INV-56",tier:"governance",title:"Knowledge-map freshness \u2014 line counts must not drift beyond tolerance",description:"RETIRED (#1244): the bespoke knowledge-map (its index doc, updater, and freshness gate) was removed; Obsidian now reads the generated wiki. No line-count tolerance is enforced any longer.",alwaysActive:!0,status:"retired",retiredReason:"Bespoke knowledge-map deleted in #1244 (index doc + updater + freshness gate); the generated wiki replaces it. No successor invariant."},{id:"INV-57",tier:"governance",title:"Canonical-paths integrity \u2014 all redirect targets must exist",description:"Every redirect target in docs/METHOD/CANONICAL_PATHS.md must exist on disk. A dangling alias (target missing) causes the gate to exit 1. Bootstrap mode: if CANONICAL_PATHS.md is absent, the gate exits 0.",alwaysActive:!0,enforcement:"scripts/check-canonical-paths.mjs (L1 gate, #255)"},{id:"INV-58",tier:"governance",title:"Node version SSOT \u2014 .nvmrc is canonical; all CI jobs use node-version-file",description:".nvmrc at the repo root is the single source of truth for the Node.js version. All GitHub Actions workflows must use `node-version-file: '.nvmrc'` \u2014 never a literal version pin. The same applies to all EJS templates that emit CI workflows. process.version major at runtime must match .nvmrc major. Enforced by scripts/check-node-version-ssot.mjs (L1 gate, #470).",alwaysActive:!0,enforcement:"scripts/check-node-version-ssot.mjs (L1 gate, #470)"},{id:"INV-59",tier:"governance",title:"Gate result parity \u2014 local L1 static gates must produce the same pass/fail pattern as CI",description:"check-all.mjs emits a gate result JSON to .arbiter/gate/local-result.json on every run (schema: arbiter-gate-v1). The parityContentHash field is a sha256 of the static L1 gate subset (27 gates, sorted by name; excludes commitlint, docs, unit tests which differ structurally between local and CI environments). The CI gate-aggregation job runs check-all.mjs L1 --json gate-result.json and uploads it as an artifact named gate-result. scripts/check-local-ci-parity.mjs (L2 gate) downloads the latest CI artifact via gh CLI and compares parityContentHash. A mismatch means local and CI disagree on a static check \u2014 a prerequisite violation for features.soloDevMode (#470). Skip (exit 0) when gh CLI is unavailable or no CI artifact exists.",alwaysActive:!0,enforcement:"scripts/check-local-ci-parity.mjs (L2 gate, #470) \u2014 exits 1 on hash mismatch; scripts/check-all.mjs --json flag emits the artifact that check-local-ci-parity.mjs reads"},{id:"INV-60",tier:"operational",languages:["rust"],minGovernanceLevel:"L2",title:"Release binary size capped at archetype default",description:"For Rust archetypes that emit an executable (cli, embedded), the release binary at target/release/<name> must stay under the archetype-default size budget: cli \u2192 10 MB, embedded \u2192 5 MB. Defaults are inlined in the src/generators/coverage.ts and src/generators/check-all.ts generators and are passed to the generated check-all.mjs via the binarySizeBytes data field. The generated script runs the size assertion in the L2 Rust block and skips gracefully when the release binary has not been built (e.g. on a freshly-cloned repo).",alwaysActive:!1,enforcement:"Generated scripts/check-all.mjs L2 step (#359, Phase 7G); generated docs/coverage/Cargo.toml.profile.release block (opt-level=s, lto, codegen-units=1, strip=symbols, panic=abort) keeps binaries within the cap"},{id:"INV-61",tier:"operational",languages:["typescript","python"],minGovernanceLevel:"L2",title:"a11y critical violations are HARD-fail at L2",description:"For web archetypes (frontend-spa, backend-web-db) the generated a11y wrapper runs the full WCAG 2.2 AA tag set (wcag2a + wcag2aa + wcag21a + wcag21aa + wcag22aa) and raises/throws on any violation whose impact is `critical` OR unclassified (impact === null/undefined/None). serious / moderate / minor violations are logged without raising \u2014 they remain evidence but do not block the gate. WCAG 2.2 AA adds: target-size (2.5.8 \u226524\xD724px), focus-appearance (2.4.11), accessible-auth (3.3.8 no cognitive test). Downstream projects can ratchet stricter by extending the wrapper (#1127). TypeScript: generated tests/e2e/a11y/run-axe.ts (@axe-core/playwright, #349). Python: generated tests/e2e/a11y/run_axe.py (axe-playwright-python>=0.1.7, resultTypes=[violations], #1149). Matrix: a11y \xD7 typescript = proven; a11y \xD7 python = beta. Other languages have no browser surface (unavailable).",alwaysActive:!1,enforcement:"TypeScript: generated tests/e2e/a11y/run-axe.ts throws on critical/unclassified \u2014 Playwright surfaces as failed spec, fails L2 playwright-e2e gate in scripts/check-all.mjs. Python: generated tests/e2e/a11y/run_axe.py raises on critical/unclassified \u2014 pytest collects tests/e2e/test_a11y.py, fails the L2 pytest-playwright e2e gate step in scripts/check-all.mjs (#1149)."},{id:"INV-62",tier:"architectural",title:"Frontend state separation \u2014 async (server) and sync (UI) state in distinct stores",description:"Async state fetched from a server (API responses, remote queries) must live in a dedicated async-state layer (e.g. TanStack Query, SWR, RTK Query). Synchronous UI state (modal open, selected tab, form draft) must live in a separate sync-state store (e.g. Zustand, Redux slice, React context). Mixing both in a single store conflates cache invalidation with UI transitions and makes optimistic updates error-prone.",alwaysActive:!1,optInGroup:"extended",enforcement:"code review / manual"},{id:"INV-63",tier:"governance",title:"SSOT atomic update \u2014 code and SSOT documentation land in the same commit",description:"When a code change alters an invariant, decision, or process rule, the corresponding SSOT document (AGENTS.md, docs/ADR/, docs/SYSTEM/CANON.md, docs/METHOD/) must be updated in the same commit. Split commits where code lands first and docs follow later create a window where the SSOT is stale and misleads reviewers and future agents.",alwaysActive:!1,optInGroup:"extended",enforcement:"code review / manual"},{id:"INV-64",tier:"governance",title:"No magic code \u2014 non-trivial idioms documented in a pattern catalog",description:'Any non-obvious pattern, workaround, or architectural idiom introduced into the codebase must be documented in a project-level pattern catalog (e.g. docs/PATTERNS/ or docs/METHOD/). "Magic" code that works for unclear reasons and is not documented creates maintenance risk when the original author is unavailable.',alwaysActive:!1,optInGroup:"extended",enforcement:"code review / manual"},{id:"INV-65",tier:"architectural",title:"Platform abstraction \u2014 env-specific APIs accessed only via adapter",description:"APIs that differ across deployment environments (browser vs. Node, local vs. cloud, test vs. prod) must be accessed through an adapter layer, not called directly in business logic. Direct calls to env-specific APIs (window, process.env, cloud SDKs) in domain code make testing harder and limit portability.",alwaysActive:!1,optInGroup:"extended",enforcement:"code review / manual"},{id:"INV-66",tier:"governance",title:"Process self-documentation \u2014 docs/METHOD/ is canonical for process rules",description:"All process rules, workflow conventions, and team agreements must be persisted under docs/METHOD/ as the authoritative source. Ad-hoc rules communicated only in PRs, Slack, or verbal agreements are not canonical and will be lost. The docs/METHOD/ directory is the single source of truth for how the team works.",alwaysActive:!1,optInGroup:"extended",enforcement:"code review / manual"},{id:"INV-67",tier:"data",title:"No internal mocking in E2E \u2014 backend endpoints are exercised against the real service",description:"End-to-end tests must exercise backend endpoints against a real running service (local or staging), not an internal mock or stub. Mocking the server within the E2E layer defeats the purpose of integration testing and hides contract mismatches. Use contract tests or test-doubles at the unit level; keep E2E real.",alwaysActive:!1,optInGroup:"extended",enforcement:"code review / manual"},{id:"INV-68",tier:"operational",title:"MCP-first forensic inspection \u2014 debug via MCP tools before raw shell",description:"When investigating a running system (querying state, inspecting logs, reading metrics), prefer MCP tool invocations over raw shell commands. MCP tools are auditable, repeatable, and safe by design. Raw shell commands issued ad-hoc during an incident bypass audit trails and increase the risk of accidental state mutation.",alwaysActive:!1,optInGroup:"extended",enforcement:"code review / manual"},{id:"INV-69",tier:"governance",title:"Design rationale traceability \u2014 new abstractions cite their motivating ADR",description:"Every new module, interface, or architectural abstraction introduced into the codebase must cite the ADR (Architecture Decision Record) that motivated it, either in a code comment, the PR description, or a linked docs/ADR/ entry. Abstractions without traceable rationale accumulate as unexplained complexity over time.",alwaysActive:!1,optInGroup:"extended",enforcement:"code review / manual"},{id:"INV-70",tier:"architectural",title:"Reuse before new \u2014 canonical registry search precedes creating a new module",description:"Before creating a new utility, helper, or module, the author must search the canonical registry (src/utils/, src/generators/, shared libs) for an existing equivalent. The search must be documented in the PR or plan. Creating a new module when an equivalent already exists produces duplication that diverges over time.",alwaysActive:!1,optInGroup:"extended",enforcement:"code review / manual"},{id:"INV-71",tier:"governance",title:"Track D task completion \u2014 docs-only changes follow the documented completion rules",description:"Documentation-only changes (Track D tasks) must follow the completion checklist defined in docs/METHOD/: update the relevant SSOT, verify no dangling references, and confirm the docs gate passes. Treating docs PRs as lower-ceremony than code PRs leads to stale documentation and broken cross-references.",alwaysActive:!1,optInGroup:"extended",enforcement:"code review / manual"},{id:"INV-72",tier:"governance",title:"File-lock semantics \u2014 process-bound exclusive lock with bootId + pid + cmd",description:"Long-running commands that mutate `.arbiter/` MUST acquire `.arbiter/.lock` via `src/utils/file-lock.ts` (acquireLock) before any state mutation, and release it on completion or crash. The lock records pid, hostname, bootId, cmd, startedAt, and a nonce. Same-host, same-boot live pids are never stale regardless of age; dead pids are stale immediately; and an unprobeable EPERM pid uses the configured age as a backstop. A changed bootId is stale immediately. Cross-host coordination is out of scope. Force-release MUST go through `forceReleaseLock` which always enforces path and symlink guards and requires a matching expectedPid except for explicit corrupt-lock recovery. Bypassing the lock (direct unlink, parallel mutators, ignoring the stale signal) corrupts the project snapshot and the file-stability log. Stale locks are surfaced by `doctor health` and auto-released by `doctor health --repair` (#824); a corrupt lock is recoverable through `doctor recover-lock`.",alwaysActive:!0,selfOnly:!0,enforcement:"doctor health check + code review for any new `.arbiter/` mutator"},{id:"INV-73",tier:"operational",title:"CI tier presence \u2014 all 8 workflow files must exist under .github/workflows/",description:"Every GitHub-enabled project must contain exactly the canonical 8 CI tier files: 01-pr-fast.yml, 02-pr-extended.yml, 03-human-approval.yml, 05-release.yml, 06-nightly.yml, 07-weekly.yml, 08-monthly.yml, 09-heartbeat.yml. Missing tiers degrade the deployment pipeline and break branch-protection required checks.",alwaysActive:!1,enforcement:"scripts/check-ci-tiers.mjs (L1 gate)",minPresent:8},{id:"INV-74",tier:"security",title:"Anti-bot human-approval gate \u2014 reviewer must be a human distinct from the PR author",description:"The 03-human-approval.yml workflow applies the approved-by-human label only when three conditions pass: (1) reviewer is not the PR author, (2) reviewer is not a Bot, (3) review state is approved. The 01-pr-fast.yml human-approval-required sentinel job asserts this label is present before merge, blocking bot-only approvals.",alwaysActive:!0,minGovernanceLevel:"L2",enforcement:"generated: 03-human-approval.yml triple-check + 01-pr-fast.yml human-approval-required job"},{id:"INV-75",tier:"operational",title:"Heartbeat watchdog \u2014 T4 nightly \u226426 h, T5 weekly \u22648 d, T5b monthly \u226435 d",description:"The 09-heartbeat.yml workflow runs daily at 06:00 UTC and asserts that 06-nightly.yml completed within the last 26 hours, 07-weekly.yml within the last 8 days, and 08-monthly.yml within the last 35 days. A missing heartbeat result triggers an auto-filed GitHub issue. Silent CI failures are treated as production incidents.",alwaysActive:!1,enforcement:"generated: 09-heartbeat.yml cron content"},{id:"INV-76",tier:"security",title:"SHA-pinned actions only \u2014 all third-party GitHub Actions must be pinned to a full 40-char SHA",description:"Tag-pinned or branch-pinned third-party actions (e.g. actions/checkout@v4) are a supply-chain attack vector: the tag can be moved after review. Every uses: reference to a non-local action must resolve to a full 40-character lowercase hex SHA. At L1: violations emit a warning. At L2+: violations are a hard gate failure.",alwaysActive:!0,minGovernanceLevel:"L2",enforcement:"self: scripts/check-action-pins.mjs (L1 \u2014 transition warn until W10 #886) + generated gate: check-action-pins.mjs.ejs (target projects: L1=warn, L2+=hard fail)"},{id:"INV-77",tier:"security",title:"Top-level workflow permissions \u2014 every workflow file must declare explicit top-level permissions",description:"Workflows without a top-level permissions: block inherit the repository default, which is often write-all. Declaring permissions: at the top of every workflow file enforces the principle of least privilege and satisfies the OSSF Scorecard Token-Permissions check.",alwaysActive:!0,minGovernanceLevel:"L2",enforcement:"generated gate: check-workflow-perms.mjs (L1)"},{id:"INV-78",tier:"security",title:"SLSA provenance present at T3 \u2014 release workflow must emit signed build provenance",description:"Every release workflow (05-release.yml) must invoke slsa-framework/slsa-github-generator to produce SLSA provenance. L2 governance targets SLSA Build L2 (signed provenance). L3 governance targets SLSA Build L3 (hermetic builder). Provenance is attached to the GitHub release as a verifiable attestation alongside the signed artifact.",alwaysActive:!0,minGovernanceLevel:"L2",enforcement:"generated: 05-release.yml slsa-github-generator reusable workflow call"},{id:"INV-79",tier:"security",title:"Cosign sign-blob present for every release artifact",description:"Every artifact published by the release workflow (jars, binaries, wheels, images, tarballs) must be signed with cosign sign-blob using keyless OIDC signing via Sigstore. Unsigned release artifacts cannot be verified by downstream consumers and fail supply-chain audits.",alwaysActive:!0,minGovernanceLevel:"L2",enforcement:"generated: 05-release.yml cosign sign-blob per archetype publish job"},{id:"INV-80",tier:"operational",title:"No continue-on-error on test or build steps \u2014 failures must propagate immediately",description:"Setting continue-on-error: true on a test, build, or security scan step masks failures and allows broken code to merge. This is a primary cause of non-deterministic CI. Only informational/alerting jobs (nightly, weekly, heartbeat) may use continue-on-error, and only at the job level, never on individual steps.",alwaysActive:!1,enforcement:"generated: workflow-integrity hook regex (post-edit)"},{id:"INV-81",tier:"operational",title:"Tier-hash local\u2194CI parity \u2014 check-all.mjs subcommand hashes must match CI workflow steps",description:"Each check-all.mjs subcommand (check, gate, full) publishes a tier-hash that is verified by the corresponding CI workflow in a parity-check step. If the local runner and the CI workflow diverge, a merge is blocked. This extends INV-59 to cover all T1..T5 gates.",alwaysActive:!1,enforcement:"generated: check-all.mjs tier-hash output + 01-pr-fast.yml parity-check step"},{id:"INV-85",tier:"operational",title:"No kit source leakage \u2014 committed kit files must not contain employer-specific tokens",description:'Files authored by the kit catalog PR (src/kit/**, .github/ISSUE_TEMPLATE/epic-kit-gold-standard.md) must not contain employer-identifying tokens defined in scripts/data/redaction-lexicon.json. Tokens include service names, internal identifiers, and regulatory/proprietary markers. Enforced by check-no-redacted-tokens.mjs at L1. Keycloak is allowed when the line also contains "Keycloak-compatible IdP" (open-source framing).',alwaysActive:!1,selfOnly:!0,enforcement:"scripts/check-no-redacted-tokens.mjs (L1 gate)"},{id:"INV-86",tier:"operational",title:"Kit catalog parity",description:'src/kit/catalog.json and src/kit/canonical-mapping.json must stay in sync: every canonical_id in mapping must match a catalog id (N01..N78); names, tml_source, and gate_type must match after NFC-normalize and suffix-strip; every BLOCKING dim must have at least one enforcement artifact or a valid disposition exemption (adopt-framework or stack-adapter with implementing_wave in W3-W11, or disposition=done). Neither file may contain tokens from scripts/data/redaction-lexicon.json. (R-08 hardening) Rows carrying import_source must have an import_name that alnum-prefix-matches their framework_realization.docs pointer; every framework_realization.{template,generator,validator} path must be prefixed "planned:" or exist on disk; every original import dim (1..76) must appear exactly once across mapping.dimensions[].import_source and unmapped_import_dims \u2014 no import payload may be silently dropped or double-attached.',alwaysActive:!1,selfOnly:!0,enforcement:"scripts/check-kit-catalog-parity.mjs (L1 gate)"},{id:"INV-87",tier:"operational",title:"Local-wrapper \u2194 CI parity fa\xE7ade",description:"The local Makefile/run.sh targets must match CI workflow job names so contributors running `make ci` invoke the same surface as CI. Static check compares Makefile .PHONY targets (excluding local-only targets) with .github/workflows/ job names. Runtime check compares parityContentHash across local and CI gate results. Both checks are skip-neutral when sources are absent (pre-W4 state). Enforced by check-local-ci-parity.mjs at L1.",alwaysActive:!1,selfOnly:!0,enforcement:"scripts/check-local-ci-parity.mjs \u2014 static Makefile\u2194workflow check at L1 (PARITY_STATIC_CHECK_ONLY=1), full static + runtime parityContentHash check at L2"},{id:"INV-88",tier:"operational",selfOnly:!0,alwaysActive:!1,title:"Stack adapter coverage",description:"RETIRED (#1837): src/adapters/ (8 files, self-admitted test-only, zero runtime callers) and its file-presence gate were removed. No successor invariant.",status:"retired",retiredReason:"src/adapters/ was a test-only scaffold with zero runtime callers (self-admitted in its registry module); deleted in #1837 along with its gate script. No replacement."},{id:"INV-89",tier:"operational",title:"Anti-drift validator family \u2014 W6+F4 validators must be present and wired",description:"The anti-drift validator family (W6+F4) consists of 20 check-*.mjs scripts emitted for target projects via src/generators/anti-drift-validators.ts (Track B). 11 of those 20 are also wired in arbiter's own L1 gate (dual-track): the 9 W6 core validators plus check-validator-helptext and check-tier-coverage. 2 scripts (check-workflow-sha-pinning.mjs, check-workflow-job-naming.mjs) are Track B only. The F4 batch adds 9 additional validators completing the agnostic anti-drift set. See docs/REFERENCE/anti-drift-family.md for the full family reference.",alwaysActive:!1,selfOnly:!1,enforcement:"scripts/check-suppression-rationale.mjs (L1) + scripts/check-suppression-expiry.mjs (L1) + scripts/pii-scan.mjs (L1) + scripts/check-secret-scan.mjs (L1) + scripts/check-drift.mjs (L1) + scripts/check-workflow-runners.mjs (L1) + scripts/check-workflow-docs-sync.mjs (L1) + scripts/check-workflow-test-integrity.mjs (L1) + scripts/check-pr-size-gate.mjs (L1) + scripts/check-unwired-guards.mjs (L1) + scripts/check-validator-helptext.mjs (L1) + scripts/check-tier-coverage.mjs (L1) + src/generators/anti-drift-validators.ts (Track B, 20 scripts)"},{id:"INV-90",tier:"operational",selfOnly:!0,alwaysActive:!1,title:"Evidence bundle schema compliance",description:"Every task evidence bundle in .evidence/task-NNN/ must conform to schemas/evidence-bundle.schema.json. Ensures audit trail completeness by requiring taskId, timestamp, gateResult (pass|fail), redTestPath, greenTestPath, and an artifacts array. Exit 0 when no bundles are present (vacuous pass for new projects).",enforcement:"scripts/check-evidence-bundle.mjs"},{id:"INV-91",tier:"security",title:"AI-PR human-approval gate",description:'Bot-authored PRs (github.event.pull_request.user.type == "Bot") must be reviewed and approved by a human before merge. Approval is signaled by the "approved-by-human" label applied by _label-on-approve.yml (idempotent, rejects bot reviewers and self-reviews). _ai-draft-check.yml asserts the label presence on every label/sync event. Complements INV-74 which enforces the label requirement regardless of PR author type.',alwaysActive:!0,minGovernanceLevel:"L2",selfOnly:!1,enforcement:"generated: _ai-draft-check.yml workflow + _label-on-approve.yml workflow"},{id:"INV-92",tier:"security",title:"Supply chain \u2014 keyless signing, SBOM attestation, and Trivy CRITICAL block",description:"Release artifacts must be signed with cosign keyless (OIDC via Sigstore) and attested with a CycloneDX SBOM via cosign attest --predicate. Trivy must scan the filesystem for CRITICAL vulnerabilities (exit-code: 1) before the signing step runs. HIGH vulnerabilities are reported but do not block (target projects may have legacy deps). A _sigstore-retry-sign reusable workflow is also generated as opt-in scaffolding for retry-on-flake signing; the live 05-release cosign-sign job signs inline and does not yet delegate to it (#1663), so retry-on-flake is available to wire in, not yet active.",alwaysActive:!0,selfOnly:!1,minGovernanceLevel:"L2",enforcement:"generated: 05-release.yml (trivy-fs-scan + cosign-sign + sbom jobs)"},{id:"INV-93",tier:"operational",selfOnly:!0,alwaysActive:!1,title:"Nightly freshness gate",description:"RETIRED (#2520): the local stamp-file gate had no writer for its artifact and passed vacuously whenever that artifact was absent, by design \u2014 structurally incapable of ever failing. 09-heartbeat.yml already enforces the real freshness property via the GitHub Actions API, and strictly better: it also fails when the nightly workflow never ran at all. No replacement invariant.",status:"retired",retiredReason:"The stamp-file gate had no writer for its artifact anywhere in the repo and exited 0 vacuously whenever it was absent, by design (the entry itself documented this as intentional) \u2014 structurally incapable of ever failing. 09-heartbeat.yml already enforces the real freshness property via the Actions API and, unlike the stamp gate, fails when the workflow has never run. Deleted in #2520; no replacement invariant."},{id:"INV-94",tier:"operational",selfOnly:!0,alwaysActive:!1,title:"Script catalog cohesion \u2014 every new gate script must carry a CATALOG marker block",description:"Every scripts/check-*.mjs file added after the baseline freeze must carry a // CATALOG: marker block of >=3 contiguous comment lines declaring what behaviour the script aggregates and why it cannot fold into a sibling script. Pre-existing scripts are grandfathered via scripts/data/script-catalog-baseline.json \u2014 the baseline is a debt ledger, not a bypass. Exit 0 when no new files exist outside the baseline; exit 1 with violating filenames when the marker block is absent or fewer than 3 lines.",enforcement:"scripts/check-script-cohesion.mjs"},{id:"INV-96",tier:"operational",selfOnly:!0,alwaysActive:!1,title:"Fail-closed audit \u2014 every gate script must default to BLOCK on uncertainty",description:"Every gate, hook, check, and generator emitted by arbiter must default to BLOCK on uncertainty, never SKIP. The fail-closed audit script checks scripts/, .githooks/, and .claude/hooks/ for known fail-open anti-patterns: missing set -euo pipefail in Bash, bare || true clauses without // FAIL-OPEN-INTENT: annotation, Node scripts that do not wrap top-level work in try/catch exit(1) or consume run-helpers.mjs, and bare catch {} swallowing without // FAIL-OPEN-INTENT: annotation. New scripts outside the baseline must pass all checks. The baseline in scripts/data/fail-closed-baseline.json is a decaying debt ledger, not a freeze: every row carries since + owner and either an expires date at most 90 days out or a written permanent rationale, and the gate fails on an expired row, on a longer window, and on a row whose file no longer violates \u2014 so the ledger can only shrink.",enforcement:"scripts/check-fail-closed-audit.mjs"},{id:"INV-82",tier:"operational",title:"Monthly (T5b) workflow present + heartbeat asserts its freshness",description:"The 08-monthly.yml workflow (T5b) must exist and the 09-heartbeat.yml cron must assert that it completed recently. A monthly run older than the bound is treated as a silent CI failure and triggers an auto-filed GitHub issue. The numeric bound itself is owned by INV-75 (heartbeat watchdog) \u2014 see there, not here, for the day count, so the two invariants cannot state disagreeing numbers.",alwaysActive:!1,enforcement:"generated: 09-heartbeat.yml (assert-monthly-freshness job)"},{id:"INV-136",tier:"operational",title:"Tier-assignment rule \u2014 a check lives at the fastest tier where its red changes the developer's immediate next action",description:"A check lives at the fastest tier where its red changes the developer\u2019s immediate next action; a red tolerated more than 48h has to be fixed, demoted to a slower tier, or deleted. Codified as the collapsed 5-lane CI doctrine, emitted opt-in via enableFiveLaneCi (mutually exclusive with the standard github/ci-tier generators): pre-commit (<10s, local via githooks) / PR-blocking (ci.yml, \u226415min) / nightly (nightly.yml, \u226445min) / weekly (weekly.yml, unbounded) / release-seal (release.yml, on tag push). Each generated workflow states its own tier and time budget in a header comment. Pairs with the A6 sticky-failure-issue mechanism: nightly.yml/weekly.yml source one shared .github/scripts/sticky-failure-issue.sh instead of filing a new issue per red run.",alwaysActive:!1,enforcement:"src/generators/ci-five-lane.ts"},{id:"INV-137",tier:"operational",minGovernanceLevel:"L1",selfOnly:!1,alwaysActive:!1,title:"Declared smoke journeys must be covered \u2014 no aspirational acceptance floor",description:"A project that declares a smoke journey in smoke-journeys.json (machine-readable manifest at repo root, schema arbiter-smoke-journeys-v1) must COVER it with \u22651 real spec file matching that journey's globs. OR semantics: a required journey passes if ANY of its globs matches \u22651 file. This is the synthesis of two precedents: the per-item { id, name, globs, status, rationale } shape and auditable machinery of INV-124 (test-pyramid) \u2014 n/a needs a rationale \u226520 chars, all-n/a is a hard fail \u2014 but the FAIL-CLOSED default direction of INV-126 (api-e2e): applicability is archetype-computed, so a journey applicable to the archetype defaults to required (absent status \u21D2 required, never silently n/a) and day-1-green comes from a REAL scaffolded starter, not a default flag. An archetype with no interactive login/CRUD/authz journeys declares a top-level applicable:false with a reason \u21D2 gate SKIPs (mirrors INV-126 required:false); a missing manifest \u21D2 SKIP (exit 0) so ungoverned repos never false-fail. Because applicability is genuine (archetype-computed) rather than human-asserted, a wired-but-dead CI job can never be laundered into a legitimate n/a here \u2014 that stays a defect to fix, not a skip. Path-traversal globs and a non-array journeys field \u21D2 exit 2 (schema error). Boundary: file PRESENCE only \u2014 assertion quality is INV-118 (anti-proforma) and execution is the render-smoke/e2e CI lane. Introduced in #2080 (sub-issue of #2043).",enforcement:"scripts/check-smoke-journeys.mjs (L1) \u2014 self-gate wired in scripts/check-all.mjs; generated for targets via src/generators/check-all.ts UNCONDITIONAL_EMISSIONS from src/templates/scripts/check-smoke-journeys.mjs.ejs (imports the shared scripts/lib/glob-walk.mjs helper, also unconditionally emitted). Manifest smoke-journeys.json + the TS Playwright starter (tests/smoke/smoke-journeys.spec.ts, frontend-spa + TypeScript only) emitted by src/generators/smoke-journeys.ts (skipIfExists:true; applicability archetype\xD7language-computed). Exit codes per INV-53: 0=PASS/SKIP, 1=policy violation, 2=schema/path-traversal error."},{id:"INV-95",tier:"security",title:"release.yml must invoke cosign sign on container image builds",description:'When deployTarget is not "none", 05-release.yml must invoke cosign sign --yes after the container image build step, signing the image digest via keyless Sigstore OIDC. Ensures every release artifact entering the supply chain is signed before being promoted to TEST or PROD. Enforcement: static-grep confirms cosign sign invocation in generated 05-release.yml.',alwaysActive:!0,minGovernanceLevel:"L2",selfOnly:!1,enforcement:"scripts/check-workflow-cosign.mjs (L1)"},{id:"INV-97",tier:"security",title:"deploy-prod must cosign-verify before traffic shift",description:'When deployTarget is not "none", 10-deploy-prod.yml must execute a cosign verify step with --certificate-identity-regexp and --certificate-oidc-issuer https://token.actions.githubusercontent.com BEFORE any container-app update or service routing command. Prevents deploying an unverified image to production even if the registry was compromised between TEST sign and PROD deploy. Enforcement: static-grep on generated 10-deploy-prod.yml asserts cosign verify precedes deploy command.',alwaysActive:!0,minGovernanceLevel:"L2",selfOnly:!1,enforcement:"scripts/check-workflow-cosign.mjs (L1)"},{id:"INV-98",tier:"security",title:"release workflow trigger must be tag-only (no branch push)",description:'When deployTarget is not "none", 05-release.yml must trigger on push.tags matching a semver pattern (v*.*.*) and must NOT trigger on push.branches. Branch-push triggers on release workflows create unsigned pre-release artifacts in the registry, polluting the digest namespace used by cosign copy in deploy-prod. Enforcement: static-grep confirms only push.tags trigger in generated 05-release.yml.',alwaysActive:!0,minGovernanceLevel:"L2",selfOnly:!1,enforcement:"scripts/check-workflow-cosign.mjs (L1)"},{id:"INV-99",tier:"architectural",title:'deployTarget must be a known cloud or "none"',description:'The deployTarget field in arbiter.json must be one of: "ghcr" | "azure-container-app" | "aws-ecs" | "gcp-cloud-run" | "none". Unknown values cause EJS include() path traversal at render time (RT-7). Enforcement: Zod schema validation on config load (default "none" prevents undefined); EJS whitelist preamble in 04-deploy-test.yml.ejs and 10-deploy-prod.yml.ejs rejects unknown values.',alwaysActive:!0,selfOnly:!1,enforcement:"Zod schema (src/config/schema.ts deployTargetSchema) + EJS whitelist preamble in 04-deploy-test.yml.ejs + 10-deploy-prod.yml.ejs"},{id:"INV-100",tier:"architectural",title:"collaborationMode must be set in arbiter.json",description:'Every arbiter-scaffolded project must declare a collaborationMode field in arbiter.json. Valid values: "trunk-solo" | "peer-review" | "gated-review". Absent collaborationMode means CI shape and branching strategy are inferred from deprecated soloDevMode, which will be removed in the next major release. Enforcement: scripts/check-collab-mode-wired.mjs reads arbiter.json and fails if collaborationMode is absent or not a known value.',alwaysActive:!0,selfOnly:!1,enforcement:"scripts/check-collab-mode-wired.mjs (L1)",adr:"ADR-051"},{id:"INV-101",tier:"architectural",title:"exact-SHA non-force landing for evidence-bearing changes",description:"An evidence-bearing trunk-solo + pr-ff landing atomically advances main from the observed gated base to the exact gated head with updateRefs and force:false, while asserting the head ref in the same transaction. Success requires live-policy validation and post-read main == gatedHeadSha; GitHub PR merge methods are forbidden. Repo compatibility settings: allow_merge_commit:true, allow_squash_merge:false, allow_rebase_merge:false. Branch settings: required_linear_history:false, allow_force_pushes:false, allow_deletions:false.",alwaysActive:!0,selfOnly:!1,enforcement:"scripts/check-merge-method.mjs (L1)",adr:"ADR-052"},{id:"INV-106",tier:"operational",languages:["typescript"],minGovernanceLevel:"L2",title:"i18n parity \u2014 all locale files must have identical key sets",description:"In FE projects, all locale JSON files must contain the same set of translation keys. Missing keys in any locale cause runtime fallback rendering (often empty strings or key identifiers instead of translated text). Raw UI text literals in component source (unfixed hardcoded strings) are also flagged. Bidirectional diff: missing in reference \u2192 target AND missing in target \u2192 reference are both failures. Mirrors P6 (i18n governance) of the FE_DESIGN_PRINCIPLES.",alwaysActive:!1,enforcement:"Generated scripts/verify-i18n-parity.mjs checks bidirectional key-set parity across all locale files. Generated scripts/i18n-literal-scanner.mjs scans component source for raw UI text not wrapped in t(). Both fail the L2 gate step in check-all.mjs."},{id:"INV-105",tier:"operational",languages:["typescript"],minGovernanceLevel:"L2",title:"design token discipline \u2014 no raw colors or phantom tokens in UI components",description:"In FE projects, UI component files MUST use semantic design tokens from design-tokens.json (W3C DTCG format). Raw hex/rgb/hsl color values in component source are FORBIDDEN. Foundation/primitive tokens (--f-* prefix) MUST NOT be referenced directly in components. Phantom tokens (CSS var references not in design-tokens.json) produce invisible elements and must be eliminated. Mirrors FE006 + P1 of the FRONTEND_CONSTITUTION and FE_DESIGN_PRINCIPLES.",alwaysActive:!1,enforcement:"Generated scripts/verify-tokens.mjs scans component source files for raw hex/rgb/hsl values and phantom token references, then fails the L2 gate step in scripts/check-all.mjs on any violation."},{id:"INV-102",tier:"operational",languages:["typescript"],minGovernanceLevel:"L2",title:"API-layer isolation \u2014 no HTTP calls outside the adapter layer",description:"In FE projects (archetype frontend-spa or lanes:[frontend]), direct fetch()/axios.* calls MUST NOT appear in UI component files, composables/hooks, or state stores. All HTTP I/O must be confined to a dedicated adapter/api layer (FSD entities/shared api modules, or src/api/). Mirrors FE001 of the FRONTEND_CONSTITUTION. Framework-aware: scans .ts/.tsx/.jsx (react), .ts/.vue (vue), or .ts/.svelte (svelte) files as appropriate.",alwaysActive:!1,enforcement:"Generated check-fe-boundaries.mjs (emitted by check-all generator, #1127) scans UI-layer component files for raw HTTP client calls (fetch, axios, XMLHttpRequest) and fails the L2 gate step in scripts/check-all.mjs on any violation."},{id:"INV-103",tier:"operational",languages:["typescript"],minGovernanceLevel:"L2",title:"Headless domain logic \u2014 no browser APIs in domain or store layer",description:"In FE projects, domain and store files MUST NOT import or reference browser APIs (window, document, localStorage, sessionStorage, matchMedia, navigator, location, history, IndexedDB). Browser coupling makes domain logic untestable in Node.js and prevents server-side rendering. Mirrors FE002 of the FRONTEND_CONSTITUTION.",alwaysActive:!1,enforcement:"Generated check-fe-boundaries.mjs (emitted by check-all generator, #1127) scans store/domain-hinted files for browser-global references and fails the L2 gate step in scripts/check-all.mjs on any violation."},{id:"INV-104",tier:"operational",languages:["typescript"],minGovernanceLevel:"L2",title:"State-management discipline \u2014 stores are synchronous and client-only",description:"In FE projects, state store files MUST NOT contain async fetch calls or direct API caching. Server state MUST be delegated to a data-fetching library (TanStack Query, SWR, or equivalent). Store getters MUST NOT encode business logic. Mirrors FE003 of the FRONTEND_CONSTITUTION.",alwaysActive:!1,enforcement:"Generated check-fe-boundaries.mjs (emitted by check-all generator, #1127) detects `await fetch` and `await axios` inside store/domain-hinted files and fails the L2 gate step in scripts/check-all.mjs on any violation."},{id:"INV-107",tier:"governance",minGovernanceLevel:"L1",selfOnly:!0,alwaysActive:!0,title:"docs/internal/ADR/ is the canonical ADR SSOT \u2014 numbers unique, canonical_id populated, README in sync",description:"Every numbered ADR file in docs/internal/ADR/ must have canonical_id set to its 3-digit filename prefix, ADR numbers must be unique across all files, and docs/internal/ADR/README.md must list every numbered ADR file. docs/internal/SYSTEM/DECISIONS.md is frozen legacy and must not receive new entries. Enforced at L1 to catch drift before commit.",adr:"ADR-073",enforcement:"scripts/check-adr-index.mjs verifies unique numbers, canonical_id match, and README coverage. Wired into scripts/check-all.mjs L1 (#1099)."},{id:"INV-108",tier:"governance",minGovernanceLevel:"L1",selfOnly:!0,alwaysActive:!0,title:"SSOT core set exhaustiveness \u2014 every qualifying doc must be listed",description:"docs/METHOD/SSOT_CORE_SET.md must list every doc that qualifies as a canonical SSOT doc (status: active AND its first kind/* tag is a backbone kind, or a non-empty canonical_id; excluding ADRs and generated dim-NN coverage stubs). The generated inventory region is the authoritative source; the gate exits 1 if a qualifying doc on disk is absent from the list. Complements INV-54 (listed\u2192exists) with the reverse direction (qualifies\u2192listed).",enforcement:"scripts/check-ssot-core.mjs verifies exhaustiveness using the selectSsotDocs predicate from scripts/gen-ssot-core.mjs (single source of the rule); the generator --check guards staleness. Wired into scripts/check-all.mjs L1 (#1100)."},{id:"INV-109",tier:"operational",minGovernanceLevel:"L2",selfOnly:!1,alwaysActive:!1,languages:["typescript"],title:"Duplication (DRY) gate + ratchet \u2014 generated and dogfooded",description:"Code duplication is BOTH a hard gate (jscpd, fails above the governance-scaled threshold: L1 10% \u2192 L2 5% \u2192 L3/L4 3%) AND a debt-ratchet metric: a patch may not increase the duplicated-token percentage (Lehman entropy). CANON-22 Tier-1 \u2014 Juergens et al. ICSE 2009: inconsistent (diverged) clones are latent bugs. Dual-sided (CANON-01): arbiter dogfoods the gate at scripts/check-all.mjs and emits the same gate to TypeScript targets via src/generators/duplication.ts (.jscpd.json + jscpd devDep + scripts/check-duplication.mjs).",enforcement:"scripts/check-duplication.mjs (L2 gate, fail-closed jscpd v5 wrapper, see .jscpd.json) + scripts/debt-report.mjs --gate (duplicationPercentage metric collected in scripts/debt-lib.mjs); generated for target projects by src/generators/duplication.ts."},{id:"INV-110",tier:"governance",minGovernanceLevel:"L1",selfOnly:!0,alwaysActive:!0,title:"GLOBAL_INVARIANTS.md must document every always-active invariant \u2014 coverage parity",description:"GLOBAL_INVARIANTS.md is the deep-reference companion to AGENTS.md. Every alwaysActive invariant in the catalog must have a `### INV-NN` section there (no silent coverage gap \u2014 the drift this gate prevents), and every documented INV must exist in the catalog (no phantom row). Mirrors the AGENTS.md<->catalog parity gate (CANON-08) for the companion doc. selfOnly: target GLOBAL_INVARIANTS.md is generated from the catalog and is in parity by construction, so the gate guards only the hand-maintained arbiter-self doc.",enforcement:"scripts/check-global-invariants-parity.mjs verifies forward (alwaysActive->documented) and reverse (documented->catalog) parity. Wired into scripts/check-all.mjs L1."},{id:"INV-111",tier:"governance",minGovernanceLevel:"L1",selfOnly:!0,alwaysActive:!0,title:"CLI reference must document every registered command \u2014 no phantom, no missing",description:"website/reference/cli.md hosts a machine-generated command-reference region (between BEGIN/END GENERATED:cli markers). Every top-level command registered in src/cli.ts must have a section in that region, and every section must correspond to a registered command (bidirectional, no phantom). Drift is caught at L1 by the gate; the hand-written prose outside the markers is preserved on every regeneration. Extended by F2 (#1838): the no-phantom rule also covers hand-authored prose \u2014 every `arbiter <cmd>` cited in PRIVACY.md/docs/website (minus internal/ and changelog/ historical prose) must exist in cli.ts routing, aliases included. selfOnly: this guards arbiter's own CLI reference doc, not generated target projects.",enforcement:"scripts/gen-cli-ref.mjs --check verifies bidirectional parity (registered<->documented); scripts/check-phantom-command-scan.mjs verifies prose citations (#1838). Both wired into scripts/check-all.mjs L1."},{id:"INV-112",tier:"governance",minGovernanceLevel:"L2",selfOnly:!1,alwaysActive:!0,title:"RTM/FEATURE_MATRIX required at L2+; serious-test DoD at L3+; 21CFR audit-trail at L4",description:"Every project at L2+ must ship and maintain a docs/FEATURE_MATRIX.md (RTM) covering 100% of its KIT catalog dimensions. The matrix uses a fail-closed status ladder: Missing (issue_ref required) \u2192 Partial (code_ref required) \u2192 Done (code_ref+test_ref+doc_ref required) \u2192 Verified (all four refs + test title parsed). At L3+ every Done/Verified row must reference a real test title (serious-test DoD). At L4 audit_trail-category rows must carry code_ref+test_ref. The matrix is machine-validated by scripts/check-feature-matrix.mjs on every gate run. The generator (src/generators/feature-matrix.ts) scaffolds the initial matrix for new projects; the committed doc is the authoritative Product-Truth source.",enforcement:"scripts/check-feature-matrix.mjs (L1 gate, fail-closed): validates status ladder, KIT-dim coverage, counter integrity, ref existence, and level-gated DoD rules. Ref existence is not the same claim as ref ACCURACY: a ref may carry a line span `path#Lx-Ly`, optionally pinned as `@<12 hex>` over that span's exact text. A span past the end of the file, or a pin that no longer matches, is OUTDATED \u2014 the citation stopped proving what the row claims, which is the \"the requirement changed, the test did not\" failure a matrix exists to catch and which whole-file refs cannot express. Both rules are additive: an unpinned ref is unaffected, so adoption is per-row and deliberate rather than a mass rewrite. `--pin <path>#Lx-Ly` produces the pinned ref, because a syntax nobody can compute by hand is a syntax nobody adopts. Axis 2 (BOTH TRACKS): a `Verified` row must carry .arbiter/evidence/rtm/<REQ-NNN>.json conforming to schemas/rtm-verdict.schema.json with verdict PROVEN, a justification, the command executed and its transcript digest \u2014 status is not evidence, the same fail-closed rule INV-146 applies to milestone `done`. Citations reuse axis 1's pinned-span grammar, so a drifted citation is OUTDATED by that mechanism rather than a second one. Four rows predate the rule; REQ-028 was EARNED (suite executed, transcript recorded) and three are grandfathered by a monotone ratchet in scripts/data/rtm-verdict-baseline.json that may fall and never rise \u2014 no verdict is reconstructed after the fact. The axis was self-only until its contract could travel with it: src/generators/feature-matrix.ts now emits schemas/rtm-verdict.schema.json beside the gate (CANON-11), so a governed project receives the rule AND the schema it validates against. An absent schema with `Verified` rows present is an ERROR on both tracks, never a silent skip \u2014 a rule that quietly stops applying is what this gate exists to prevent. Wired into scripts/check-all.mjs L1. Generated for target projects at L2+ by src/generators/feature-matrix.ts (CANON-23)."},{id:"INV-113",tier:"governance",minGovernanceLevel:"L1",selfOnly:!0,alwaysActive:!0,title:"Single authoritative task-phase document \u2014 no split-brain dotfiles",description:"Task lifecycle state is sourced from ONE authoritative document pair: .claude/.task/status.json (structured phase + step-cursor + metadata) and .claude/.task/log.md (append-only digest). The legacy split-brain \u2014 flat .claude/.task-* dotfiles plus a per-id status.json that froze at phase:red \u2014 is abolished. All phase writes route through one atomic read-modify-write so the document can never diverge, and every reader (engine, generated hooks, shell consumers) reads the unified document. No source or template code may read or write the legacy flat dotfiles; only the migration shim src/commands/task-state.ts may name them (to consume-and-delete during migration).",enforcement:"scripts/check-phase-doc-consistency.mjs (L1 gate): scans src/** for legacy .task-* dotfile-name literals (allowlisting the migration shim) and validates status.json well-formedness when present. Wired into scripts/check-all.mjs L1."},{id:"INV-114",tier:"governance",minGovernanceLevel:"L2",selfOnly:!1,alwaysActive:!0,title:"Fail-closed Stop gate \u2014 completion claims require correlated evidence",description:"On a task/ or ship/ branch whose phase is not yet complete, an agent may not end its turn claiming completion (task complete / ready to merge / pr merged \u2026) unless three evidence artifacts exist AND correlate to the current branch and HEAD sha: (1) the plan-review latest.json with verdict PASS, recorded on this branch at a commit that is an ancestor of HEAD; (2) the agents-dispatched sidecar (.arbiter/agents-dispatched.json) on this branch at an ancestor commit; (3) the gate-pass marker (.arbiter/gate-pass.json) that still BINDS this tree \u2014 schema arbiter-gate-pass-v2, head_sha equal to HEAD, matching branch and task id, plus the #2328 identity axes: working-tree content hash, checkout root, toolchain fingerprint, gate level and TTL. A missing or blank field is a rejection, never an unconstrained axis, so a pre-v2 marker is refused. A claim with any missing or stale artifact is blocked. Every other path \u2014 no claim, unreadable transcript, non-task branch, phase complete, hook re-entry \u2014 stands down. This is the backstop the soft UserPromptSubmit completion guard cannot be: it observes the agent's own stop, not the next user prompt.",enforcement:".claude/hooks/stop-evidence-guard.mjs (Claude Code Stop event, exit 2 = block-the-stop and return stderr to the model). Generated for target projects at L2+ by src/generators/claude.ts and dogfooded in arbiter's own .claude/ (CANON-01/14). Evidence writers (scripts/check-all.mjs for the gate-pass marker; the /task and /ship command playbooks in .claude/commands/ for the plan-review and agents-dispatched sidecars) stamp branch+sha so correlation is possible; the marker binding itself lives in the shared scripts/lib/gate-evidence.mjs (#2328). Empirical coverage: __tests__/hooks/empirical/stop-evidence-guard.test.ts, __tests__/evidence/gate-evidence-consumers.test.ts."},{id:"INV-115",tier:"governance",minGovernanceLevel:"L1",selfOnly:!1,alwaysActive:!0,adr:"ADR-109",title:"Free-text governance prohibitions must resolve to a verified enforcer, live scan, or explicit triage",description:'Every hard prohibition declared in free-text governance (AGENTS.md, CANON.md, CLAUDE.md) via a directive marker (NEVER / MUST NOT / DO NOT / \u{1F6D1} / `No <tok>` / `never <tok>`, or the passive register forbidden / prohibited / disallowed / not permitted / not allowed at entry level, #2582) must resolve to exactly one honest state \u2014 it may not be merely asserted in prose. (1) COVERED: mapped in scripts/constraint-map.json to an enforcer whose existence is verified at scan time (gate\u2192referenced in check-all.mjs, hook\u2192file under .claude/hooks, inv\u2192id in catalog.ts, lint\u2192rule in an eslint config, template\u2192path under src/templates); a map entry naming a missing enforcer is MAP-FICTION and fails the gate (the CANON-23 fiction guard). (2) ENFORCED-BY-SCAN: a derivable code token, live-grepped against source every run \u2014 the scan itself is the wiring. (3) UNENFORCEABLE: prose / path / non-code token surfaced for human triage. Extends CANON-09 (claimed-enforcement = wired-gate) from invariant citations to free-text prohibitions. A MISSING map file (#2037, ADR-109) fails the gate closed \u2014 a project declaring this gate must supply linking data, not silently scan against nothing; a present-but-empty map still warns (fresh project, curated over time). Escape: governance.constraintScan:"off" in arbiter.json.',enforcement:"scripts/check-constraint-scan.mjs (L1 gate, wired in scripts/check-all.mjs): extracts directive prohibitions, classifies via scripts/constraint-map.json, hard-fails on a live un-covered derivable violation, a map-fiction entry, or a MISSING map file (ADR-109). scripts/constraint-map.json is scaffolded unconditionally alongside the gate (#2037) so the missing-file case only arises from deletion/retrofit, never a fresh project. Emitted for target projects as src/templates/scripts/check-constraint-scan.mjs.ejs (warn-default on ENFORCED-BY-SCAN, --enforce to promote; the missing-map fail-closed applies regardless) and dogfooded on arbiter's own governance (CANON-01/14). Empirical coverage: __tests__/scripts/check-constraint-scan.test.ts."},{id:"INV-116",tier:"governance",minGovernanceLevel:"L1",selfOnly:!0,alwaysActive:!0,title:"wiki/ must be free of broken wikilinks, orphan pages, stale source hashes, and missing citations",description:"The generated LLM-wiki (wiki/) must pass four lint dimensions: broken-link (every [[WikiPage]] ref resolves to wiki/{page}.md), orphan (every page reachable from INDEX.md; INDEX.md exempt), stale (source_sha matches current git hash \u2014 asserted only when the wiki dir is git-tracked; an untracked, generated wiki skips this dimension loudly, #2585), and citation (source: field present and git-tracked). Exit 0 on bootstrap (wiki/ absent). Non-authoritative (SSOT wins on conflict). selfOnly: true because target projects may not generate wiki/ (#1241).",enforcement:"scripts/check-wiki-lint.mjs (L2 gate, wired in check-all.mjs): validates all 4 dimensions; exits 0 on bootstrap. Emitted for targets as check-wiki-lint.mjs.ejs (CANON-01/14). Tests: tests/gates/wiki-lint-fixture.test.ts."},{id:"INV-117",tier:"governance",minGovernanceLevel:"L1",selfOnly:!0,alwaysActive:!0,title:"arbiter self-repo must not track binary build artifacts",description:"The arbiter repository must not commit binary build artifacts. npm pack outputs (*.tgz) bloat git history permanently and are unreproducible from source. selfOnly: true because consumer projects use diverse packaging (maven jars, python wheels, Go binaries, Docker images) \u2014 a blanket *.tgz prohibition would incorrectly block legitimate non-JS packaging workflows (#1217).",enforcement:"scripts/check-no-tracked-artifacts.mjs (L1, selfOnly \u2014 arbiter self-governance only). Exit codes per INV-53: 0=PASS, 1=FAIL, 2=ERROR."},{id:"INV-118",tier:"governance",minGovernanceLevel:"L1",alwaysActive:!0,title:"Anti-proforma test gate \u2014 every test must carry a real assertion",description:'Test methods (it(), test(), @Test, @ParameterizedTest, @RepeatedTest) must contain at least one recognized assertion. Proforma tests (no assertions) provide false confidence \u2014 they always pass regardless of the code under test (\xA7R-41). JVM: enforced via ArchUnit bytecode scan (AntiProformaTest.java, L2+). TypeScript/other: enforced via source-text regex scan (check-anti-proforma.mjs, L1+, warn-default). Bypass: @AntiProformaExempt("rationale") (JVM) or // anti-proforma-exempt: rationale (other). Bypass ratio > 5% triggers EXEMPT-THRESHOLD alarm (#1249).',enforcement:"scripts/check-anti-proforma.mjs (L1+, warn-default; --enforce promotes to hard-block). JVM: src/templates/archunit/AntiProformaTest.java.ejs (L2+, hard-block via ArchUnit). Exit codes per INV-53: 0=PASS/WARN, 1=FAIL (--enforce), 2=ERROR."},{id:"INV-119",tier:"governance",minGovernanceLevel:"L2",alwaysActive:!0,title:"Commit-footer audit evidence required for suppression/override/bypass commits",description:"Commits that touch suppression/bypass files (*.trivyignore, owasp-suppressions.xml, pitest-override configs, sigstore-bypass configs, suppressions/**) in the origin/main..HEAD range must carry at least one recognized immutable commit-footer trailer (\xA711.10(e)). Recognized trailers: Suppression-Rationale:, Pitest-Override-Rationale:, Trivy-Expiry-Extension:, Sigstore-Bypass:. Evidence artifact written to .arbiter/evidence/commit-footer-audit/<timestamp>.json. Hard-blocks on missing trailer (exit 1). Git failure \u2192 exit 0 with WARN (fail-open when origin/main unavailable) (#1249).",enforcement:"scripts/check-commit-footer-rationale.mjs (L2+, hard-block; exit 1 on violation). Exit codes per INV-53: 0=PASS, 1=FAIL, 2=ERROR."},{id:"INV-120",tier:"governance",minGovernanceLevel:"L1",selfOnly:!0,alwaysActive:!0,title:"Workflow needs-chain depth must not exceed the configured limit (parallelism regression gate)",description:"CI workflow job dependency chains (critical path depth, measured as edge count in the needs: DAG) must not regress beyond per-workflow thresholds. Default limit: 3 edges. Per-file overrides: 01-pr-fast \u22643 (Java Maven path uses 3 edges), 05-release \u22644 (cosign/sbom-attest chain), nightly/weekly/monthly \u22645. Aggregator sinks (jobs with `if: always()`) are excluded \u2014 they are pure status barriers, not wall-clock critical-path contributors. selfOnly: true because consumer projects generate their own workflows from templates; this gate protects arbiter's own generated CI from undetected chain regression (#1231).",enforcement:"scripts/check-workflow-parallelism.mjs (L1, selfOnly \u2014 arbiter self-governance only). Configurable via ARBITER_MAX_NEEDS_CHAIN env. Exit codes per INV-53: 0=PASS, 1=FAIL, 2=ERROR."},{id:"INV-121",tier:"operational",minGovernanceLevel:"L1",selfOnly:!1,alwaysActive:!1,title:"Stack conformity \u2014 the repo-root manifest must not contradict declared axes",description:'A governed project must not declare a language/databaseEngine in arbiter.json that the repo-ROOT manifest contradicts. Declared language="go" with a root package.json and no go.mod (a Node project masquerading), or declared databaseEngine="sqlite" while the root manifest pulls a postgres/mysql/mongo driver, is configuration drift that silently mis-governs the project. Fail-closed self-safety is RUNTIME-resident in the emitted gate (not render-time EJS, which check-self-dogfood defaults to typescript): the script re-reads the TARGET arbiter.json \u2014 absent language \u21D2 exit 0 (undeclared never fails); absent/none databaseEngine \u21D2 DB conformity skipped. Root-scope ONLY (./go.mod, ./package.json, ./*.lock) \u2014 never recurses, so monorepo subdir manifests and fixtures do not false-fail (#1312).',enforcement:"scripts/check-stack-conformity.mjs (L1 gate, wired in check-all.mjs when a language is declared). Emitted for target projects by src/generators/check-stack-conformity.ts and rendered from src/templates/scripts/check-stack-conformity.mjs.ejs (CANON-01). Exit codes per INV-53: 0=PASS/SKIP, 1=FAIL (contradiction), 2=ERROR."},{id:"INV-122",tier:"operational",minGovernanceLevel:"L1",selfOnly:!1,alwaysActive:!1,title:"Update propagates template fixes to pristine generated files; user-modified files are preserved",description:'`arbiter update` must rewrite a skipIfExists-emitted file whose on-disk content is byte-identical to arbiter\'s last recorded render (pristine, unmodified since generation) so upstream template fixes reach the governed fleet; it must preserve a user-modified file (on-disk hash \u2260 recorded baseline) and warn that the fix was withheld; and `arbiter diff` must report a pristine-stale file as changed, never as a lying "unchanged". Provenance is a committed per-file content-hash manifest (.arbiter-generated-manifest.json at the repo root, sibling of .arbiter-generated.json), not config alone. A corrupt manifest fails closed (exit 2); a missing one is a legitimate first run that conservatively skips (#1328).',enforcement:"Integration tests (__tests__/integration/update-propagates-fixes.test.ts) + unit tests for the generation session and manifest (fs-generation-session, generated-manifest). Runtime-resident in the arbiter CLI engine (init/update/diff) and inherited by the fleet via the CLI \u2014 not a render-time gate. Exit codes per INV-53: corrupt manifest \u21D2 2=ERROR."},{id:"INV-123",tier:"operational",minGovernanceLevel:"L1",selfOnly:!1,alwaysActive:!1,title:"Emission coherence \u2014 every referenced emission must exist or be a declared optional",description:"A generated tree must contain every file it references. Every scripts/*.mjs invoked by check-all.mjs, every handler registered in .claude/hooks/hooks.mjs, every node script run by a .githook or a workflow, every command in .claude/settings.json, every script referenced by a Makefile recipe, and every script/hook named in a .claude/commands/*.md playbook must resolve to an emitted file. An UNGUARDED-missing reference is a crash-class ghost and ALWAYS fails. A GUARDED-missing reference (behind an existsSync()/shell [ -f ] guard \u2014 a legitimately-optional industry or frontend overlay script) fails UNLESS it is declared in scripts/optional-emissions.json with a non-empty rationale; the manifest can never silence an unguarded reference (strictly weaker than a suppression). Workflows must additionally SHA-pin every uses: ref (local ./ and docker:// excepted) and name every top-level job. Makefile-recipe and command-playbook references are UNGUARDED by construction and so can never be silenced. Caught the fleet-wide ci-classify-changes / exitplanmode-banner / build-kit ghosts (#1331) and the done-evidence / route-auditors Makefile/command-doc ghosts (#1345).",enforcement:"scripts/check-emission-coherence.mjs (pure checkEmissionCoherence(dir)) \u2014 wired into arbiter's own check-all.mjs self-gate against the repo tree, and run across the FULL (language \xD7 level \xD7 mode) matrix by __tests__/integration/e2e/emission-coherence-matrix.test.ts (static, in-process, no toolchains, affordable per-PR). The optional manifest is emitted for target projects by src/generators/check-all.ts from src/templates/scripts/optional-emissions.json.ejs (CANON-01). Exit codes per INV-53: 0=PASS, 1=FAIL (ghost), 2=ERROR (no dir arg)."},{id:"INV-124",tier:"operational",minGovernanceLevel:"L1",selfOnly:!1,alwaysActive:!1,title:"Declared test levels must be non-empty \u2014 no aspirational pyramid",description:`A project that declares a test level in test-pyramid.json (machine-readable manifest at repo root) must have \u22651 real test file matching that level's globs. OR semantics: a required level passes if ANY of its declared globs matches \u22651 file. A level legitimately not needed must be explicitly marked status:"n/a" with a rationale \u226520 chars (auditable, mirrors the suppression-rationale gate). A fully-skipped pyramid (all levels n/a) is itself a hard fail \u2014 it defeats the gate. Manifest absent \u2192 gate SKIPS (exit 0), so ungoverned repos never false-fail; fail-closed is provided by generator emission (Track B) and the SSOT completeness test (Track A). Path-traversal globs and non-array levels.fields \u2192 exit 2 (schema error). Boundary: file PRESENCE only \u2014 assertion quality is INV-118 (anti-proforma). Rust L1 Unit is auto-emitted as n/a (inline #[test] annotations are undetectable by glob). Introduced in #1364 as child of epic #1363 ("proof must touch reality").`,enforcement:"scripts/check-test-pyramid.mjs (L1) \u2014 self-gate wired in scripts/check-all.mjs; generated for targets via src/generators/check-all.ts UNCONDITIONAL_EMISSIONS from src/templates/scripts/check-test-pyramid.mjs.ejs (CANON-01/04/11). Manifest test-pyramid.json emitted for targets by src/generators/test-pyramid-manifest.ts (skipIfExists:true; archetype-mismatch guard at gate time). Exit codes per INV-53: 0=PASS/SKIP, 1=policy violation, 2=schema/path-traversal error."},{id:"INV-125",tier:"operational",minGovernanceLevel:"L1",selfOnly:!1,alwaysActive:!1,title:"Persisted domain fields must be reachable through the public HTTP API",description:"A persisted domain field absent from both request and response schemas is inaccessible. The manifest domain-api-surface.json (schema: arbiter-domain-api-surface-v1) declares resources with per-field inRequestSchema/inResponseSchema flags. Gate fails when any persisted:true field has both flags false. Manifest absent => SKIP.",enforcement:"scripts/check-domain-api-surface.mjs (L1) \u2014 wired in check-all.mjs; generated for targets where config.hasPublicApi is true via emitDomainApiSurface helper (skipIfExists:true). Exit codes per INV-53: 0=PASS/SKIP, 1=surface gap, 2=schema/parse error."},{id:"INV-126",tier:"operational",minGovernanceLevel:"L2",selfOnly:!1,alwaysActive:!1,title:"Service archetypes must ship a non-mocked live-API e2e suite",description:'A service archetype (backend-web-db) must exercise the RUNNING binary over real HTTP, not just mocked unit/integration tests. The manifest api-e2e.json declares { archetype, required, suiteDir, framework, glob }; required:true (service) means the glob must match >=1 non-empty suite file under tests/api/ that boots the real binary and asserts on live responses. This is the INVERTED absent-semantics vs INV-124: a declared service with an absent or empty suite is a hard fail (exit 1), closing the "domain green, HTTP wiring broken" gap (a field in domain/DB never wired into the HTTP input => 400). required:false (non-service) or absent manifest => SKIP (exit 0). Boundary: file presence + non-emptiness only \u2014 the live run is CI/L2 via tests/api/run.sh; assertion quality is INV-118 (anti-proforma). Introduced in #1365 as a child of epic #1363 ("proof must touch reality").',enforcement:"scripts/check-api-e2e.mjs (L1) \u2014 self-gate wired in scripts/check-all.mjs; generated for targets via src/generators/check-all.ts UNCONDITIONAL_EMISSIONS from src/templates/scripts/check-api-e2e.mjs.ejs (CANON-01/04/11). Manifest api-e2e.json and the starter suite + tests/api/run.sh (chmod 0o755) emitted by src/generators/api-e2e.ts (skipIfExists:true; suite scaffolded only for service archetypes). Exit codes per INV-53: 0=PASS/SKIP, 1=absent/empty suite, 2=schema/path-traversal error."},{id:"INV-127",tier:"operational",minGovernanceLevel:"L1",selfOnly:!1,alwaysActive:!1,title:"Frontend archetypes must carry a render-smoke behavioural test",description:`A token-purity pass (no raw hex/px) does not prove the screen renders \u2014 a prior internal project shipped green while its UI was broken empty boxes. Any project whose archetype is frontend-spa, OR that declares a "frontend" lane, MUST carry a render-smoke behavioural test: a headless-browser (Playwright) spec that boots the built SPA and asserts the app shell + key screens mount with real content and ZERO console errors / uncaught page errors. The gate is PRESENCE-based (\u22651 spec matching the render-smoke globs: tests/e2e/render-smoke.spec.ts, **/*.render-smoke.{spec,test}.ts, frontend/tests/e2e/render-smoke.spec.ts) \u2014 assertion quality is the spec author's job, executed by the CI render-smoke lane (16-frontend-quality.yml). Non-frontend archetypes and ungoverned repos (no arbiter.json) SKIP, so they never false-fail. arbiter init scaffolds a starter spec for TS frontends; VRT against a committed baseline remains the stronger optional bar (VRT_SETUP.md). Introduced in #1366 as child of epic #1363 ("proof must touch reality").`,enforcement:"scripts/check-render-smoke.mjs (L1) \u2014 self-gate wired in scripts/check-all.mjs; generated for targets via src/generators/check-all.ts UNCONDITIONAL_EMISSIONS from src/templates/scripts/check-render-smoke.mjs.ejs (imports the shared scripts/lib/glob-walk.mjs helper, also unconditionally emitted). The starter spec is scaffolded for TS frontends by src/generators/frontend-quality.ts from src/templates/e2e/playwright-ts/render-smoke.spec.ts.ejs (skipIfExists:true). Exit codes per INV-53: 0=PASS/SKIP, 1=missing render-smoke spec, 2=schema/parse error."},{id:"INV-128",tier:"operational",title:"Conformance script generated",description:"Every arbiter-governed project must ship a conformance scorecard runner (`scripts/conformance.mjs`). The runner is SELF-CONTAINED: the standalone `arbiter conformance` command was retired, so it no longer shells out to it \u2014 it points at `arbiter gold-audit` (the surviving governance scorecard: level band + missing items) and exits 0. Enforced by the conformance generator (UNCONDITIONAL_EMISSIONS entry in check-all.ts); wired as an advisory (runWarnCheck) in check-all.mjs L2 \u2014 informational, never hard-fails the gate.",selfOnly:!1,alwaysActive:!1,minGovernanceLevel:"L1",enforcement:"scripts/conformance.mjs \u2014 emitted unconditionally for all governed targets via src/generators/check-all.ts UNCONDITIONAL_EMISSIONS from src/templates/scripts/conformance.mjs.ejs (CANON-01/04/11). Invoked as advisory (runWarnCheck) in generated scripts/check-all.mjs L2 behind existsSync guard (#1398/C6). Self-contained fail-closed runner (INV-96): a top-level catch exits non-zero on unexpected error. Exit codes per INV-53: 0=advisory OK, 2=ERROR."},{id:"INV-129",tier:"governance",minGovernanceLevel:"L1",selfOnly:!1,alwaysActive:!0,title:"No tracked data/state files or compiled binaries in the index",description:"Neither the arbiter repo NOR any governed target project may track data/state files (*.sqlite, *.sqlite3, *.db, *.db-shm, *.db-wal) or compiled binaries (ELF / Mach-O / PE, detected by MAGIC BYTES) in the git index. This is distinct from INV-117 (selfOnly *.tgz build-artifact hygiene, kept unchanged): INV-129 is the DATA/STATE axis and applies downstream too. The three-way security split makes this gate load-bearing \u2014 a committed finance.sqlite trips NEITHER gitleaks (no secret pattern) NOR pii-scan (which skips binaries by extension), so without this gate a database full of records sits in history undetected. Binary detection is magic-byte-primary (a renamed Go/Rust binary cannot evade it); go.mod / cargo names are a secondary hint only. Allowlist for intentional binaries: __tests__/fixtures/** path prefixes + font/image/.wasm/.pdf extensions. Fail-closed: a non-git tree is an ERROR (exit 2), never a silent NO-DATA pass.",enforcement:"scripts/check-no-tracked-artifacts.mjs (L1, self \u2014 extended for data/state globs + magic-byte binary detection). Downstream: emitted for governed targets via src/generators/check-all.ts UNCONDITIONAL_EMISSIONS from src/templates/scripts/check-no-tracked-artifacts.mjs.ejs (CANON-01/04/11), wired at L1 in generated scripts/check-all.mjs. Exit codes per INV-53: 0=PASS, 1=FAIL, 2=ERROR."},{id:"INV-130",tier:"operational",minGovernanceLevel:"L1",selfOnly:!1,alwaysActive:!1,title:"E2E flaky-test quarantine annotates but never suppresses, and cannot rot",description:'Every arbiter-governed project ships a stack-agnostic E2E reliability subsystem: a library (scripts/lib/e2e-reliability.mjs \u2014 deterministic failure fingerprint, INFRA/FLAKE/REGRESSION classify, initial\u2192single-test\u2192spec retry ladder, R0\u2013R4 risk tier that fail-closes to R4, append-only JSONL ledger, quarantine schema) plus a fail-closed quarantine hygiene gate (scripts/check-e2e-quarantine.mjs). A quarantine entry ANNOTATES a known-unstable test but NEVER suppresses it \u2014 quarantined tests still run and still report, and CI exit codes are unchanged by membership. The gate enforces that the registry (.arbiter/e2e/quarantine.json) cannot ROT into a permanent silent mute: every entry must carry the full required-field set AND a FUTURE expires date; an expired, incomplete, or malformed entry fails closed (exit 1). Self-SKIPs (exit 0) when no registry is present (vacuous pass). This closes the #1 fake-green source for a prompt-only operator \u2014 a flake re-run green that masks a real regression. #1817 (A3): retries hide races, so the @smoke tier gets ZERO retries \u2014 retryLadder({ tier: "smoke" }) force-truncates the ladder to a single ["initial"] attempt, overriding any caller-supplied opts.scopes (non-bypassable). Quarantine rot is additionally surfaced as a conformance dimension (DISC-e2e-quarantine, tier-1 must-pass): a registry with an expired, incomplete, or malformed entry fails `arbiter conformance`, not just the local CI gate. #1817 (A4): this subsystem is one enforced slice of a broader, installable ~10-rule E2E determinism standard \u2014 docs/GOVERNANCE/E2E_CONSTITUTION.md, generated by generateE2eConstitution for any project with a Playwright harness (frontend-spa or backend-web-db). The installed file is customizable: arbiter never overwrites it once present (skipIfExists). DISC-e2e-quarantine evidence links back to it.',enforcement:"scripts/check-e2e-quarantine.mjs \u2014 emitted unconditionally for all governed targets via src/generators/check-all.ts UNCONDITIONAL_EMISSIONS from src/templates/scripts/check-e2e-quarantine.mjs.ejs (CANON-01/04/11), alongside the library src/templates/scripts/lib/e2e-reliability.mjs.ejs it imports; wired HARD (runCheck) at L1 in generated scripts/check-all.mjs. Track-B (not an arbiter self-gate). Exit codes per INV-53: 0=PASS/absent, 1=FAIL (expired/malformed), 2=ERROR. #1817 (A3): smoke-tier zero-retry contract lives in retryLadder() (same library, same template); quarantine-TTL rot is also probed by src/conformance/dimensions.ts probeE2eQuarantine (DISC-e2e-quarantine), wired in src/commands/conformance.ts collectDimensions() \u2014 an arbiter self-gate (Track-A), distinct from the emitted Track-B script above."},{id:"INV-131",tier:"operational",minGovernanceLevel:"L1",selfOnly:!1,alwaysActive:!1,title:"TDD red\u2192green evidence is re-verified on a fresh checkout in CI",description:"The rigor arbiter applies to itself (scripts/check-tdd-evidence.mjs re-verifies its own red\u2192green evidence in CI) is shipped to governed targets: a generated, self-contained gate (scripts/check-tdd-evidence.mjs) re-verifies, on a FRESH CI checkout, that every task-ID commit on the branch carries valid TDD evidence \u2014 evidence file present + schema-valid, task_id matches, a recognised test-runner FAILURE signature present (proves RED), test_commit_sha exists in history, test_path exists in that commit. The emitted gate INLINES the schema + git checks so a target needs no local arbiter install. The ARBITER-SKIP-TDD commit trailer is forbidden. Self-SKIPs (exit 0) when origin/main is unavailable or no task-ID commits exist. Without this, a governed target never re-verifies its own evidence on a clean CI machine \u2014 the rigor stops at arbiter and is not delivered to its users.",enforcement:"scripts/check-tdd-evidence.mjs \u2014 emitted unconditionally for all governed targets via src/generators/check-all.ts UNCONDITIONAL_EMISSIONS from src/templates/scripts/check-tdd-evidence.mjs.ejs (CANON-01/04/11); wired HARD (runCheck) at L2 in generated scripts/check-all.mjs (independent of debt gates). Track-B (not an arbiter self-gate; arbiter dogfoods its own scripts/check-tdd-evidence.mjs which delegates to the CLI). Exit codes per INV-53: 0=PASS/vacuous, 1=FAIL (missing/inconsistent evidence or forbidden skip trailer), 2=ERROR."},{id:"INV-132",tier:"operational",minGovernanceLevel:"L1",selfOnly:!0,alwaysActive:!1,adr:"ADR-098",title:"arbiter init exposes a progressive-adoption tier on-ramp (bootstrap \u2192 L4)",description:"`arbiter init --tier <bootstrap|L1|L2|L3|L4>` is a progressive-adoption on-ramp so brownfield/startup teams are not forced into all-or-nothing governance. `bootstrap` is the gentlest Day-1 entry \u2014 it desugars to governance L1 (the minimal runnable gate) plus brownfield baseline lock-in, so a messy repo gets a gate that runs and passes (pre-existing debt is captured as a baseline, not thrown as day-1 red); `L1`\u2013`L4` are governance-level aliases. The tier adds NO new persisted config field \u2014 it is a view over (governanceLevel, brownfield, grace) and desugars into the existing `--level` + `--brownfield` settings. The adoption ladder bootstrap\u2192L1\u2192L2\u2192L3\u2192L4 has documented entry/exit criteria; graduation uses the existing `arbiter upgrade-level` (grace-softened, ADR-028) and `arbiter configure` flows. selfOnly: this governs arbiter's own init CLI behaviour, not a gate emitted into target projects.",enforcement:"src/commands/init.ts (resolveAdoptionTier desugars --tier into governanceLevel + brownfield; runInit applies it before level resolution) + the --tier CLI option in src/cli.ts. Verified by __tests__/commands/init-tier.test.ts (red\u2192green: bootstrap \u2192 L1 + brownfield, L1\u2013L4 pass-through, invalid rejected, bootstrap config emits a runnable check-all.mjs). Documented in ADR-098. No runtime gate script (selfOnly CLI behaviour); not emitted to target projects."},{id:"INV-133",tier:"governance",minGovernanceLevel:"L2",selfOnly:!1,alwaysActive:!0,title:"TODO max-age enforced via linked-issue creation date",description:"A TODO(#NNN) whose LINKED ISSUE was created more than MAX_AGE_DAYS (default 180) ago FAILS the gate. Age is derived from the issue `created_at` ONLY \u2014 never from line/blame/git metadata \u2014 so a TODO cannot be silently kept alive by re-touching its line. This complements INV-21 (orphan-TODO: every TODO must carry an issue ID): INV-21 makes a TODO traceable, INV-133 makes a traceable TODO age out. The gate walks source for TODO(#NNN), resolves OWNER/REPO from the git origin remote, and for each issue calls `gh api repos/OWNER/REPO/issues/NNN --jq .created_at` (cached per issue number to avoid rate limits). It GRACEFULLY SKIPS (exit 0) \u2014 never false-fails \u2014 when gh is missing, no token is present, the repo is offline, or no created_at could be resolved; only a genuinely over-age issue produces a FAIL. The age decision is a PURE function (isOverAge / classifyOverAge over an injected {issueNumber\u2192created_at} map) so it is unit-tested without any live gh.",enforcement:"scripts/check-todo-max-age.mjs (L2, self) \u2014 emitted unconditionally for governed targets via src/generators/check-all.ts UNCONDITIONAL_EMISSIONS from src/templates/scripts/check-todo-max-age.mjs.ejs (CANON-01/04/11), wired at L2 in the generated scripts/check-all.mjs (behind the standard runCheck path) and in the self scripts/check-all.mjs. Graceful-skip: gh missing / token absent / offline \u2192 SKIP (exit 0), never a false-fail. Verified by __tests__/scripts/check-todo-max-age.test.ts (pure red\u2192green: over-age map \u2192 FAIL classification, empty/offline map \u2192 SKIP) and __tests__/templates/check-todo-max-age-render.test.ts (CANON-04 render + cross-stack L2 wiring). Exit codes per INV-53: 0=PASS/SKIP, 1=FAIL (over-age), 2=ERROR."},{id:"INV-135",tier:"operational",minGovernanceLevel:"L1",selfOnly:!1,alwaysActive:!1,title:"doc-set + anti-fake-green runners generated",description:'Every arbiter-governed project ships TWO Track-B thin runners that delegate to arbiter via npx so a consumer needs NO local `yaml` dep: scripts/check-doc-set.mjs (delegates to `arbiter doc-set` \u2014 the deterministic doc-set presence audit) and scripts/check-anti-fake-green.mjs (delegates to `arbiter anti-fake-green` \u2014 the disarm-proof guard aggregate). Both follow the gold-audit thin-runner shape (#1419, INV-128): a STATIC `spawnSync("npx", ["--no-install", "arbiter", "<cmd>", ...args])` delegation. Wired ADVISORY (runWarnCheck) at L2 in generated check-all.mjs so a fresh consumer passes with no day-1 redness \u2014 doc-set is advisory unless --strict, and the anti-fake-green gh-audit guards fail OPEN when `gh` is absent. The engine + its `yaml` parse run inside arbiter\'s own environment, never in the consumer.',enforcement:"scripts/check-doc-set.mjs + scripts/check-anti-fake-green.mjs \u2014 emitted unconditionally for all governed targets via src/generators/check-all.ts UNCONDITIONAL_EMISSIONS from src/templates/scripts/check-doc-set.mjs.ejs and src/templates/scripts/check-anti-fake-green.mjs.ejs (CANON-01/04/11). Both wired as advisory (runWarnCheck) in generated scripts/check-all.mjs L2 behind existsSync guards (#1428). Exit codes per INV-53: 0=PASS/advisory, 1=FAIL, 2=ERROR."},{id:"INV-134",tier:"operational",minGovernanceLevel:"L2",selfOnly:!1,alwaysActive:!1,title:"per-module coverage non-regression ratchet",description:"Per-MODULE (per-file/package) test coverage is held to an upward-only ratchet with a \xB10.5pp slack: a module whose line coverage drops more than 0.5 percentage points below module-coverage-baseline.json is a regression. All-languages, greenfield-aware (a module with zero executable lines contributes nothing and never false-fails). ADVISORY at L2 (start-warn, promote-later) to bound false positives while the per-module baseline beds in. Complements \u2014 does not duplicate \u2014 the FE per-layer coverage ratchet (INV, FSD layers, frontend-only), the total-coverage greenfield gate (INV-30), and the bloat/debt ratchets (those gate size and debt, not per-module coverage erosion).",enforcement:"scripts/verify-module-coverage.mjs (Track-B \u2014 emitted to governed targets, not an arbiter self-gate) \u2014 emitted unconditionally via src/generators/check-all.ts UNCONDITIONAL_EMISSIONS from src/templates/scripts/verify-module-coverage.mjs.ejs (CANON-01/04/11). Reads the per-language coverage summary (TypeScript/JavaScript coverage/coverage-summary.json robustly; Java/Python/Rust/Go scaffolded to SKIP when their summary is absent/unsupported \u2014 never a false-fail), computes per-module line pct, and compares each module vs module-coverage-baseline.json via the pure exported compareModuleCoverage (\xB10.5pp slack, upward-only). First run with coverage and no baseline seeds the baseline (exit 0); no coverage artifact \u2192 SKIP; --update-baseline advances it (never auto-updated in CI). Wired ADVISORY (runWarnCheck) at L2 in the generated scripts/check-all.mjs behind existsSync. Verified by __tests__/generators/module-coverage-ratchet.test.ts (pure ratchet red\u2192green: within slack PASS, drop >0.5pp VIOLATION, greenfield/first-run seed) + __tests__/templates/module-coverage-render.test.ts (render across archetypes). exit 0=PASS/SKIP/seed, 1=regression (#1457)."},{id:"INV-138",tier:"governance",selfOnly:!0,alwaysActive:!0,title:"Acceptance-criteria anchor \u2014 plans freeze issue AC; fit is evidenced per criterion",description:'A green gate certifies mechanics, not intent; rework lives in the gap between "green" and "what was asked". The anchor closes it in three steps. (1) Entry gate: an issue enters a wave / ship preflight only when its body carries explicit `AC-N:` acceptance criteria (beyond template stock lines), a Non-goals section, and the files/contracts it touches \u2014 otherwise it is labeled needs-clarification and excluded (issue-readiness.mjs, orchestration-time, gh allowed, deliberately not a gate step). (2) External DoD anchor: during implementation phases the active task plan freezes the issue AC verbatim under "## Acceptance Criteria" (explicit AC-N ids) plus "## Non-Goals", so acceptance tests derive from the issue, not from the implementing agent self-grading its own interpretation. (3) Fit evidence: at verification/close the reviewer-written .arbiter/evidence/ac-fit/<task>.json must cover every AC-N with verdict PASS and a cited file:line \u2014 the mechanical form of "unproven criterion = REJECT". Rework telemetry (rework-log.mjs, committed .arbiter/rework/ledger.jsonl) tags every redone PR with why + where-caught, making the issue template self-correcting. Flag-gated (features.acceptanceAnchor / ARBITER_ACCEPTANCE_ANCHOR) and vacuous without an active task, so main, CI and fresh clones stay green. AC\u2194test-title mapping stays a reviewer rubric, deliberately not a grep gate (CANON-22: contested heuristics advise).',enforcement:"scripts/check-acceptance.mjs (L1, flag-gated features.acceptanceAnchor; --plan mode for wave integrate) \u2014 wired runCheck in scripts/check-all.mjs. Pure parsing/validation core in scripts/lib/acceptance-criteria.mjs. Verified by __tests__/scripts/acceptance-criteria-lib.test.ts + __tests__/scripts/check-acceptance.test.ts + __tests__/scripts/issue-readiness.test.ts + __tests__/scripts/rework-log.test.ts (red\u2192green). The orchestration tools (issue-readiness.mjs, rework-log.mjs, lib/acceptance-criteria.mjs) ARE emitted to governed targets via src/generators/check-all.ts UNCONDITIONAL_EMISSIONS (CANON-01/04, __tests__/templates/acceptance-anchor-scripts-render.test.ts), and #2405 closed the ADR-110 follow-up: check-acceptance.mjs is emitted from src/templates/scripts/check-acceptance.mjs.ejs and wired in the generated check-all via the `acceptance-anchor` row of gate-registry.yml.ejs (advisory). The catalog entry stays selfOnly because ACTIVATION is opt-in per project (features.acceptanceAnchor / ARBITER_ACCEPTANCE_ANCHOR): the mechanism now exists in every governed tree, but the RULE is not one every target is held to by default. exit 0=PASS/SKIP, 1=FAIL, 2=ERROR per INV-53.",adr:"ADR-110"},{id:"INV-139",tier:"governance",selfOnly:!0,alwaysActive:!0,title:"Fixture and smoke output must never land in real evidence directories",description:"A smoke or fixture run must never write into a real evidence root (.arbiter/evidence, .evidence). The origin is the #2176 /ship-v2 study, where two contaminated runs carrying `fake-*` finding IDs reached the real result set, passed every mechanical guard, and were caught only by the semantic judge. Detection is ANCHORED-SCALAR over parsed .json/.jsonl: whitespace-free string values and object keys matching /^fake-/ or containing STUDY_FAKE, deliberately NOT a substring grep because the live corpus legitimately contains 158 `fake-green`/`fake-db` occurrences inside multi-line diff and log blobs. An unparseable document is skipped, non-JSON artifacts are out of scope, and NO-DATA is a PASS so fresh clones and ungoverned repos never false-fail. The guard scans the FILESYSTEM rather than the git index so contamination is caught before it is committed; selfOnly because the marker set is arbiter-study vocabulary and would be a false-positive minefield in an arbitrary target project \u2014 the Track-B mirror waits on a project-configurable marker set.",enforcement:"scripts/check-fixture-isolation.mjs (L1 gate, self) \u2014 wired in scripts/check-all.mjs and also enrolled in the anti-fake-green aggregate roster (scripts/lib/anti-fake-green-guards.mjs, class file-scan, so a broken guard fails the aggregate) with a discrimination proof in scripts/lib/guard-flip-registry.mjs. Verified by __tests__/scripts/check-fixture-isolation.test.ts (red\u2192green). exit 0=PASS/NO-DATA, 1=contamination, 2=ERROR per INV-53 (#2181)."},{id:"INV-140",tier:"governance",selfOnly:!0,alwaysActive:!0,title:"Every identifier scheme is registered, collision-free, and its citations resolve",description:"An identifier scheme that lives only in the heads of the people using it drifts into two schemes wearing one prefix. That happened twice here: `MN` meant both a product milestone and an agent-orchestration methodology measure, and `E1-E7` meant both an anti-context-rot enforcer and a gold-registry enforcement dimension \u2014 so a bare citation was ambiguous and no mechanism could say so. Worse, `OD-NN` was cited in a hook, two empirical tests and the advisory ledger with NO file defining it, making an invented decision id indistinguishable from a real one. docs/internal/SYSTEM/ID-REGISTRY.md now declares every scheme in one machine-parsed block \u2014 prefix, anchored pattern, SSOT, gate, track, tool, hook, status \u2014 and the gate proves no two schemes can match the same identifier (by expanding each pattern into a canonical sample and cross-matching every regex), that every declared SSOT resolves on disk, and that every OD-NN in the tree resolves to a row in the owner-decision registry. A `staged` row is a DATED obligation: it names the wave that wires it and the gate fails once its `expires` passes, the same dated-debt discipline as INV-31.",enforcement:"scripts/check-id-registry.mjs (L1 gate, self) \u2014 wired in scripts/check-all.mjs as 'id registry (INV-140)'. Validates against schemas/id-registry.schema.json using the shared validator in scripts/lib/agent-return-validate.mjs (no second validator: CANON-16). Verified by __tests__/scripts/check-id-registry.test.ts. exit 0=PASS, 1=violation, 2=ERROR per INV-53. Staged self-only (scripts/canon01-self-only.json): the Track-B twin ships when governed projects carry their own registry."},{id:"INV-141",tier:"governance",selfOnly:!0,alwaysActive:!0,title:"No artifact type exists as documentation alone \u2014 every active scheme is wired",description:"The failure this closes is the one every governance framework dies of: a rule is written, nothing runs it, and the document reads like coverage in an audit. For each `active` row in the ID registry the gate proves three legs are real \u2014 the gate script is registered on the side its `track` names (scripts/check-all.mjs for self, the declarative Track-B roster src/templates/scripts/gate-registry.yml.ejs for target, so a correctly-declined gate is never mistaken for an unwired one), the `tool` verb resolves to a .command() in src/cli.ts, and the `hook` file exists, is registered in .claude/settings.json, and \u2014 since wave 8 of #2480 \u2014 actually COVERS the row's SSOT. That last clause was the gate's own blind spot: existence and registration are necessary and never sufficient, and four rows passed this check while naming a hook whose dispatch table matched none of their instances, so the edit-time enforcement they advertised could not have fired. An unregistered hook never fires; an uncovering one never fires either. A fourth leg since wave 8: a declared `graphNode` must be a NodeKind the graph actually has \u2014 six rows asserted one while src/graph/model.ts had none of them, the same statement of intent reading as a statement of fact. `staged` rows are exempt by design \u2014 a stage is a dated obligation enforced by INV-140, not a second copy of the same failure \u2014 and are COUNTED instead, against a monotone ratchet over the unwired legs. There is deliberately no --allow-increase: the count may fall freely, and raising it means hand-editing scripts/data/ontology-baseline.json in the same PR as the row that needs it, where the number lands in the diff beside its justification.",enforcement:"scripts/check-ontology-wired.mjs (L1 gate, self) \u2014 wired in scripts/check-all.mjs as 'ontology wired (INV-141)', with the ratchet in scripts/data/ontology-baseline.json. Verified by __tests__/scripts/check-ontology-wired.test.ts. exit 0=PASS, 1=violation, 2=ERROR per INV-53. Staged self-only, downstream of INV-140."},{id:"INV-142",tier:"governance",selfOnly:!0,alwaysActive:!0,title:"An edited ontology artifact is schema-valid at edit time, not at merge time",description:"A schema checked only in CI teaches the agent an hour late, after a commit and a push, when the cheapest moment to learn was the edit itself. One PostToolUse hook carries a table of registered instances (path or directory prefix, schema, and how to extract the document \u2014 the whole file as JSON or YAML, or a ```json fence between sentinels) and validates whatever was just written against its registered schema, blocking with exit 2. The table is the mechanism AND was the gap: until wave 8 of #2480 it held two entries while four ID-registry rows named this hook as their edit-time enforcement, so no milestone, epic, source record or use case was ever checked at the edit. INV-141 now resolves each row's SSOT through this table, so that claim cannot be made again without being true. One hook rather than one per artifact is the point: each wave that lands an artifact type adds a single line to the table and the type becomes edit-time enforced for free. The hook fails OPEN on its own infrastructure \u2014 if the validator or schema cannot be loaded it exits 0 \u2014 because a guard that blocks an unrelated edit when its own dependency moved is a worse failure than the one it prevents; the CI gate remains the backstop.",enforcement:".claude/hooks/post-edit-artifact-schema.mjs (PostToolUse Edit|Write, self) \u2014 registered in .claude/settings.json. Exit 2 is the only blocking code under the hook protocol; exit 1 prints and the agent never sees it, which would make the guard decoration. Verified by __tests__/hooks/post-edit-artifact-schema.test.ts. Staged self-only: the Track-B twin ships with the target-side registry."},{id:"INV-143",tier:"governance",selfOnly:!0,alwaysActive:!0,title:"The arbiter <-> forma schema contract is pinned and gated on both sides",description:"arbiter owns the governance ontology; forma owns the stack-agnostic C4 model shape and renders what arbiter defines. Two repositories sharing schemas by good intentions drift the first time either ships a change alone, and the drift surfaces as a visualiser silently rendering a model it half-understands. schemas/CONTRACT.json names, for each shared schema, the owning repository, the path inside it, a sha256, and which repos vendor a pinned copy. Both repositories hold a byte-identical copy and each gates its own half: the owner re-hashes what it owns, the consumer re-hashes what it vendored. Editing a shared shape without re-pinning turns the owning repo red at once; a stale vendored copy turns the consuming repo red. When both checkouts sit side by side the gate also proves the two manifests are byte-identical and the sibling files still hash to the pin \u2014 and when they do not, that half SKIPS OUT LOUD rather than passing in silence, because a cross-repo check that quietly does nothing is the exact failure the contract exists to prevent.",enforcement:"scripts/check-forma-contract.mjs (L1 gate, self) \u2014 wired in scripts/check-all.mjs as 'forma schema contract (INV-143)'; forma runs the mirror gate scripts/check-arbiter-contract.mjs in its own CI. Verified by __tests__/scripts/check-forma-contract.test.ts. exit 0=PASS, 1=violation, 2=ERROR per INV-53. Confirmed self-only: the subject is the boundary between these two repositories, which a governed target owns neither side of."},{id:"INV-144",tier:"governance",selfOnly:!1,alwaysActive:!0,title:"The architecture document is a filled structure, not a surviving skeleton",description:"arc42 is twelve enumerable slots, and a project that scaffolds one and never fills it has a document that answers no question while satisfying every presence check. This invariant makes the structure addressable: ARC-01..ARC-12 are parsed out of the architecture document, and every slot the arc42 skeleton for that project's tier column provides must be present in it \u2014 a section deleted from the document is a structural gap, not a simplification. The required set is READ from the skeleton rather than restated, so the gate can never hold a second opinion about what a tier owes, and the skeleton's own gaps against canonical arc42 ride a second ratchet, so dropping a section from a skeleton cannot quietly lower the bar for every project that receives it. A hollow slot \u2014 a body that is nothing but the skeleton prompt, or a lone filler word standing in for content \u2014 is counted, not forbidden: the count may fall freely and may never rise, so a section may be left unfilled but a new unfilled one may not be added. On a first run with no baseline the count is recorded AND PERSISTED, because a freshly generated arc42 is hollow by construction and a gate that made `arbiter init` produce a red repo would only teach people to delete the gate \u2014 but a count tolerated only in memory means the ratchet never arms in a governed project at all, so the first clean run writes the baseline it measured.",enforcement:"scripts/check-arc42-slots.mjs \u2014 self: L1, runCheck, unconditional, wired in scripts/check-all.mjs as 'arc42 slots (INV-144)'. Track B is DELIBERATELY WEAKER and the asymmetry is declared here rather than left to be discovered: the row in src/templates/scripts/gate-registry.yml.ejs is L2, runWarnCheck, and gated on enableDebtGates, because a freshly generated arc42 is hollow by construction and a gate that made `arbiter init` produce a red repo would only teach people to delete it. The governed project receives a thin runner delegating to `arbiter doc-set --arc42`, so the engine and its skeletons stay in one place and no copy can drift; the engine ships via package.json files[], enforced by the DERIVED engine set in check-tarball-contents.mjs (#2335/#2480 \u2014 a hand-maintained list of the same paths failed to ratchet and let a fourth engine ship unshipped). Verified by __tests__/scripts/check-arc42-slots.test.ts (43 cases), which reads the real skeletons and a package-shaped dist-only layout rather than fixtures alone. exit 0=PASS, 1=violation, 2=ERROR per INV-53."},{id:"INV-145",tier:"governance",selfOnly:!1,alwaysActive:!0,title:"Adversarial review closes only when nothing above low severity survives",description:"One pass of review finds what one reader happens to look for. A high-stakes change is therefore reviewed by independent skeptics carrying a REFUTE mandate, and the loop REPEATS \u2014 each hop attacking the fixes the previous hop forced \u2014 until no finding above `low` is left unaddressed. This is the complement of the refutation-majority rule, not a duplicate of it: that rule stops a phantom finding being acted on (the false positive), this one stops the loop ending while something real is still open (the false negative). Both read the same skeptic envelopes, so the second axis needs no new artifact. Severity is the HIGHEST any skeptic assigned \u2014 when two disagree the loop must clear the worse reading, not the kinder one. A finding below quorum, or majority-REFUTED, never blocks: one skeptic's false alarm must not hold a wave hostage, which is the very failure the majority rule exists to prevent. A hop that cannot reach an independent reviewer may be self-probed, but the marker records `degraded` and the round never counts as independent \u2014 a self-review filed as an independent one is precisely the fake-green this catalog exists to prevent.",enforcement:"scripts/check-refutation-verdicts.mjs \u2014 wired on BOTH tracks already as 'refutation majority (E2 #1943)': runWarnCheck in scripts/check-all.mjs and a registry row in src/templates/scripts/gate-registry.yml.ejs, so this axis inherits that wiring rather than adding a second gate over the same evidence (CANON-16). Marker-gated exactly as the majority axis is: with .arbiter/evidence/agent-returns/<task>/refutation-required.json present, every majority-UPHELD finding at critical/high/med must appear in the acted-on set; absent the marker the gate passes vacuously. Verified by __tests__/scripts/check-refutation-verdicts.test.ts, tamper-proven in both directions (an unaddressed high fails; the same tree with it addressed and only a low left passes). exit 0=PASS, 1=violation, 2=ERROR per INV-53. CANON-24."},{id:"INV-146",tier:"governance",alwaysActive:!0,title:"A milestone is done only when its exit criteria carry evidence",description:'A roadmap in prose cannot be wrong, because nothing reads it. Milestones are therefore a typed SSOT \u2014 docs/internal/PRODUCT/MILESTONES.yml \u2014 carrying a GSN goal (claim plus the strategy by which the exit criteria are argued to establish it), exit criteria, dependencies and a Now/Next/Later horizon. Three properties are enforced that a document cannot hold: the dependency graph is ACYCLIC, so a plan cannot quietly require itself; granularity DECAYS with distance, so a `later` milestone may not carry a due date, which is false precision the schema refuses rather than a convention readers are trusted to follow; and `done` is FAIL-CLOSED on evidence \u2014 every exit criterion must cite a resolvable artifact, and `verified` requires those citations to actually resolve. That last rule is the reason the gate exists: a roadmap whose `done` means "someone typed done" is not a weaker roadmap, it is a false claim with a schema around it. The migration is FORWARD-ONLY by owner decision \u2014 historical milestones stay in the prose archive rather than having exit-criteria evidence reconstructed after the fact to satisfy the gate, which would be exactly the fake-green this catalog exists to prevent. The same SSOT carries the EPIC index (EP-NN, wave 8), read by the same gate on purpose: an epic that targets no milestone and a milestone claiming an epic that does not exist are one defect seen from two ends, and the join is checked in BOTH directions. An issue may be claimed once, as an epic or as a plain member, never both; a terminal epic status carries the same fail-closed evidence_ref rule the milestone statuses do; and a milestone cannot be `done` while an epic targeting it is open. What the gate CANNOT do is compare a status against GitHub \u2014 it is offline by contract (INV-13) \u2014 and that gap is not hypothetical: all three epics first recorded here were already closed, two abandoned rather than delivered, while the file called them open.',enforcement:'scripts/check-milestones.mjs \u2014 wired on BOTH tracks: self as \'milestones (INV-146)\' via runCheck in scripts/check-all.mjs, target via the milestones row in src/templates/scripts/gate-registry.yml.ejs, emitted WITH schemas/milestone.schema.json (CANON-11). The emitted twin reads a fenced JSON block in docs/MILESTONES.md rather than YAML, and that is forced rather than chosen: `yaml` is arbiter\'s dependency, no emitted script imports it, and a Go or Python project receives 77 .mjs gates and NO package.json in which to declare one \u2014 the same constraint that already shaped docs/SOURCES.md and docs/USE_CASES.md. Every RULE is identical; the reader, the path, and the INV namespace (AGENTS.md on the target, the TypeScript catalog here) are the three divergences, pinned in .dogfood-divergences.json. Because the two copies differ in their READER, the emitted one is proven by EXECUTION rather than by rendering: __tests__/templates/track-b-evidence-gates.test.ts renders it into a project-shaped tree and runs it, cycle, decay, fail-closed `done`, the epic join and both malformed-block paths included (#2335). A missing MILESTONES.yml SKIPs out loud rather than passing silently, so a project without a codified roadmap is distinguishable from one whose gate never ran. `--emit <path>` writes the machine projection forma consumes (schema arbiter-milestones-v1, carrying the epic index) \u2014 written only AFTER every rule passes, so an invalid plan cannot produce a projection, and omitting estimate_days rather than defaulting it so a consumer can tell "no estimate" from "zero". Verified by __tests__/scripts/check-milestones.test.ts (84 cases), tamper-proven in both directions on every rule: a cycle, a dangling depends_on, a duplicate id, a `later` carrying a due date, `done` without evidence and `verified` citing an unresolvable ref each fail, and the same tree with the defect removed passes. exit 0=PASS or SKIP, 1=violation, 2=ERROR per INV-53.'},{id:"INV-147",tier:"governance",alwaysActive:!0,title:"A cited source is quotable, and the quotation checks out",description:"A URL in a document proves nothing: the page can change, or can never have said what it is cited for, and nobody notices either way. A source is therefore admitted as evidence only with a COMMITTED, citation-length excerpt and the sha256 of that excerpt, so every quotation the project makes is checkable offline, by machine, against evidence that cannot drift without the gate noticing. Both halves are load-bearing and neither is sufficient alone: a substring check passes on an excerpt someone edited after the fact, and a hash proves the excerpt is unchanged while saying nothing about whether the quotation appears in it at all. Deliberately NOT done: the url is recorded provenance and is never dereferenced \u2014 a gate that fails when a website is down fails for a reason unrelated to the claim it guards \u2014 and RELEVANCE is a judgement, not a decidable property, so tier 1 refuses to pretend it can settle it and leaves that to independent skeptics (tier 2) and graph reachability (tier 3).",enforcement:"scripts/check-sources.mjs \u2014 wired on the SELF track as 'sources tier 1 (INV-147)' via runCheck in scripts/check-all.mjs, and on TRACK B via the sources row in src/templates/scripts/gate-registry.yml.ejs, from src/templates/scripts/check-sources.mjs.ejs emitted by src/generators/check-all.ts. Validates the SOURCES registry against schemas/source-record.schema.json, then proves each excerpt hashes to its recorded content_hash and contains every quoted_text literally. A missing SOURCES.md SKIPs out loud \u2014 a project need not cite anything, but a skip must never be mistakable for a pass. The schema travels WITH the gate (schemas/source-record.schema.json is emitted beside it), which is what unblocked the port: shipping the rule without its contract would have handed a governed project an error the moment it recorded a source. The one divergence between the two copies is the registry path \u2014 docs/SOURCES.md in a target, docs/internal/PRODUCT/SOURCES.md here \u2014 pinned in .dogfood-divergences.json. The SRC row stays `staged` in the ID registry until its CLI verb (arbiter sources) and its SOTA-required hook land with tiers 2 and 3 \u2014 neither exists yet, so neither is cited here as enforcement. Verified by __tests__/scripts/check-sources.test.ts (21 cases) and tamper-proven on arbiter's own two recorded sources in both directions: editing an excerpt so the quote still appears but the hash drifts fails, and claiming a quote the source never made fails. exit 0=PASS or SKIP, 1=violation, 2=ERROR per INV-53."},{id:"INV-148",tier:"governance",selfOnly:!0,alwaysActive:!0,title:"A runbook names the invariants it handles, and what it names is real",description:'A runbook is what a human does once enforcement has already fired and the thing is broken, so it is the one governance artifact whose value is realised at the worst possible moment. Two directions are checked, and they are DELIBERATELY ASYMMETRIC because only one of them is honestly pass/fail today. Hard: every doc whose frontmatter tags declare kind/runbook carries an RB-NN canonical_id, unique across the set, and a non-empty `handles:` list whose every INV id resolves in the catalog \u2014 an unresolvable reference is the runbook equivalent of a dangling test_ref, reading as coverage while covering nothing, and a runbook that handles nothing named is a document about a topic rather than a response to a failure. Ratcheted: operational-tier invariants that no runbook handles. That second direction is a DEBT COUNTER and is labelled one, because there are 49 operational invariants and two runbooks: shipping "every operational invariant needs a runbook" as a rule would be red on arrival and instantly baselined into meaninglessness, which is the green-because-baselined failure this catalog exists to prevent. As a ratchet the debt is visible, bounded, and cannot grow in silence. Discovery reads FRONTMATTER, never greps: docs/GOVERNANCE.md and docs/INDEX.md both contain the literal string kind/runbook while merely listing tags, so a grep would have made the gate\'s first finding its own false positive.',enforcement:"scripts/check-runbook-coverage.mjs \u2014 wired on the SELF track as 'runbook coverage (INV-148)' via runCheck in scripts/check-all.mjs, with the ratchet in scripts/data/runbook-baseline.json. Self-only, and the asymmetry is declared rather than left to be found: the RB scheme is `staged` in docs/internal/SYSTEM/ID-REGISTRY.md with a dated expiry, and the Track-B emission lands with it \u2014 claiming both tracks before that exists is the error INV-144 was caught making. A missing catalog SKIPs out loud, visible as verdict \"skip\" under --json, so a tree with no invariants is distinguishable from a gate that never ran. Unlike the ADR ratchet, --update-baseline RECORDS a rise rather than refusing it: the counter is over a catalog this gate does not own, and adding an operational invariant legitimately raises it \u2014 what stays refused is a SILENT rise, since the new number lands in the diff either way. Verified by __tests__/scripts/check-runbook-coverage.test.ts, tamper-proven in both directions on every rule: an empty or off-pattern canonical_id, a duplicate RB id, an empty handles list, a handles ref naming an invariant that does not exist, and a ratchet rise each fail, and the same tree with the defect removed passes. `--emit <path>` writes the projection a viewer consumes (schema arbiter-runbooks-v1), carrying the uncovered LIST rather than only its length \u2014 an operations view whose only signal is a number cannot tell an operator which failure they have no procedure for. exit 0=PASS or SKIP, 1=violation, 2=ERROR per INV-53."},{id:"INV-149",tier:"governance",alwaysActive:!0,title:"A use case names an actor, a goal, and features that exist",description:"A use-case matrix decays by RENAME. A feature is renamed in the matrix, every use case pointing at the old id keeps reading exactly like coverage, and nothing notices \u2014 which is why the central rule here is not that use cases exist but that every featureId RESOLVES. The schema carries the other half: an actor (a use case with none is a feature description wearing a use case's name), a goal in the actor's terms, and `featureIds` minItems 1, because a use case demanding no feature is a promise with nothing behind it. The scenario JOIN is checked in both directions: a tabletop scenario naming a use case that does not exist fails, and a use case claiming status `exercised` that no scenario walks fails \u2014 status is not a walk. This gate does its real work on the TARGET track, and the asymmetry is structural rather than an omission: arbiter's own feature-matrix rows are cross-cutting capability areas, so one of its use cases would name nearly all 62 of them and the link would carry no information. Measured against a real product matrix a use case names ONE or TWO features, and that ratio is what makes the edge worth checking. So the self copy SKIPs out loud and the emitted twin is proven to RUN rather than proven to render.",enforcement:"scripts/check-use-cases.mjs \u2014 wired on BOTH tracks: self as 'use cases (INV-149)' via runCheck in scripts/check-all.mjs, target via the use-cases row in src/templates/scripts/gate-registry.yml.ejs. Emitted WITH schemas/use-case.schema.json (CANON-11) \u2014 the lesson the sources port learned the hard way: a rule shipped without its contract dies on MODULE_NOT_FOUND the moment a project uses it, and a missing schema is exit 2 here rather than a silent skip. The three document paths are the only divergence between the copies (docs/USE_CASES.md, docs/FEATURE_MATRIX.md and docs/TABLETOP-SCENARIOS.md in a target; the docs/internal/ equivalents in arbiter), pinned in .dogfood-divergences.json. A missing SSOT SKIPs out loud and surfaces as verdict \"skip\" under --json. Verified by __tests__/templates/track-b-evidence-gates.test.ts, which RENDERS the gate into a project-shaped tree and RUNS it \u2014 every failure path included, because a gate whose rules are exercised nowhere in CI is a gate that has never run (#2335). `--emit <path>` writes the projection a viewer consumes (schema arbiter-use-cases-v1), written only AFTER every rule passes so an invalid set cannot produce one, and carrying `exercisedBy` DERIVED from the scenarios rather than copied from the declared status \u2014 the two can disagree and only one of them is checkable. exit 0=PASS or SKIP, 1=violation, 2=ERROR per INV-53."}];
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Language, GovernanceLevel } from '../wizard/types.js';
|
|
2
|
+
import type { Invariant, InvariantTier, InvariantPreset } from './types.js';
|
|
3
|
+
export declare function getFilteredInvariants(config: {
|
|
4
|
+
language: Language;
|
|
5
|
+
governanceLevel: GovernanceLevel;
|
|
6
|
+
invariantTiers: InvariantTier[];
|
|
7
|
+
/** Include arbiter-internal invariants (selfOnly: true). Default: false (target-project context). */
|
|
8
|
+
includeArbiterInternal?: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Include extended opt-in invariants (INV-62..INV-71).
|
|
11
|
+
* Default: false. Enable via arbiter.json governance.invariants_catalog = 'extended'.
|
|
12
|
+
*/
|
|
13
|
+
includeExtendedInvariants?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Project-declared invariants (PROJ-NN, #2035) merged into the filtered
|
|
16
|
+
* result. This is the SINGLE merge point through which project invariants
|
|
17
|
+
* propagate to AGENTS.md, GLOBAL_INVARIANTS.md, the graph, explain, and the
|
|
18
|
+
* parity gate. Project invariants pass the same per-entry gates as catalog
|
|
19
|
+
* entries (retired/selfOnly/extended/language/minGovernanceLevel) EXCEPT the
|
|
20
|
+
* tier filter — a project explicitly declared them, so a tier mismatch never
|
|
21
|
+
* silently drops them. Deterministic order: catalog entries first, then
|
|
22
|
+
* project invariants in declaration order.
|
|
23
|
+
*/
|
|
24
|
+
projectInvariants?: Invariant[];
|
|
25
|
+
}): Invariant[];
|
|
26
|
+
export declare function getInvariantsByTier(invariants: Invariant[]): Map<InvariantTier, Invariant[]>;
|
|
27
|
+
export declare function presetToTiers(preset: InvariantPreset): InvariantTier[];
|
|
28
|
+
export declare function defaultPresetForLevel(level: GovernanceLevel): InvariantPreset;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{INVARIANT_CATALOG}from"./catalog.js";import{levelAtLeast}from"../config/levels.js";function meetsGovernanceLevel(required,actual){return required?levelAtLeast(actual,required):!0}export function getFilteredInvariants(config){const filtered=INVARIANT_CATALOG.filter(inv=>!(inv.status==="retired"||inv.optInGroup==="extended"&&!config.includeExtendedInvariants||inv.selfOnly&&!config.includeArbiterInternal||inv.languages&&!inv.languages.includes(config.language)&&config.language!=="multi"||!meetsGovernanceLevel(inv.minGovernanceLevel,config.governanceLevel)||!inv.alwaysActive&&!config.invariantTiers.includes(inv.tier))),project=(config.projectInvariants??[]).filter(inv=>inv.status==="retired"||inv.optInGroup==="extended"&&!config.includeExtendedInvariants||inv.selfOnly&&!config.includeArbiterInternal||inv.languages&&!inv.languages.includes(config.language)&&config.language!=="multi"?!1:meetsGovernanceLevel(inv.minGovernanceLevel,config.governanceLevel));return[...filtered,...project]}export function getInvariantsByTier(invariants){const map=new Map;for(const inv of invariants){const existing=map.get(inv.tier)??[];existing.push(inv),map.set(inv.tier,existing)}return map}export function presetToTiers(preset){const base=["architectural","governance"];switch(preset){case"essential":return base;case"standard":return[...base,"data","operational"];case"full":return[...base,"data","security","operational"]}}export function defaultPresetForLevel(level){switch(level){case"L1":return"essential";case"L2":return"standard";case"L3":case"L4":return"full"}}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { INVARIANT_CATALOG } from './catalog.js';
|
|
2
|
+
export { getFilteredInvariants, getInvariantsByTier, presetToTiers, defaultPresetForLevel, } from './filter.js';
|
|
3
|
+
export type { Invariant, InvariantTier, InvariantPreset } from './types.js';
|
|
4
|
+
export type { Language, GovernanceLevel } from '../wizard/types.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const TIER_LABELS={architectural:"Tier 1: Architectural Integrity",data:"Tier 2: Data Integrity",security:"Tier 3: Security & Compliance",operational:"Tier 4: Operational Excellence",governance:"Tier 5: Governance"};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { Invariant, InvariantTier, InvariantPreset } from '../wizard/types.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
const LANGUAGE_TOKENS=new Set(["java"]),TOKEN_ROUTER={hexagonal:c=>c.architectureStyle==="hexagonal",spring:c=>typeof c.framework=="string"&&c.framework.toLowerCase().includes("spring"),java:c=>c.language==="multi"?!0:c.language==="unknown"?!1:c.language==="java"||c.language==="kotlin",frontend:c=>c.archetype==="frontend-spa",fullstack:c=>c.archetype==="backend-web-db",backend:c=>c.archetype==="backend-web-db"||c.archetype==="data-pipeline",api:c=>c.hasPublicApi,cli:c=>c.archetype==="cli",embedded:c=>c.archetype==="embedded"};function matchToken(token,config){const handler=TOKEN_ROUTER[token];return handler?handler(config):(process.stderr.write(`[applicability] unknown token '${token}' \u2014 treating as non-match
|
|
3
|
+
`),!1)}export function evaluateApplicability(dim,config){const{applies,excludes}=dim.archetypeGating;for(const token of excludes)if(matchToken(token,config))return{applicability:"na",reason:`excluded by token '${token}'`};if(dim.requiresDbEngine&&dim.requiresDbEngine.length>0){const engine=config.databaseEngine;if(!config.hasDatabase||!engine||!dim.requiresDbEngine.includes(engine))return{applicability:"na",reason:"database engine not in required set"}}if(applies.length>0&&!applies.some(t=>matchToken(t,config))){const hasLanguageToken=applies.some(t=>LANGUAGE_TOKENS.has(t));return{applicability:"na",reason:config.language==="unknown"&&hasLanguageToken?"language is 'unknown' \u2014 fail-closed for language-gated dimensions":`no applies token matched: [${applies.join(", ")}]`}}return dim.conditionalFlag==="spring-boot"&&!(typeof config.framework=="string"&&config.framework.toLowerCase().includes("spring"))?{applicability:"na",reason:"spring-boot framework not configured"}:{applicability:"applicable"}}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
const STATUS_BADGE={present:"PRESENT",partial:"PARTIAL",missing:"MISSING"};export function renderAuditMarkdown(measurements,wavePlan,applicabilityReasons){const lines=[];lines.push("# SELF-KIT Audit Report"),lines.push(""),lines.push(`Brownfield class: **${wavePlan.brownfieldClass}**`),lines.push("");for(const wave of wavePlan.waves){if(lines.push(`## ${wave.label} \u2014 ${wave.goal}`),lines.push(""),wave.dimensions.length===0){lines.push("_No dimensions in this wave._"),lines.push("");continue}lines.push("| Dim | Status | Evidence |"),lines.push("|-----|--------|----------|");for(const entry of wave.dimensions){const m=measurements[entry.dimId],status=m?STATUS_BADGE[m.status]??m.status:"MISSING",evidence=m?m.evidence.join(", "):"",reason=applicabilityReasons[entry.dimId],naNote=reason?` _(NA: ${reason})_`:"";lines.push(`| ${entry.dimId} | ${status}${naNote} | ${evidence} |`)}lines.push("")}const naEntries=Object.entries(applicabilityReasons).filter(([id])=>!wavePlan.waves.some(w=>w.dimensions.some(d=>d.dimId===id)));if(naEntries.length>0){lines.push("## N/A Dimensions"),lines.push(""),lines.push("| Dim | Reason |"),lines.push("|-----|--------|");for(const[id,reason]of naEntries.sort(([a],[b])=>a.localeCompare(b)))lines.push(`| ${id} | ${reason} |`);lines.push("")}return lines.push(`Summary: ${wavePlan.summary.totalDims} dims total`),lines.push(""),lines.join(`
|
|
3
|
+
`)}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{readdirSync,lstatSync,existsSync,readFileSync}from"node:fs";import{join,extname}from"node:path";const SOURCE_EXTENSIONS={java:new Set([".java"]),typescript:new Set([".ts",".tsx"]),python:new Set([".py"]),go:new Set([".go"]),rust:new Set([".rs"]),multi:new Set([".java",".ts",".tsx",".py",".go",".rs"])},FALLBACK_EXTS=new Set([".java",".ts",".tsx",".py",".go",".rs"]);function getSourceExts(language){const found=SOURCE_EXTENSIONS[language];return found!==void 0?found:FALLBACK_EXTS}const SKIP_DIRS=new Set(["node_modules",".git","target","build","dist",".gradle","__pycache__",".mypy_cache",".ruff_cache","vendor",".next","out","coverage",".nyc_output","htmlcov"]);function countSourceFiles(dir,exts,depth=0){if(depth>12)return 0;let count=0,entries;try{entries=readdirSync(dir)}catch{return 0}for(const entry of entries){if(SKIP_DIRS.has(entry))continue;const full=join(dir,entry);let st;try{st=lstatSync(full)}catch{continue}st.isDirectory()?(!entry.startsWith(".")||entry===".github")&&(count+=countSourceFiles(full,exts,depth+1)):exts.has(extname(entry))&&count++}return count}function readJacocoCoverage(repoPath){const candidatePaths=[join(repoPath,"target","site","jacoco","jacoco.csv"),join(repoPath,"build","reports","jacoco","test","jacoco.csv")];for(const csvPath of candidatePaths)if(existsSync(csvPath))try{const lines=readFileSync(csvPath,"utf8").trim().split(`
|
|
3
|
+
`);let missed=0,covered=0;for(const line of lines.slice(1)){const cols=line.split(","),lineMissed=Number(cols[7]),lineCovered=Number(cols[8]);isNaN(lineMissed)||(missed+=lineMissed),isNaN(lineCovered)||(covered+=lineCovered)}const total=missed+covered;return total===0?null:covered/total}catch{continue}return null}function readVitestCoverage(repoPath){const candidatePaths=[join(repoPath,"coverage","coverage-summary.json"),join(repoPath,".coverage","coverage-summary.json")];for(const summaryPath of candidatePaths)if(existsSync(summaryPath))try{const pct=JSON.parse(readFileSync(summaryPath,"utf8")).total?.lines?.pct;if(typeof pct=="number")return pct/100}catch{continue}return null}function readCoberturaLineRate(candidatePaths){for(const xmlPath of candidatePaths)if(existsSync(xmlPath))try{const xml=readFileSync(xmlPath,"utf8"),match=/<coverage\b[^>]*\bline-rate="([0-9.]+)"/.exec(xml);if(!match)continue;const rate=Number(match[1]);if(!isNaN(rate)&&rate>=0&&rate<=1)return rate}catch{continue}return null}function readPythonCoverage(repoPath){const xmlRate=readCoberturaLineRate([join(repoPath,"coverage.xml")]);if(xmlRate!==null)return xmlRate;const jsonPath=join(repoPath,"coverage.json");if(existsSync(jsonPath))try{const pct=JSON.parse(readFileSync(jsonPath,"utf8")).totals?.percent_covered;if(typeof pct=="number")return pct/100}catch{}return null}function readGoCoverage(repoPath){const candidatePaths=[join(repoPath,"coverage.out"),join(repoPath,"cover.out")];for(const profilePath of candidatePaths)if(existsSync(profilePath))try{const lines=readFileSync(profilePath,"utf8").trim().split(`
|
|
4
|
+
`);let total=0,covered=0;for(const line of lines){if(line.startsWith("mode:")||line.trim()==="")continue;const parts=line.trim().split(/\s+/);if(parts.length<3)continue;const numStmts=Number(parts[parts.length-2]),count=Number(parts[parts.length-1]);isNaN(numStmts)||isNaN(count)||(total+=numStmts,count>0&&(covered+=numStmts))}return total===0?null:covered/total}catch{continue}return null}function readRustCoverage(repoPath){return readCoberturaLineRate([join(repoPath,"cobertura.xml"),join(repoPath,"target","llvm-cov","cobertura.xml"),join(repoPath,"target","nextest","cobertura.xml")])}function readCoverage(repoPath,language){const byLanguage={java:readJacocoCoverage,typescript:readVitestCoverage,python:readPythonCoverage,go:readGoCoverage,rust:readRustCoverage};if(language==="multi"){const chain=[readJacocoCoverage,readVitestCoverage,readPythonCoverage,readGoCoverage,readRustCoverage];for(const reader2 of chain){const ratio=reader2(repoPath);if(ratio!==null)return ratio}return null}const reader=byLanguage[language];return reader?reader(repoPath):null}export function detectBrownfieldClass(repoPath,language){const exts=getSourceExts(language),sourceFileCount=countSourceFiles(repoPath,exts),coverageRatio=readCoverage(repoPath,language),coverageUsed=coverageRatio!==null;let brownfieldClass;return sourceFileCount<50?brownfieldClass="gold":sourceFileCount<500?brownfieldClass=coverageRatio===null||coverageRatio>.3?"light":"medium":sourceFileCount<2e3?brownfieldClass=coverageRatio===null||coverageRatio>=.05?"medium":"heavy":brownfieldClass="heavy",{brownfieldClass,sourceFileCount,coverageRatio,coverageUsed}}
|