@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,7 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{existsSync,statSync,lstatSync}from"node:fs";import{resolve}from"node:path";import{safeResolve,readText,readScanText,expandGlob,walkRepo,hasNestedUnboundedQuantifier,MAX_SCAN_BYTES}from"./shared.js";function cmpCodeUnit(a,b){return a<b?-1:a>b?1:0}const INVALID_PATH="invalid path";function lineOf(text,needle){const idx=text.indexOf(needle);if(idx<0)return null;let line=1;for(let i=0;i<idx;i++)text[i]===`
|
|
3
|
+
`&&line++;return line}function lineAtIndex(text,idx){let line=1;for(let i=0;i<idx&&i<text.length;i++)text[i]===`
|
|
4
|
+
`&&line++;return line}function verdictPoints(verdict){return verdict==="Y"?1:verdict==="P"?.5:0}function evalFileExists(abs,rel){if(!existsSync(abs))return{verdict:"N",evidence:{file:rel,detail:"missing"}};try{if(statSync(abs).isDirectory())return{verdict:"N",evidence:{file:rel,detail:"is a directory"}}}catch{return{verdict:"N",evidence:{file:rel,detail:"unreadable"}}}const text=readText(abs);return text===null?{verdict:"N",evidence:{file:rel,detail:"unreadable"}}:text.trim()===""?{verdict:"N",evidence:{file:rel,detail:"present but empty"}}:{verdict:"Y",evidence:{file:rel}}}function evalFileContains(abs,rel,pattern){if(pattern==="")return{verdict:"N",evidence:{file:rel,detail:"empty or missing pattern"}};const text=readText(abs);if(text===null)return{verdict:"N",evidence:{file:rel,detail:"missing"}};const line=lineOf(text,pattern);return line!==null?{verdict:"Y",evidence:{file:rel,line}}:{verdict:"N",evidence:{file:rel,detail:`pattern not found: ${pattern}`}}}function countOccurrences(text,pattern){if(pattern.length===0)return 0;let count=0,from=0;for(;;){const i=text.indexOf(pattern,from);if(i<0)break;count++,from=i+pattern.length}return count}function evalCountMatches(abs,rel,pattern,want){const text=readText(abs);if(text===null)return{verdict:"N",evidence:{file:rel,detail:"missing"}};const count=countOccurrences(text,pattern);return count>=want?{verdict:"Y",evidence:{file:rel,detail:`count=${count}`}}:count>0?{verdict:"P",evidence:{file:rel,detail:`count=${count}/${want}`}}:{verdict:"N",evidence:{file:rel,detail:`count=0/${want}`}}}function evalValue(abs,rel,expected){if(expected==="")return{verdict:"N",evidence:{file:rel,detail:"empty or missing pattern"}};const text=readText(abs);if(text===null)return{verdict:"N",evidence:{file:rel,detail:"missing"}};const line=lineOf(text,expected);return line!==null?{verdict:"Y",evidence:{file:rel,line}}:{verdict:"N",evidence:{file:rel,detail:`value not present: ${expected}`}}}function extractJson(text,select){let node;try{node=JSON.parse(text)}catch{return null}const keys=select.split(".").filter(k=>k!=="");for(const[i,key]of keys.entries()){if(key==="length"&&i===keys.length-1&&node==null)return 0;if(node===null||typeof node!="object")return null;node=node[key]}const n=typeof node=="number"?node:Number(node);return Number.isFinite(n)?n:null}function isXmlElementBoundary(c){return c===void 0||c===" "||c===" "||c===`
|
|
5
|
+
`||c==="\r"||c===">"||c==="/"}function scanElementOpens(text,tag,nth){const needle=`<${tag}`;let count=0,from=0;for(;;){const i=text.indexOf(needle,from);if(i<0)break;if(isXmlElementBoundary(text[i+needle.length])){if(nth>=0&&count===nth)return i;count++}from=i+needle.length}return nth>=0?-1:count}function extractXmlAttr(text,spec){const at=spec.indexOf("@");if(at<0)return null;const tag=spec.slice(0,at),attr=spec.slice(at+1);if(tag===""||attr==="")return null;const open=scanElementOpens(text,tag,0);if(open<0)return null;const close=text.indexOf(">",open),segment=close<0?text.slice(open):text.slice(open,close);if(hasNestedUnboundedQuantifier(attr))return null;let m;try{m=new RegExp(`${attr}\\s*=\\s*"([^"]*)"`).exec(segment)}catch{return null}if(m===null)return null;const n=Number(m[1]);return Number.isFinite(n)?n:null}function parseXmlAttrs(segment){const attrs={},re=/([\w:.-]+)\s*=\s*"([^"]*)"/g;let m;for(;(m=re.exec(segment))!==null;)attrs[m[1]]=m[2];return attrs}function extractCoveragePercent(text,counterType){const needle="<counter";let from=0,covered=0,missed=0,found=!1;for(;;){const i=text.indexOf(needle,from);if(i<0)break;if(from=i+needle.length,!isXmlElementBoundary(text[i+needle.length]))continue;const close=text.indexOf(">",i),segment=close<0?text.slice(i):text.slice(i,close),attrs=parseXmlAttrs(segment);if(attrs.type!==counterType)continue;const c=Number(attrs.covered),m=Number(attrs.missed);!Number.isFinite(c)||!Number.isFinite(m)||(covered=c,missed=m,found=!0)}if(!found)return null;const total=covered+missed;return total<=0?null:covered*100/total}function extractXml(text,select){if(select.startsWith("count:")){const tag=select.slice(6);return tag===""?null:scanElementOpens(text,tag,-1)}if(select.startsWith("attr:"))return extractXmlAttr(text,select.slice(5));if(select.startsWith("coverage:")){const counterType=select.slice(9);return counterType===""?null:extractCoveragePercent(text,counterType)}return null}function extractRegex(text,select){if(hasNestedUnboundedQuantifier(select))return null;let re;try{re=new RegExp(select)}catch{return null}const m=re.exec(text);if(m===null||m[1]===void 0)return null;const n=Number(m[1]);return Number.isFinite(n)?n:null}function extractGoCoverProfile(text){let total=0,covered=0,sawBlock=!1;for(const raw of text.split(`
|
|
6
|
+
`)){const m=/:\d+\.\d+,\d+\.\d+\s+(\d+)\s+(\d+)\s*$/.exec(raw);if(m===null)continue;const numStmt=Number(m[1]),hits=Number(m[2]);!Number.isFinite(numStmt)||!Number.isFinite(hits)||(total+=numStmt,hits>0&&(covered+=numStmt),sawBlock=!0)}return!sawBlock||total<=0?null:covered*100/total}function extractMetric(text,format,select){return format==="json"?extractJson(text,select):format==="xml"?extractXml(text,select):format==="regex"?extractRegex(text,select):format==="go-coverprofile"?extractGoCoverProfile(text):null}function compareValue(actual,op,bar){return op==="gte"?actual>=bar:op==="lte"?actual<=bar:op==="eq"?actual===bar:!1}function resolveBar(check,options){const ref=check.threshold_ref;if(ref!==void 0&&ref!==""){const row=(options.thresholds??{})[ref];if(row===void 0)return null;const cls=options.brownfieldClass??"gold",bar=Object.prototype.hasOwnProperty.call(row,cls)?row[cls]:void 0;return typeof bar=="number"&&Number.isFinite(bar)?bar:null}const lit=check.args?.expected;return typeof lit=="number"&&Number.isFinite(lit)?lit:null}function evalValueReport(abs,rel,check,options){if(!existsSync(abs))return{verdict:"NA",evidence:null};const args=check.args??{},format=String(args.format??""),select=String(args.select??""),op=String(args.op??""),bar=resolveBar(check,options);if(bar===null)return{verdict:"N",evidence:{file:rel,detail:"unresolved threshold"}};const read=readScanText(abs);if(!read.ok){const detail=read.reason==="oversize"?"report too large to scan":"unreadable";return{verdict:"N",evidence:{file:rel,detail}}}const text=read.text,actual=extractMetric(text,format,select);return actual===null?{verdict:"N",evidence:{file:rel,detail:`no metric for ${format}:${select}`}}:compareValue(actual,op,bar)?{verdict:"Y",evidence:{file:rel,detail:`${actual} ${op} ${bar}`}}:{verdict:"N",evidence:{file:rel,detail:`${actual} !${op} ${bar}`}}}function latestChangelogVersion(text,pattern){if(typeof pattern!="string"||pattern===""||hasNestedUnboundedQuantifier(pattern))return null;let re;try{re=new RegExp(pattern,"m")}catch{return null}const m=re.exec(text);return m!==null&&m[1]?m[1]:null}function extractJsonString(text,select){let node;try{node=JSON.parse(text)}catch{return null}for(const key of select.split("."))if(key!==""){if(node===null||typeof node!="object")return null;node=node[key]}return typeof node=="string"&&node!==""?node:null}function invalidVersionResult(version,vFile,vSelect){return version===null?{verdict:"P",evidence:{file:vFile,detail:`no ${vSelect} in version file`}}:version===""?{verdict:"N",evidence:{file:vFile,detail:"empty version file"}}:null}function evalVersionConsistency(args,root){const vFile=typeof args.version_file=="string"?args.version_file:"",cFile=typeof args.changelog_file=="string"?args.changelog_file:"",vSelect=typeof args.version_select=="string"?args.version_select:"",vAbs=safeResolve(root,vFile),cAbs=safeResolve(root,cFile);if(vAbs===null||cAbs===null)return{verdict:"N",evidence:{file:vFile||cFile,detail:INVALID_PATH}};const vText=readText(vAbs);if(vText===null)return{verdict:"N",evidence:{file:vFile,detail:"missing version file"}};const cRead=readScanText(cAbs);if(!cRead.ok){const detail=cRead.reason==="oversize"?"changelog too large to scan":"missing changelog";return{verdict:"N",evidence:{file:cFile,detail}}}const cText=cRead.text;if(cText.trim()==="")return{verdict:"N",evidence:{file:cFile,detail:"present but empty"}};const version=vSelect!==""?extractJsonString(vText,vSelect):vText.trim(),invalidVersion=invalidVersionResult(version,vFile,vSelect);if(invalidVersion!==null)return invalidVersion;const latest=latestChangelogVersion(cText,args.changelog_pattern);return latest===null?{verdict:"P",evidence:{file:cFile,detail:"no changelog entry matches the pattern"}}:version===latest?{verdict:"Y",evidence:{file:vFile,detail:`${version} == ${latest}`}}:{verdict:"P",evidence:{file:cFile,detail:`VERSION ${version} != CHANGELOG ${latest}`}}}function resolveGlobArg(args,root,getFiles){const glob=typeof args.glob=="string"?args.glob:"",matched=glob===""?null:expandGlob(root,glob,getFiles());return matched===null?{ok:!1,result:{verdict:"N",evidence:{file:glob,detail:"invalid or empty glob"}}}:{ok:!0,glob,matched}}function hasGlobChar(s){return/[*?[\]]/.test(s)}function forbiddenExcludeViolation(excludeRaw,glob,args){for(const ex of excludeRaw)if(typeof ex!="string"||hasGlobChar(ex))return{verdict:"N",evidence:{file:glob,detail:`exclude_paths must be literal: ${String(ex)}`}};return excludeRaw.length>0&&(typeof args.rationale=="string"?args.rationale.trim():"")===""?{verdict:"N",evidence:{file:glob,detail:"exclude_paths requires a rationale"}}:null}function scanForbiddenFiles(remaining,re,root,glob){for(const rel of remaining){const abs=safeResolve(root,rel),read=abs===null?null:readScanText(abs);if(read===null||!read.ok){const detail=read!==null&&read.reason==="oversize"?`too large to scan (> ${MAX_SCAN_BYTES} bytes) \u2014 cannot verify absence`:"unreadable \u2014 cannot verify absence";return{verdict:"N",evidence:{file:rel,detail}}}const text=read.text,m=re.exec(text);if(m!==null)return{verdict:"N",evidence:{file:rel,line:lineAtIndex(text,m.index),detail:"forbidden pattern present"}}}return{verdict:"Y",evidence:{file:glob,detail:`absent across ${remaining.length} file(s)`}}}function evalForbiddenPattern(args,root,getFiles){const pattern=typeof args.pattern=="string"?args.pattern:"";if(pattern==="")return{verdict:"N",evidence:{file:"",detail:"empty or non-string pattern"}};let re;try{re=new RegExp(pattern)}catch{return{verdict:"N",evidence:{file:"",detail:`invalid regex: ${pattern}`}}}if(hasNestedUnboundedQuantifier(pattern))return{verdict:"N",evidence:{file:"",detail:`unsafe regex (ReDoS risk): ${pattern}`}};const g=resolveGlobArg(args,root,getFiles);if(!g.ok)return g.result;const{glob,matched}=g,excludeRaw=Array.isArray(args.exclude_paths)?args.exclude_paths:[],violation=forbiddenExcludeViolation(excludeRaw,glob,args);if(violation!==null)return violation;if(matched.length===0)return{verdict:"NA",evidence:null};const exclude=new Set(excludeRaw),remaining=matched.filter(f=>!exclude.has(f));return remaining.length===0?{verdict:"N",evidence:{file:glob,detail:"all matched files excluded (refusing fake-green)"}}:scanForbiddenFiles(remaining,re,root,glob)}function gitFileModeEnabled(root){const cfg=readText(resolve(root,".git/config"));if(cfg===null)return!0;let inCore=!1,lastValue=null;for(const raw of cfg.split(`
|
|
7
|
+
`)){const line=raw.trim();if(line.startsWith("[")){inCore=/^\[core\]/i.test(line);continue}if(!inCore)continue;const m=/^filemode\s*=\s*(\S+)/i.exec(line);m!==null&&(lastValue=(m[1]??"").toLowerCase())}return lastValue===null?!0:lastValue!=="false"}function isExecutableRegular(abs){if(abs===null)return!1;try{const st=lstatSync(abs);return!st.isSymbolicLink()&&(st.mode&73)!==0}catch{return!1}}function evalFileStat(args,root,getFiles){const bit=typeof args.bit=="string"?args.bit.toLowerCase():"executable";if(bit!=="executable")return{verdict:"N",evidence:{file:"",detail:`only the executable bit is deterministic, got: ${bit}`}};const g=resolveGlobArg(args,root,getFiles);if(!g.ok)return g.result;const{glob,matched}=g;if(matched.length===0)return{verdict:"NA",evidence:null};if(!gitFileModeEnabled(root))return{verdict:"NA",evidence:null};let withBit=0,firstMissing=null;for(const rel of matched)isExecutableRegular(safeResolve(root,rel))?withBit++:firstMissing===null&&(firstMissing=rel);return withBit===matched.length?{verdict:"Y",evidence:{file:glob,detail:`executable across ${matched.length} file(s)`}}:withBit===0?{verdict:"N",evidence:{file:firstMissing??glob,detail:"not executable"}}:{verdict:"P",evidence:{file:firstMissing??glob,detail:`executable ${withBit}/${matched.length}`}}}function evalCheck(check,run){const type=check.type;if(type==="manual")return{verdict:"NV",evidence:null};const{root}=run,args=check.args??{};return type==="version_consistency"?evalVersionConsistency(args,root):type==="forbidden_pattern"?evalForbiddenPattern(args,root,run.getFiles):type==="file_stat"?evalFileStat(args,root,run.getFiles):evalSingleFileCheck(check,type,args,root,run.options)}function evalSingleFileCheck(check,type,args,root,options){const rawPath=args.path;if(typeof rawPath!="string")return{verdict:"N",evidence:{file:String(rawPath??""),detail:INVALID_PATH}};const abs=safeResolve(root,rawPath);if(abs===null)return{verdict:"N",evidence:{file:rawPath,detail:INVALID_PATH}};const rel=rawPath;return type==="file_exists"?evalFileExists(abs,rel):type==="file_contains"?evalFileContains(abs,rel,String(args.pattern??"")):type==="count_matches"?evalCountMatches(abs,rel,String(args.pattern??""),Number(args.min??1)):type==="value"?args.format?evalValueReport(abs,rel,check,options):evalValue(abs,rel,String(args.equals??"")):{verdict:"N",evidence:{file:rel,detail:`unknown check type: ${type}`}}}function capabilityMet(cond,overlays){const name=typeof cond.name=="string"?cond.name:"";return name===""?!0:overlays.has(name)}function fileExistsMet(cond,root){const p=typeof cond.path=="string"?cond.path:"";if(p==="")return!0;const abs=safeResolve(root,p);return abs===null?!0:existsSync(abs)}function fileContainsMet(cond,root){const p=typeof cond.path=="string"?cond.path:"",pattern=typeof cond.pattern=="string"?cond.pattern:"";if(p===""||pattern==="")return!0;const abs=safeResolve(root,p);if(abs===null)return!0;const text=readText(abs);return text===null?!1:text.includes(pattern)}function countMatchesMet(cond,root){const p=typeof cond.path=="string"?cond.path:"",pattern=typeof cond.pattern=="string"?cond.pattern:"";if(p===""||pattern==="")return!0;const min=Number(cond.min??1);if(!Number.isFinite(min))return!0;const abs=safeResolve(root,p);if(abs===null)return!0;const text=readText(abs);return text===null?!1:countOccurrences(text,pattern)>=min}function preconditionApplies(cond,overlays,root){const type=typeof cond.type=="string"?cond.type:"";return type==="capability"?capabilityMet(cond,overlays):type==="file_exists"?fileExistsMet(cond,root):type==="file_contains"?fileContainsMet(cond,root):type==="count_matches"?countMatchesMet(cond,root):!0}function isApplicable(check,overlays,root){const cond=check.applies_if;return!cond||cond==="always"?!0:typeof cond=="string"?overlays.has(cond):typeof cond=="object"?preconditionApplies(cond,overlays,root):!0}function accumDim(dims,dimId,verdict,weight){let d=dims.get(dimId);d===void 0&&(d={earned:0,possible:0,y:0},dims.set(dimId,d)),verdict==="Y"&&d.y++,verdict!=="NA"&&verdict!=="NV"&&(d.earned+=verdictPoints(verdict)*weight,d.possible+=weight)}function buildDimensions(dims){const result={};for(const[id,d]of[...dims.entries()].sort((a,b)=>cmpCodeUnit(a[0],b[0])))result[id]={score:d.possible>0?Math.round(d.earned/d.possible*1e3)/10:0,y:d.y};return result}function processCheck(check,overlays,dims,run){const applicable=isApplicable(check,overlays,run.root);let verdict,evidence;if(!applicable)verdict="NA",evidence=null;else{const r=evalCheck(check,run);verdict=r.verdict,evidence=r.evidence}const rawWeight=Number(check.weight??1),weight=Number.isFinite(rawWeight)&&rawWeight>=0?rawWeight:1,risk=check.risk==="RISKY"?"RISKY":"SAFE",dimId=String(check.dimension??"D-UNCLASSIFIED");accumDim(dims,dimId,verdict,weight);let earned=0,possible=0;return verdict!=="NA"&&verdict!=="NV"&&(earned=verdictPoints(verdict)*weight,possible=weight),{checkResult:{id:String(check.id),dimension:dimId,title:String(check.title??""),type:String(check.type??""),verdict,weight,risk,anchor:check.anchor?String(check.anchor):null,evidence},yCount:verdict==="Y"?1:0,riskyCount:risk==="RISKY"?1:0,earned,possible}}export function evaluate(registry,overlays,root,options={}){try{const overlaySet=overlays instanceof Set?overlays:new Set(overlays??[]),opts=options&&typeof options=="object"?options:{},sorted=[...(Array.isArray(registry.checks)?registry.checks:[]).filter(c=>!!c&&typeof c=="object")].sort((a,b)=>cmpCodeUnit(String(a.id),String(b.id))),checks=[],dims=new Map;let yCount=0,riskyCount=0,earned=0,possible=0,walkedFiles=null;const run={root,options:opts,getFiles:()=>walkedFiles??=walkRepo(root)};for(const check of sorted){const r=processCheck(check,overlaySet,dims,run);checks.push(r.checkResult),yCount+=r.yCount,riskyCount+=r.riskyCount,earned+=r.earned,possible+=r.possible}const score=possible>0?Math.round(earned/possible*1e3)/10:0;return{registryVersion:String(registry.version??"0"),score,yCount,riskyCount,totals:{checks:checks.length,y:yCount,p:checks.filter(c=>c.verdict==="P").length,n:checks.filter(c=>c.verdict==="N").length,na:checks.filter(c=>c.verdict==="NA").length,nv:checks.filter(c=>c.verdict==="NV").length},dimensions:buildDimensions(dims),checks}}catch{return{registryVersion:"0",score:0,yCount:0,riskyCount:0,totals:{checks:0,y:0,p:0,n:0,na:0,nv:0},dimensions:{},checks:[]}}}export function checkNoRegress(current,baseline){const reasons=[];return current.score<baseline.score&&reasons.push(`score regressed: ${current.score} < baseline ${baseline.score}`),current.yCount<baseline.yCount&&reasons.push(`Y-count regressed: ${current.yCount} < baseline ${baseline.yCount}`),{ok:reasons.length===0,reasons}}export function ratchet(current,baseline){const dimensions={},ids=new Set([...Object.keys(current.dimensions),...Object.keys(baseline.dimensions)]);for(const id of[...ids].sort(cmpCodeUnit)){const c=current.dimensions[id]??{score:0,y:0},p=baseline.dimensions[id]??{score:0,y:0};dimensions[id]={score:Math.max(c.score,p.score),y:Math.max(c.y,p.y)}}return{score:Math.max(current.score,baseline.score),yCount:Math.max(current.yCount,baseline.yCount),dimensions}}export function baselineOf(current){return{score:current.score,yCount:current.yCount,dimensions:current.dimensions}}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{ensureDir,mkdtempTranslated,rmTranslated,writeFileTranslated}from"../utils/fs.js";import{join}from"node:path";import{tmpdir}from"node:os";import{probeDTestLevels,probeDLiveE2e,probeDFeRenderGate,probeDDomainApi,probeDDoneEvidence,probeGateGreen,probeCoverageThresholds,probeInvariantsEnforced,probeNoOverclaim,probeCommitHygiene,probeFindingHygiene,probeE2eQuarantine}from"./dimensions.js";export function verdictBites(verdict){return verdict==="N"||verdict==="P"}function writeJson(dir,relPath,body){const abs=join(dir,relPath);ensureDir(join(abs,"..")),writeFileTranslated(abs,JSON.stringify(body))}function writeText(dir,relPath,body){const abs=join(dir,relPath);ensureDir(join(abs,"..")),writeFileTranslated(abs,body)}const GATE_PROOFS=[{id:"D-TEST-LEVELS",violation:"test-pyramid.json declares a required level with no matching test file",seed:dir=>{writeJson(dir,"test-pyramid.json",{levels:[{level:"unit",status:"required",globs:["nonexistent/**/*.spec.ts"]}]})},run:dir=>probeDTestLevels(dir)},{id:"D-LIVE-E2E",violation:"backend archetype with api-e2e.json declaring zero suites",seed:dir=>{writeJson(dir,"api-e2e.json",{suiteCount:0})},run:dir=>probeDLiveE2e(dir,"backend")},{id:"D-FE-RENDER-GATE",violation:"frontend archetype with no playwright/vitest-browser/chromatic config",seed:()=>{},run:dir=>probeDFeRenderGate(dir,"frontend")},{id:"D-DOMAIN-API",violation:"domain-api-surface.json present with an empty checks array",seed:dir=>{writeJson(dir,"domain-api-surface.json",{checks:[]})},run:dir=>probeDDomainApi(dir)},{id:"D-DONE-EVIDENCE",violation:"done-evidence recorded with reality_contact.passed=false",seed:dir=>{writeJson(dir,".claude/.last-done-evidence.json",{reality_contact:{passed:!1}})},run:dir=>probeDDoneEvidence(dir)},{id:"D-GATE-GREEN",violation:"local gate result recorded with pass=false",seed:dir=>{writeJson(dir,".arbiter/gate/local-result.json",{pass:!1})},run:dir=>probeGateGreen(dir)},{id:"D-COVERAGE-THRESHOLDS",violation:"coverage summary with lines below the 80% threshold",seed:dir=>{writeJson(dir,"coverage/coverage-summary.json",{total:{lines:{pct:50},branches:{pct:90},functions:{pct:90},statements:{pct:90}}})},run:dir=>probeCoverageThresholds(dir)},{id:"D-NO-OVERCLAIM",violation:"done-evidence recorded with no_overclaim=false",seed:dir=>{writeJson(dir,".claude/.last-done-evidence.json",{no_overclaim:!1})},run:dir=>probeNoOverclaim(dir)},{id:"D-COMMIT-HYGIENE",violation:"hooks dir and commitlint config both present but empty (substance, not presence)",seed:dir=>{ensureDir(join(dir,".githooks")),writeText(dir,"commitlint.config.js","")},run:dir=>probeCommitHygiene(dir)},{id:"DISC-finding-hygiene",violation:"open findings count rose since the recorded baseline (filed without draining)",seed:dir=>{writeText(dir,".arbiter/findings/shard-0.jsonl",`${JSON.stringify({fingerprint:"fp-1",ts:new Date().toISOString()})}
|
|
3
|
+
${JSON.stringify({fingerprint:"fp-2",ts:new Date().toISOString()})}
|
|
4
|
+
`),writeJson(dir,".arbiter/finding-hygiene-baseline.json",{openFindingsCount:1})},run:dir=>probeFindingHygiene(dir)},{id:"DISC-e2e-quarantine",violation:"quarantine registry with one entry past its TTL (expired)",seed:dir=>{writeJson(dir,".arbiter/e2e/quarantine.json",{entries:[{id:"flaky-checkout-redirect",fingerprint:"fp_0123456789abcdef",reason:"intermittent redirect race under load",owner:"team-payments",added:"2000-01-01",expires:"2000-01-02",issue:"#9999"}]})},run:dir=>probeE2eQuarantine(dir)},{id:"D-INVARIANTS-ENFORCED",violation:"invariants prescribed (arbiter.json invariantTiers) but no catalog present",seed:dir=>{writeJson(dir,"arbiter.json",{invariantTiers:["security"]})},run:dir=>probeInvariantsEnforced(dir)}];function runOneProof(proof){const dir=mkdtempTranslated(join(tmpdir(),"arbiter-gate-proof-"));try{proof.seed(dir);const entry=proof.run(dir);return{id:proof.id,title:entry.title,violation:proof.violation,verdict:entry.verdict,bites:verdictBites(entry.verdict),detail:entry.evidence.detail??""}}catch(err){return{id:proof.id,title:proof.id,violation:proof.violation,verdict:"NV",bites:!1,detail:`proof crashed: ${err instanceof Error?err.message:String(err)}`}}finally{rmTranslated(dir,{recursive:!0,force:!0})}}export function runGateProofs(){return GATE_PROOFS.map(runOneProof)}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{existsSync,readFileSync,readdirSync,lstatSync,statSync}from"node:fs";import{resolve,relative,isAbsolute,join}from"node:path";export function safeResolve(root,p){if(p.includes("\0"))return null;const abs=resolve(root,p),rel=relative(root,abs);return rel.startsWith("..")||isAbsolute(rel)?null:abs}export function readText(abs){try{return readFileSync(abs,"utf-8")}catch{return null}}export const MAX_SCAN_BYTES=2e6;export function readScanText(abs){let size;try{size=statSync(abs).size}catch{return{ok:!1,reason:"unreadable"}}if(size>MAX_SCAN_BYTES)return{ok:!1,reason:"oversize"};const text=readText(abs);return text===null?{ok:!1,reason:"unreadable"}:{ok:!0,text}}function skipCharClass(src,i){let j=i+1;for(src[j]==="^"&&j++,src[j]==="]"&&j++;j<src.length&&src[j]!=="]";)src[j]==="\\"&&j++,j++;return j+1}function braceQuantifier(src,i){const close=src.indexOf("}",i);if(close<0)return{isQuant:!1};const body=src.slice(i+1,close);return/^\d+(,\d*)?$/.test(body)?{isQuant:!0,unbounded:/^\d+,$/.test(body),next:close+1}:{isQuant:!1}}export function hasNestedUnboundedQuantifier(source){const groupSawUnbounded=[!1];let prevGroupSawUnbounded=!1,i=0;const n=source.length;for(;i<n;){const ch=source[i];if(ch==="\\"){i+=2,prevGroupSawUnbounded=!1;continue}if(ch==="["){i=skipCharClass(source,i),prevGroupSawUnbounded=!1;continue}if(ch==="("){groupSawUnbounded.push(!1),i++,prevGroupSawUnbounded=!1;continue}if(ch===")"){const closed=groupSawUnbounded.length>1?groupSawUnbounded.pop()??!1:!1;closed&&(groupSawUnbounded[groupSawUnbounded.length-1]=!0),prevGroupSawUnbounded=closed,i++;continue}let unbounded=!1,next=i+1;if(ch==="*"||ch==="+")unbounded=!0;else if(ch==="{"){const q=braceQuantifier(source,i);q.isQuant&&(unbounded=q.unbounded,next=q.next)}if(unbounded){if(prevGroupSawUnbounded)return!0;groupSawUnbounded[groupSawUnbounded.length-1]=!0,prevGroupSawUnbounded=!1,i=next;continue}prevGroupSawUnbounded=!1,i=next}return!1}export function readJson(abs){const text=readText(abs);if(text===null)return null;try{return JSON.parse(text)}catch{return null}}export function fileExists(abs){try{return existsSync(abs)}catch{return!1}}export const SKIP_DIRS=new Set(["node_modules",".git","dist","build","coverage",".coverage",".venv","venv","__pycache__"]);function globToRegExp(pattern){let reStr="^",i=0;for(;i<pattern.length;){const ch=pattern[i]??"";ch==="*"&&pattern[i+1]==="*"?pattern[i+2]==="/"?(reStr+="(?:[^/]*/)*",i+=3):(reStr+="[\\s\\S]*",i+=2):ch==="*"?(reStr+="[^/]*",i++):"\\.+?^${}()|[]".includes(ch)?(reStr+="\\"+ch,i++):(reStr+=ch,i++)}return reStr+="$",new RegExp(reStr)}export function globMatch(pattern,filepath){return globToRegExp(pattern).test(filepath)}function validateGlob(pattern){return isAbsolute(pattern)?!1:!pattern.split("/").includes("..")}export function walkRepo(root){const base=root.replace(/[/\\]+$/,"")||root,files=[],visited=new Set,visit=dir=>{let dirStat;try{dirStat=statSync(dir)}catch{return}const key=`${dirStat.dev}:${dirStat.ino}`;if(visited.has(key))return;visited.add(key);let entries;try{entries=readdirSync(dir)}catch{return}for(const entry of entries){if(SKIP_DIRS.has(entry))continue;const full=join(dir,entry);let stat;try{stat=lstatSync(full)}catch{continue}if(stat.isSymbolicLink()){files.push(full.slice(base.length+1).replace(/\\/g,"/"));continue}stat.isDirectory()?visit(full):files.push(full.slice(base.length+1).replace(/\\/g,"/"))}};return visit(base),files}export function expandGlob(root,pattern,files){if(typeof pattern!="string"||pattern===""||!validateGlob(pattern))return null;const re=globToRegExp(pattern);return(files??walkRepo(root)).filter(f=>re.test(f)).sort()}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{detectArchetypeHint}from"./framework.js";import{defaultContractType}from"../wizard/archetype-defaults.js";import{detectLanes}from"./lanes.js";export const ARCHETYPE_DB_SET=new Set(["backend-web-db","data-pipeline"]);function pickHasPublicApi(stored,archetype){return stored?.hasPublicApi??archetype==="backend-web-db"}function deriveDatabase(stored,archetype){let engine;return stored?.databaseEngine!=null?engine=stored.databaseEngine:stored?.hasDatabase!=null?engine=stored.hasDatabase?"postgresql":"none":engine=ARCHETYPE_DB_SET.has(archetype)?"postgresql":"none",{databaseEngine:engine,hasDatabase:engine!=="none"}}export function deriveAxisDefaults(stored,archetype){const hasPublicApi=pickHasPublicApi(stored,archetype),{databaseEngine,hasDatabase}=deriveDatabase(stored,archetype);return{architectureStyle:stored?.architectureStyle??"none",isMultiTenant:stored?.isMultiTenant??!1,hasDatabase,databaseEngine,hasPublicApi,contractType:stored?.contractType??defaultContractType(archetype,hasPublicApi)}}function resolveLanes(stored,targetDir){return stored?.lanes??detectLanes(targetDir).lanes}export function resolveAxisFields(stored,targetDir,language,framework){const archetype=stored?.archetype??detectArchetypeHint(targetDir,language,framework)??"library",defaults=deriveAxisDefaults(stored,archetype),lanes=resolveLanes(stored,targetDir);return{archetype,architectureStyle:defaults.architectureStyle,isMultiTenant:defaults.isMultiTenant,hasDatabase:defaults.hasDatabase,databaseEngine:defaults.databaseEngine,hasPublicApi:defaults.hasPublicApi,contractType:defaults.contractType,lanes}}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{existsSync}from"node:fs";import{join}from"node:path";import{readPackageJsonSafe}from"../utils/safe-read.js";import{jvmRoot}from"./language.js";import{detectPackageManager}from"./package-manager.js";export function detectBuildCommands(dir,language){switch(language){case"typescript":return detectTypescriptCommands(dir);case"rust":return detectRustCommands(dir);case"java":case"kotlin":case"multi":return detectJavaCommands(jvmRoot(dir)??dir);case"go":return{buildTool:"go",buildCommand:"go build ./...",testCommand:"go test ./...",lintCommand:"golangci-lint run",formatCommand:"gofmt -l ."};case"python":return{buildTool:"pip",buildCommand:"pip install -e .",testCommand:"pytest",lintCommand:"ruff check .",formatCommand:"ruff format --check ."};default:return{buildTool:"unknown",buildCommand:'echo "configure build command"',testCommand:'echo "configure test command"',lintCommand:'echo "configure lint command"',formatCommand:'echo "configure format command"'}}}function detectTypescriptCommands(dir){const pkg=readPackageJson(dir),packageManager=detectPackageManager(dir).name,hasEslint=hasScript(pkg,"lint"),hasPrettier=hasPrettierDependency(pkg);return{buildTool:"npm",packageManager,buildCommand:getScript(pkg,"build",packageManager)??runScript(packageManager,"build"),testCommand:getScript(pkg,"test",packageManager)??runTest(packageManager),lintCommand:hasEslint?getScript(pkg,"lint",packageManager)??runScript(packageManager,"lint"):'echo "no lint configured"',formatCommand:hasPrettier?prettierCommand(packageManager):'echo "no formatter configured"'}}function detectRustCommands(dir){const manifest=existsSync(join(dir,"src-tauri","Cargo.toml"))?"--manifest-path src-tauri/Cargo.toml":"";return{buildTool:"cargo",buildCommand:`cargo build ${manifest}`.trim(),testCommand:`cargo test ${manifest}`.trim(),lintCommand:`cargo clippy ${manifest} -- -D warnings`.trim(),formatCommand:`cargo fmt ${manifest} --check`.trim()}}function detectJavaCommands(dir){const useWrapper=existsSync(join(dir,"gradlew")),hasBuildGradle=existsSync(join(dir,"build.gradle"))||existsSync(join(dir,"build.gradle.kts"));if(!useWrapper&&!hasBuildGradle&&existsSync(join(dir,"pom.xml")))return{buildTool:"maven",buildCommand:"mvn package -DskipTests",testCommand:"mvn test",lintCommand:"mvn checkstyle:check",formatCommand:'echo "no formatter configured"'};const gradle=useWrapper?"./gradlew":"gradle";return{buildTool:"gradle",buildCommand:`${gradle} build -x test`,testCommand:`${gradle} test`,lintCommand:`${gradle} checkstyleMain`,formatCommand:'echo "no formatter configured"'}}const readPackageJson=readPackageJsonSafe;function hasScript(pkg,name){const scripts=pkg.scripts;return typeof scripts=="object"&&scripts!==null&&name in scripts}function hasPrettierDependency(pkg){for(const key of["dependencies","devDependencies","peerDependencies"]){const deps=pkg[key];if(!(typeof deps!="object"||deps===null)){for(const name of Object.keys(deps))if(name==="prettier"||name.startsWith("prettier-plugin-"))return!0}}return!1}function getScript(pkg,name,packageManager){const scripts=pkg.scripts;return typeof scripts=="object"&&scripts!==null&&name in scripts&&typeof scripts[name]=="string"?runScript(packageManager,name):null}function runScript(packageManager,name){return`${packageManager} run ${name}`}function runTest(packageManager){return`${packageManager} test`}function prettierCommand(packageManager){switch(packageManager){case"pnpm":return"pnpm exec prettier --check .";case"yarn":return"yarn exec prettier --check .";case"bun":return"bunx prettier --check .";case"npm":return"npx prettier --check ."}}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{existsSync,readdirSync,readFileSync}from"node:fs";import{join}from"node:path";const MAX_TEST_SCAN_DEPTH=4,MAX_TEST_SCAN_ENTRIES=500,EXCLUDED_TEST_SCAN_DIRECTORIES=new Set([".git","build","dist","node_modules","target","vendor"]);export function detectExisting(dir){return{agentsMd:existsSync(join(dir,"AGENTS.md")),claudeDir:existsSync(join(dir,".claude")),agentsDir:existsSync(join(dir,".agents")),aiRulez:existsSync(join(dir,".ai-rulez"))||existsSync(join(dir,"ai-rulez.yml")),settingsJson:existsSync(join(dir,".claude","settings.json")),checkAllScript:existsSync(join(dir,"scripts","check-all.mjs"))||existsSync(join(dir,"scripts","check-all.sh")),geminiDir:existsSync(join(dir,".gemini")),windsurfRules:existsSync(join(dir,"windsurf-instructions.md")),aiderConf:existsSync(join(dir,".aider.conf.yml")),tests:detectTests(dir),ciWorkflows:detectCiWorkflows(dir),lintConfig:detectLintConfig(dir)}}export function isBrownfield(state){return state.tests||state.ciWorkflows||state.lintConfig}function detectTests(dir){let scannedEntries=0;const pending=[{path:dir,depth:0}];for(;pending.length>0&&scannedEntries<MAX_TEST_SCAN_ENTRIES;){const current=pending.pop();if(current===void 0)break;let entries;try{entries=readdirSync(current.path,{withFileTypes:!0})}catch(err){if(isMissingPath(err))continue;throw new Error(`Cannot scan ${current.path} for tests`,{cause:err})}for(const entry of entries){if(scannedEntries+=1,scannedEntries>MAX_TEST_SCAN_ENTRIES)return!1;if(entry.isDirectory()){if(isTestDirectory(entry.name))return!0;current.depth<MAX_TEST_SCAN_DEPTH&&!EXCLUDED_TEST_SCAN_DIRECTORIES.has(entry.name)&&pending.push({path:join(current.path,entry.name),depth:current.depth+1});continue}if(entry.isFile()&&isTestFile(entry.name))return!0}}return!1}function isTestDirectory(name){return name==="test"||name==="tests"||name==="__tests__"||name==="spec"}function isTestFile(name){return name.endsWith("_test.go")||/^test_.*\.py$/.test(name)||/_test\.py$/.test(name)||/\.test\.(?:ts|tsx|js|jsx)$/.test(name)||/\.spec\.[^.]+$/.test(name)}function detectCiWorkflows(dir){const workflowsDir=join(dir,".github","workflows");if(!existsSync(workflowsDir))return!1;try{return readdirSync(workflowsDir,{withFileTypes:!0}).some(entry=>entry.isFile()&&/\.ya?ml$/.test(entry.name))}catch(err){throw new Error(`Cannot inspect CI workflows in ${workflowsDir}`,{cause:err})}}function detectLintConfig(dir){let entries;try{entries=readdirSync(dir,{withFileTypes:!0})}catch(err){if(isMissingPath(err))return!1;throw new Error(`Cannot inspect lint configuration in ${dir}`,{cause:err})}const names=new Set(entries.map(entry=>entry.name));if([...names].some(name=>name.startsWith(".eslintrc")||name.startsWith("eslint.config."))||[".golangci.yml",".golangci.yaml","ruff.toml",".ruff.toml","setup.cfg",".flake8","tox.ini",".rubocop.yml","clippy.toml","checkstyle.xml"].some(name=>names.has(name)))return!0;if(!names.has("pyproject.toml"))return!1;try{return/^\s*\[tool\.(ruff|black)\]\s*$/m.test(readFileSync(join(dir,"pyproject.toml"),"utf8"))}catch(err){throw new Error(`Cannot read pyproject.toml in ${dir}`,{cause:err})}}function isMissingPath(err){return typeof err=="object"&&err!==null&&"code"in err&&err.code==="ENOENT"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{existsSync}from"node:fs";import{homedir}from"node:os";import{join}from"node:path";import{CliError,runCli}from"../utils/run-cli.js";const PROVIDER_SPECS={codex:{command:"codex",versionArgs:["--version"],vendor:"openai",authSignal:"~/.codex/auth.json presence (inference; openai/codex#10233)",installHint:"Install the Codex CLI: https://github.com/openai/codex"}},detectionCache=new Map;function parseVersion(stdout){return stdout.match(/\b\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?\b/)?.[0]??null}function hasCodexAuth(options){return existsSync(join(options.homeDir??homedir(),".codex","auth.json"))}function probeTimeoutMs(options){const raw=(options.env??process.env).ARBITER_EXTERNAL_PROBE_TIMEOUT_MS,parsed=Number(raw);return Number.isFinite(parsed)&&parsed>0?parsed:5e3}function detectExternalModel(provider,options={}){const cached=detectionCache.get(provider);if(cached!==void 0)return cached;const spec=PROVIDER_SPECS[provider];try{const result=runCli(spec.command,[...spec.versionArgs],{timeoutMs:probeTimeoutMs(options),retries:0}),authenticated=hasCodexAuth(options),access={provider,vendor:spec.vendor,available:!0,authenticated,version:parseVersion(result.stdout),error:authenticated?null:"Not authenticated"};return detectionCache.set(provider,access),access}catch(error){const access={provider,vendor:spec.vendor,available:!1,authenticated:!1,version:null,error:error instanceof CliError&&error.notFound?`${spec.command} CLI not found \u2014 ${spec.installHint}`:error instanceof CliError&&error.timedOut?"codex CLI probe timed out":"codex CLI probe failed"};return detectionCache.set(provider,access),access}}function detectExternalModels(providers=Object.keys(PROVIDER_SPECS),options={}){return providers.map(provider=>detectExternalModel(provider,options))}function resetExternalModelDetection(){detectionCache.clear()}export{detectExternalModel,detectExternalModels,resetExternalModelDetection};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{existsSync}from"node:fs";import{join}from"node:path";import{readFileSafe,readPackageJsonSafe}from"../utils/safe-read.js";import{jvmRoot}from"./language.js";import{FE_FRAMEWORKS}from"./lanes.js";export function detectFramework(dir,language){return language==="multi"?detectMultiFramework(dir):language==="typescript"?detectTypescriptFramework(dir):language==="rust"?detectRustFramework(dir):language==="java"?detectJavaFramework(dir):null}function detectMultiFramework(dir){const ts=detectTypescriptFramework(dir),javaDir=jvmRoot(dir),java=javaDir!==null?detectJavaFramework(javaDir):null;return ts&&java?`${ts}+${java}`:ts??java}function detectTypescriptFramework(dir){const pkg=readPackageJson(dir),deps=getAllDeps(pkg),hasTauri=existsSync(join(dir,"src-tauri")),hasVue=deps.has("vue"),hasReact=deps.has("react"),hasExpress=deps.has("express"),hasNext=deps.has("next"),hasFastify=deps.has("fastify");return hasTauri&&hasReact?"tauri+react":hasTauri&&hasVue?"tauri+vue":hasTauri?"tauri":hasNext?"next":hasExpress&&hasReact?"express+react":hasExpress&&hasVue?"express+vue":hasExpress?"express":hasFastify?"fastify":hasVue?"vue":hasReact?"react":detectAdditionalTypescriptFramework(deps)}function detectAdditionalTypescriptFramework(deps){if(deps.has("@sveltejs/kit"))return"sveltekit";for(const[dependency,framework]of FE_FRAMEWORKS)if(deps.has(dependency))return framework;return null}function detectRustFramework(dir){return existsSync(join(dir,"src-tauri"))?"tauri":null}function detectJavaFramework(dir){const buildFile=readFileSafe(join(dir,"build.gradle"))+readFileSafe(join(dir,"build.gradle.kts"))+readFileSafe(join(dir,"pom.xml"));return buildFile.includes("spring-boot")||buildFile.includes("org.springframework.boot")?"spring-boot":buildFile.includes("quarkus")||buildFile.includes("io.quarkus")?"quarkus":null}const readPackageJson=readPackageJsonSafe,BACKEND_WEB_DB="backend-web-db",FRONTEND_SPA="frontend-spa";function getAllDeps(pkg){const deps=new Set;for(const key of["dependencies","devDependencies","peerDependencies"]){const d=pkg[key];if(typeof d=="object"&&d!==null)for(const name of Object.keys(d))deps.add(name)}return deps}const FRAMEWORK_ARCHETYPE_MAP=new Map([["java:spring-boot",BACKEND_WEB_DB],["java:quarkus",BACKEND_WEB_DB],["typescript:next",BACKEND_WEB_DB],["typescript:astro",BACKEND_WEB_DB],["typescript:nuxt",BACKEND_WEB_DB],["typescript:sveltekit",BACKEND_WEB_DB],["typescript:express",BACKEND_WEB_DB],["typescript:express+react",BACKEND_WEB_DB],["typescript:express+vue",BACKEND_WEB_DB],["typescript:fastify",BACKEND_WEB_DB],["typescript:tauri+react",FRONTEND_SPA],["typescript:tauri+vue",FRONTEND_SPA],["typescript:tauri",FRONTEND_SPA],["typescript:react",FRONTEND_SPA],["typescript:vue",FRONTEND_SPA],["typescript:angular",FRONTEND_SPA],["typescript:svelte",FRONTEND_SPA],["typescript:solid",FRONTEND_SPA],["typescript:preact",FRONTEND_SPA],["typescript:vite",FRONTEND_SPA],["rust:tauri",FRONTEND_SPA]]),LANGUAGE_FALLBACK_ARCHETYPE=new Map([["java","library"],["typescript","library"],["rust","library"],["multi",BACKEND_WEB_DB]]);export function detectArchetypeHint(_dir,language,framework){if(framework!==null){if(language==="multi"&&framework.includes("+"))return detectMultiCompositeArchetype(framework);const key=`${language}:${framework}`,mapped=FRAMEWORK_ARCHETYPE_MAP.get(key);if(mapped!==void 0)return mapped}return LANGUAGE_FALLBACK_ARCHETYPE.get(language)??null}function detectMultiCompositeArchetype(framework){const parts=framework.split("+"),last=parts[parts.length-1];if(last===void 0)return null;const tsFramework=parts.slice(0,-1).join("+"),tsArchetype=FRAMEWORK_ARCHETYPE_MAP.get(`typescript:${tsFramework}`)??null,javaArchetype=FRAMEWORK_ARCHETYPE_MAP.get(`java:${last}`)??null;return tsArchetype!==null&&tsArchetype===javaArchetype?tsArchetype:tsArchetype===BACKEND_WEB_DB||javaArchetype===BACKEND_WEB_DB?BACKEND_WEB_DB:tsArchetype??javaArchetype}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{existsSync}from"node:fs";import{join}from"node:path";import{runCli}from"../utils/run-cli.js";export function detectGitInfo(dir){if(!(runCmd("git",["rev-parse","--is-inside-work-tree"],dir)==="true"))return{isGitRepo:!1,remoteUrl:null,githubOwner:null,githubRepo:null,projectName:null};const remoteUrl=runCmd("git",["remote","get-url","origin"],dir),{owner,repo}=parseGithubUrl(remoteUrl??"");return{isGitRepo:!0,remoteUrl,githubOwner:owner,githubRepo:repo,projectName:repo}}const SSH_RE=/git@github\.com:([^/]+)\/(.+?)(?:\.git)?$/,HTTPS_RE=/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/;function parseGithubUrl(url){const sshMatch=SSH_RE.exec(url);if(sshMatch)return{owner:sshMatch[1]??null,repo:sshMatch[2]??null};const httpsMatch=HTTPS_RE.exec(url);return httpsMatch?{owner:httpsMatch[1]??null,repo:httpsMatch[2]??null}:{owner:null,repo:null}}function runCmd(cmd,args,cwd){try{return runCli(cmd,args,{cwd}).stdout.trim()}catch{return null}}export function detectAdverseGitState(dir){const gitDir=runCmd("git",["rev-parse","--git-dir"],dir);if(!gitDir)return null;const absGitDir=gitDir.startsWith("/")?gitDir:join(dir,gitDir);return existsSync(join(absGitDir,"rebase-merge"))||existsSync(join(absGitDir,"rebase-apply"))?{type:"rebase",message:"A git rebase is in progress. arbiter cannot safely write files during a rebase.",suggestedFix:`Complete or abort the rebase first:
|
|
3
|
+
git rebase --continue
|
|
4
|
+
git rebase --abort`}:existsSync(join(absGitDir,"MERGE_HEAD"))?{type:"merge",message:"A git merge is in progress. arbiter cannot safely write files during a merge.",suggestedFix:`Complete or abort the merge first:
|
|
5
|
+
git merge --continue
|
|
6
|
+
git merge --abort`}:existsSync(join(absGitDir,"CHERRY_PICK_HEAD"))?{type:"cherry-pick",message:"A git cherry-pick is in progress. arbiter cannot safely write files during a cherry-pick.",suggestedFix:`Complete or abort the cherry-pick first:
|
|
7
|
+
git cherry-pick --continue
|
|
8
|
+
git cherry-pick --abort`}:existsSync(join(absGitDir,"BISECT_LOG"))?{type:"bisect",message:"A git bisect is in progress. arbiter cannot safely write files during a bisect.",suggestedFix:`Finish the bisect first:
|
|
9
|
+
git bisect reset`}:runCmd("git",["symbolic-ref","--quiet","HEAD"],dir)===null?{type:"detached-head",message:`HEAD is detached at ${runCmd("git",["rev-parse","--short","HEAD"],dir)??"unknown"}. arbiter cannot safely write files in detached HEAD state.`,suggestedFix:`Checkout a branch first:
|
|
10
|
+
git checkout <branch-name>
|
|
11
|
+
git checkout -b <new-branch-name>`}:null}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{runCli}from"../utils/run-cli.js";export function detectGithubAccess(){if(runCmd("gh",["--version"])===null)return{available:!1,authenticated:!1,username:null,error:"gh CLI not found. Install from https://cli.github.com"};const authStatus=runCmd("gh",["auth","status","--json","loggedIn,activeToken,user"]);if(authStatus!==null)try{const parsed=JSON.parse(authStatus);return parsed.loggedIn===!1?{available:!0,authenticated:!1,username:null,error:"Not authenticated. Run: gh auth login"}:{available:!0,authenticated:!0,username:parsed.user?.login??null,error:null}}catch{}const textStatus=runCmd("gh",["auth","status"]),isAuthed=textStatus!==null&&(textStatus.includes("Logged in to")||textStatus.includes("Active account: true")),username=/account\s+(\S+)/.exec(textStatus??"")?.[1]??null;return{available:!0,authenticated:isAuthed,username,error:isAuthed?null:"Not authenticated. Run: gh auth login"}}function runCmd(cmd,args){try{return runCli(cmd,args).stdout.trim()}catch{return null}}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{existsSync,readdirSync,readFileSync}from"node:fs";import{join}from"node:path";import{toFsError}from"../utils/fs.js";export const FE_FRAMEWORKS=new Map([["react","react"],["vue","vue"],["next","next"],["astro","astro"],["nuxt","nuxt"],["@angular/core","angular"],["svelte","svelte"],["solid-js","solid"],["preact","preact"],["vite","vite"]]);const BE_MANIFEST_FILES=["pom.xml","build.gradle","build.gradle.kts","Cargo.toml","go.mod","pyproject.toml","requirements.txt"],BE_NODE_FRAMEWORKS=new Set(["express","fastify","hono","koa","@nestjs/core"]);function hasFrontendLane(dir){const pkgPath=join(dir,"frontend","package.json");if(!existsSync(pkgPath))return!1;try{const pkg=JSON.parse(readFileSync(pkgPath,"utf8")),allDeps={...pkg.dependencies,...pkg.devDependencies};return Object.keys(allDeps).some(k=>FE_FRAMEWORKS.has(k))}catch(err){return process.stderr.write(`[arbiter] Warning: could not read ${pkgPath} for lane detection \u2014 ${toFsError(err,pkgPath).message}
|
|
3
|
+
`),!1}}function hasBackendLane(dir){if(BE_MANIFEST_FILES.some(f=>existsSync(join(dir,f))))return!0;const beDir=join(dir,"backend");if(!existsSync(beDir))return!1;if(BE_MANIFEST_FILES.some(f=>existsSync(join(beDir,f))))return!0;const pkgPath=join(beDir,"package.json");if(!existsSync(pkgPath))return!1;try{const pkg=JSON.parse(readFileSync(pkgPath,"utf8")),allDeps={...pkg.dependencies,...pkg.devDependencies};return Object.keys(allDeps).some(k=>BE_NODE_FRAMEWORKS.has(k))}catch(err){return process.stderr.write(`[arbiter] Warning: could not read ${pkgPath} for lane detection \u2014 ${toFsError(err,pkgPath).message}
|
|
4
|
+
`),!1}}function hasDocsLane(dir){const docsDir=join(dir,"docs");if(!existsSync(docsDir))return!1;const hasMd=(d,depth=0)=>{if(depth>1)return!1;try{for(const entry of readdirSync(d,{withFileTypes:!0}))if(entry.isFile()&&entry.name.endsWith(".md")||entry.isDirectory()&&hasMd(join(d,entry.name),depth+1))return!0}catch(err){return process.stderr.write(`[arbiter] Warning: could not read directory during docs-lane detection \u2014 ${toFsError(err,d).message}
|
|
5
|
+
`),!1}return!1};return hasMd(docsDir)}export function detectLanes(dir){const lanes=[];return hasFrontendLane(dir)&&lanes.push("frontend"),hasBackendLane(dir)&&lanes.push("backend"),hasDocsLane(dir)&&lanes.push("docs"),{lanes}}export function isSubtreeFrontendLane(config){return config.archetype!==void 0&&config.archetype!=="frontend-spa"&&config.lanes.includes("frontend")}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
const TS_NO_ANY={name:"check-no-any.mjs",description:"No `any` type in TypeScript source files",body:`#!/usr/bin/env node
|
|
2
|
+
// Fail if a TypeScript file was edited with an explicit 'any' type
|
|
3
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
4
|
+
import { resolveToolInputPath } from './lib.mjs';
|
|
5
|
+
const file = resolveToolInputPath();
|
|
6
|
+
if (!file.endsWith('.ts') && !file.endsWith('.tsx')) process.exit(0);
|
|
7
|
+
if (!existsSync(file)) process.exit(0);
|
|
8
|
+
const repoRoot = process.cwd();
|
|
9
|
+
if (!file.startsWith(repoRoot)) process.exit(0);
|
|
10
|
+
if (/:\\s*any\\b/.test(readFileSync(file, 'utf-8'))) {
|
|
11
|
+
process.stderr.write(\`[arbiter] INV-04: No 'any' type allowed: \${file}\\n\`);
|
|
12
|
+
process.stderr.write(\`[arbiter] Run \\\`arbiter explain INV-04\\\` for details.\\n\`);
|
|
13
|
+
process.exit(2);
|
|
14
|
+
}`},RUST_NO_UNWRAP={name:"check-no-unwrap.mjs",description:"No `.unwrap()` calls in Rust source files (use `?` or proper error handling)",body:`#!/usr/bin/env node
|
|
15
|
+
// Fail if a Rust file was edited with an .unwrap() call
|
|
16
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
17
|
+
import { resolveToolInputPath } from './lib.mjs';
|
|
18
|
+
const file = resolveToolInputPath();
|
|
19
|
+
if (!file.endsWith('.rs')) process.exit(0);
|
|
20
|
+
if (!existsSync(file)) process.exit(0);
|
|
21
|
+
const repoRoot = process.cwd();
|
|
22
|
+
if (!file.startsWith(repoRoot)) process.exit(0);
|
|
23
|
+
if (/\\.unwrap\\(\\)/.test(readFileSync(file, 'utf-8'))) {
|
|
24
|
+
process.stderr.write(\`[arbiter] INV: No .unwrap() allowed in Rust: \${file}\\n\`);
|
|
25
|
+
process.exit(2);
|
|
26
|
+
}`},COMMON_NO_ORPHAN_TODO={name:"check-no-orphan-todo.mjs",description:"Every TODO comment must reference a task ID (e.g., // TODO(#123))",body:`#!/usr/bin/env node
|
|
27
|
+
// Fail if a file has a TODO without a task reference
|
|
28
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
29
|
+
import { resolveToolInputPath } from './lib.mjs';
|
|
30
|
+
const file = resolveToolInputPath();
|
|
31
|
+
if (!file || !existsSync(file)) process.exit(0);
|
|
32
|
+
const repoRoot = process.cwd();
|
|
33
|
+
if (!file.startsWith(repoRoot)) process.exit(0);
|
|
34
|
+
let content; try { content = readFileSync(file, 'utf-8'); } catch { process.exit(0); }
|
|
35
|
+
const offending = content.split('\\n').flatMap((line, i) =>
|
|
36
|
+
/\\bTODO\\b/.test(line) && !/\\bTODO\\b.*\\(#\\d+\\)/.test(line) ? [\`\${i + 1}: \${line.trim()}\`] : []
|
|
37
|
+
);
|
|
38
|
+
if (offending.length > 0) {
|
|
39
|
+
process.stderr.write(\`[arbiter] INV: Orphan TODO found (must include task ID like TODO(#123)): \${file}\\n\`);
|
|
40
|
+
offending.slice(0, 3).forEach(l => process.stderr.write(\` \${l}\\n\`));
|
|
41
|
+
process.exit(2);
|
|
42
|
+
}`},GO_NO_UNCHECKED_ERR={name:"check-no-unchecked-err.mjs",description:"No discarded error returns in Go source files (no '_ = ' patterns that ignore errors)",body:`#!/usr/bin/env node
|
|
43
|
+
// Fail if a Go file discards an error return with _ = pattern
|
|
44
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
45
|
+
import { resolveToolInputPath } from './lib.mjs';
|
|
46
|
+
const file = resolveToolInputPath();
|
|
47
|
+
if (!file.endsWith('.go')) process.exit(0);
|
|
48
|
+
if (!existsSync(file)) process.exit(0);
|
|
49
|
+
const repoRoot = process.cwd();
|
|
50
|
+
if (!file.startsWith(repoRoot)) process.exit(0);
|
|
51
|
+
const lines = readFileSync(file, 'utf-8').split('\\n');
|
|
52
|
+
const offending = lines.flatMap((line, i) =>
|
|
53
|
+
/^\\s*_\\s*=\\s*\\S+/.test(line) && !line.trimStart().startsWith('//') ? [\`\${i + 1}: \${line.trim()}\`] : []
|
|
54
|
+
);
|
|
55
|
+
if (offending.length > 0) {
|
|
56
|
+
process.stderr.write(\`[arbiter] INV: Unchecked error (no '_ = ' patterns allowed): \${file}\\n\`);
|
|
57
|
+
offending.slice(0, 3).forEach(l => process.stderr.write(\` \${l}\\n\`));
|
|
58
|
+
process.exit(2);
|
|
59
|
+
}`},PY_NO_BARE_EXCEPT={name:"check-no-bare-except.mjs",description:"No bare except clauses in Python source files (always specify exception type)",body:`#!/usr/bin/env node
|
|
60
|
+
// Fail if a Python file uses a bare 'except:' clause
|
|
61
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
62
|
+
import { resolveToolInputPath } from './lib.mjs';
|
|
63
|
+
const file = resolveToolInputPath();
|
|
64
|
+
if (!file.endsWith('.py')) process.exit(0);
|
|
65
|
+
if (!existsSync(file)) process.exit(0);
|
|
66
|
+
const repoRoot = process.cwd();
|
|
67
|
+
if (!file.startsWith(repoRoot)) process.exit(0);
|
|
68
|
+
const lines = readFileSync(file, 'utf-8').split('\\n');
|
|
69
|
+
const offending = lines.flatMap((line, i) =>
|
|
70
|
+
/\\bexcept\\s*:/.test(line) && !line.trimStart().startsWith('#') ? [\`\${i + 1}: \${line.trim()}\`] : []
|
|
71
|
+
);
|
|
72
|
+
if (offending.length > 0) {
|
|
73
|
+
process.stderr.write(\`[arbiter] INV: Bare except clause found (specify exception type): \${file}\\n\`);
|
|
74
|
+
offending.slice(0, 3).forEach(l => process.stderr.write(\` \${l}\\n\`));
|
|
75
|
+
process.exit(2);
|
|
76
|
+
}`},JAVA_NO_RAW_TYPES={name:"check-no-raw-types.mjs",description:"No raw generic types in Java source files (always use type parameters)",body:`#!/usr/bin/env node
|
|
77
|
+
// Fail if a Java file uses raw generic types (unparameterized generics)
|
|
78
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
79
|
+
import { resolveToolInputPath } from './lib.mjs';
|
|
80
|
+
const file = resolveToolInputPath();
|
|
81
|
+
if (!file.endsWith('.java')) process.exit(0);
|
|
82
|
+
if (!existsSync(file)) process.exit(0);
|
|
83
|
+
const repoRoot = process.cwd();
|
|
84
|
+
if (!file.startsWith(repoRoot)) process.exit(0);
|
|
85
|
+
const lines = readFileSync(file, 'utf-8').split('\\n');
|
|
86
|
+
const offending = lines.flatMap((line, i) => {
|
|
87
|
+
const t = line.trimStart();
|
|
88
|
+
// Skip comments AND import/package statements \u2014 fully-qualified type names in
|
|
89
|
+
// imports legitimately appear unparameterized (e.g. \`import java.util.List;\`).
|
|
90
|
+
// Previously every non-trivial Java file tripped the hook on its own imports
|
|
91
|
+
// (#278 finding #6).
|
|
92
|
+
if (t.startsWith('//') || t.startsWith('*') || t.startsWith('/*')) return [];
|
|
93
|
+
if (t.startsWith('import ') || t.startsWith('package ')) return [];
|
|
94
|
+
// Exclude a following '<' (parameterized: List<String>) AND a following '.'
|
|
95
|
+
// (static-factory call / field access: List.of, Optional.empty). Without the
|
|
96
|
+
// '.' exclusion the hook false-positived on idiomatic modern Java (List.of,
|
|
97
|
+
// Map.of, Set.of, Optional.empty/ofNullable) on every edit (#1560).
|
|
98
|
+
return /\\b(List|Map|Set|Collection|ArrayList|HashMap|HashSet|LinkedList|Queue|Deque|Iterator|Optional)\\b(?![<.])/.test(line) ? [\`\${i + 1}: \${line.trim()}\`] : [];
|
|
99
|
+
});
|
|
100
|
+
if (offending.length > 0) {
|
|
101
|
+
process.stderr.write(\`[arbiter] INV: Raw generic type found (always use type parameters like List<String>): \${file}\\n\`);
|
|
102
|
+
offending.slice(0, 3).forEach(l => process.stderr.write(\` \${l}\\n\`));
|
|
103
|
+
process.exit(2);
|
|
104
|
+
}`},JAVA_NO_MOCKMVC={name:"check-no-mockmvc.mjs",description:"No MockMvc usage in Java test files \u2014 use RestAssured for integration tests",body:`#!/usr/bin/env node
|
|
105
|
+
// Fail if a Java file imports or uses MockMvc (use RestAssured instead)
|
|
106
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
107
|
+
import { resolveToolInputPath } from './lib.mjs';
|
|
108
|
+
const file = resolveToolInputPath();
|
|
109
|
+
if (!file.endsWith('.java')) process.exit(0);
|
|
110
|
+
if (!existsSync(file)) process.exit(0);
|
|
111
|
+
const repoRoot = process.cwd();
|
|
112
|
+
if (!file.startsWith(repoRoot)) process.exit(0);
|
|
113
|
+
const content = readFileSync(file, 'utf-8');
|
|
114
|
+
if (/\\b(MockMvc|AutoConfigureMockMvc|MockMvcBuilders|MockMvcRequestBuilders|MockMvcResultMatchers)\\b/.test(content)) {
|
|
115
|
+
process.stderr.write(\`[arbiter] INV-29: MockMvc is forbidden \u2014 use RestAssured for integration tests: \${file}\\n\`);
|
|
116
|
+
process.exit(2);
|
|
117
|
+
}`};export function getLanguageHooks(language){const hooks=[COMMON_NO_ORPHAN_TODO];return(language==="typescript"||language==="multi")&&hooks.push(TS_NO_ANY),language==="rust"&&hooks.push(RUST_NO_UNWRAP),language==="go"&&hooks.push(GO_NO_UNCHECKED_ERR),language==="python"&&hooks.push(PY_NO_BARE_EXCEPT),(language==="java"||language==="multi")&&hooks.push(JAVA_NO_RAW_TYPES,JAVA_NO_MOCKMVC),hooks}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{existsSync,readdirSync}from"node:fs";import{join}from"node:path";function hasKotlinSources(dir){const kotlinRoot=join(dir,"src/main/kotlin");if(!existsSync(kotlinRoot))return!1;try{const stack=[kotlinRoot];let budget=200;for(;stack.length>0&&budget-- >0;){const cur=stack.pop();if(cur===void 0)break;const entries=readdirSync(cur,{withFileTypes:!0});for(const e of entries){if(e.isFile()&&e.name.endsWith(".kt"))return!0;e.isDirectory()&&stack.push(join(cur,e.name))}}return!1}catch{return!1}}function findJvmBuildFile(dir){return existsSync(join(dir,"pom.xml"))?"pom.xml":existsSync(join(dir,"build.gradle"))?"build.gradle":existsSync(join(dir,"build.gradle.kts"))?"build.gradle.kts":null}export function jvmRoot(dir){if(findJvmBuildFile(dir)!==null)return dir;const backend=join(dir,"backend");return findJvmBuildFile(backend)!==null?backend:null}export function detectLanguageWithSource(dir){const hasTs=existsSync(join(dir,"package.json")),jvmAtRoot=findJvmBuildFile(dir),jvmInBackend=findJvmBuildFile(join(dir,"backend"));return hasTs&&jvmInBackend!==null?{language:"multi",source:`package.json + ${jvmInBackend}`}:jvmAtRoot!==null?hasKotlinSources(dir)?{language:"kotlin",source:jvmAtRoot}:{language:"java",source:jvmAtRoot}:existsSync(join(dir,"Cargo.toml"))?{language:"rust",source:"Cargo.toml"}:existsSync(join(dir,"go.mod"))?{language:"go",source:"go.mod"}:existsSync(join(dir,"pyproject.toml"))?{language:"python",source:"pyproject.toml"}:existsSync(join(dir,"setup.py"))?{language:"python",source:"setup.py"}:existsSync(join(dir,"requirements.txt"))?{language:"python",source:"requirements.txt"}:hasTs?{language:"typescript",source:"package.json"}:{language:"unknown",source:null}}export function detectLanguage(dir){return detectLanguageWithSource(dir).language}export function languageSignalPresent(dir,lang){const hasTs=existsSync(join(dir,"package.json")),jvmFile=findJvmBuildFile(dir)??findJvmBuildFile(join(dir,"backend"));switch(lang){case"typescript":return hasTs;case"rust":return existsSync(join(dir,"Cargo.toml"));case"java":return jvmFile!==null;case"kotlin":return jvmFile!==null&&hasKotlinSources(dir);case"go":return existsSync(join(dir,"go.mod"));case"python":return existsSync(join(dir,"pyproject.toml"))||existsSync(join(dir,"setup.py"))||existsSync(join(dir,"requirements.txt"));case"multi":return hasTs&&jvmFile!==null;case"unknown":return!1}}export function resolveLanguage(dir,stored){const storedLang=stored?.language;if(storedLang!==void 0&&storedLang!=="unknown"){if(languageSignalPresent(dir,storedLang))return storedLang;const detected=detectLanguage(dir);return detected==="unknown"?storedLang:detected}return detectLanguage(dir)}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{existsSync}from"node:fs";import{join}from"node:path";import{readPackageJsonSafe}from"../utils/safe-read.js";export function detectPackageManager(dir){const pkg=readPackageJsonSafe(dir),configured=packageManagerFromField(pkg.packageManager),lockfile=packageManagerFromLockfile(dir);return{name:configured??lockfile??"npm",source:configured===null?lockfile===null?"default":"lockfile":"packageManager-field",isWorkspace:isWorkspace(dir,pkg)}}function packageManagerFromField(value){return typeof value!="string"?null:packageManagerFromName(value.split("@",1)[0]??"")}function packageManagerFromLockfile(dir){return existsSync(join(dir,"pnpm-lock.yaml"))?"pnpm":existsSync(join(dir,"bun.lockb"))||existsSync(join(dir,"bun.lock"))?"bun":existsSync(join(dir,"yarn.lock"))?"yarn":existsSync(join(dir,"package-lock.json"))?"npm":null}function packageManagerFromName(name){switch(name){case"npm":case"pnpm":case"yarn":case"bun":return name;default:return null}}function isWorkspace(dir,pkg){if(existsSync(join(dir,"pnpm-workspace.yaml")))return!0;const workspaces=pkg.workspaces;if(Array.isArray(workspaces))return workspaces.length>0;if(typeof workspaces!="object"||workspaces===null)return!1;const packages=workspaces.packages;return Array.isArray(packages)&&packages.length>0}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{existsSync,readdirSync,statSync}from"node:fs";import{join}from"node:path";import{readFileTranslated}from"../utils/fs.js";export function detectBasePackage(dir){const pomPath=join(dir,"pom.xml");if(existsSync(pomPath)){const content=readFileTranslated(pomPath,"utf-8"),own=content.replace(/<parent>[\s\S]*?<\/parent>/i,"").match(/<groupId>([^<]+)<\/groupId>/);if(own?.[1])return own[1].trim();const inherited=content.match(/<groupId>([^<]+)<\/groupId>/);if(inherited?.[1])return inherited[1].trim()}for(const gradleFile of["build.gradle","build.gradle.kts"]){const gradlePath=join(dir,gradleFile);if(existsSync(gradlePath)){const match=readFileTranslated(gradlePath,"utf-8").match(/^group\s*=\s*['"]([^'"]+)['"]/m);if(match?.[1])return match[1].trim()}}return detectFromJavaSourceTree(join(dir,"src","main","java"))}function detectFromJavaSourceTree(javaRoot){if(!existsSync(javaRoot))return;const parts=[];let current=javaRoot;for(let depth=0;depth<6;depth++){let entries;try{entries=readdirSync(current).filter(e=>statSync(join(current,e)).isDirectory())}catch{break}if(entries.length!==1)break;const entry=entries[0];parts.push(entry),current=join(current,entry)}return parts.length>0?parts.join("."):void 0}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{existsSync,readFileSync,readdirSync}from"node:fs";import{join}from"node:path";function declaredOptionalScripts(dir){const manifestPath=join(dir,"scripts","optional-emissions.json");if(!existsSync(manifestPath))return new Set;try{const parsed=JSON.parse(readFileSync(manifestPath,"utf-8"));return new Set((Array.isArray(parsed.optional)?parsed.optional:[]).filter(entry=>typeof entry.path=="string"&&typeof entry.rationale=="string"&&entry.rationale.trim()).map(entry=>entry.path))}catch{return new Set}}function readIfExists(dir,rel){const abs=join(dir,rel);if(!existsSync(abs))return null;try{return readFileSync(abs,"utf-8")}catch{return null}}function listCheckScripts(dir){const scriptsDir=join(dir,"scripts");if(!existsSync(scriptsDir))return[];let entries;try{entries=readdirSync(scriptsDir)}catch{return[]}return entries.filter(f=>/^check-.*\.mjs$/.test(f)&&f!=="check-all.mjs").sort().map(f=>`scripts/${f}`)}export function checkScaffoldWiring(dir){const optional=declaredOptionalScripts(dir),candidates=listCheckScripts(dir).filter(rel=>!optional.has(rel));if(candidates.length===0)return{unwired:[]};const corpus=[readIfExists(dir,join("scripts","check-all.mjs")),readIfExists(dir,"run.sh"),readIfExists(dir,"Makefile")].filter(c=>c!==null);return{unwired:candidates.filter(rel=>{const name=rel.slice(8);return!corpus.some(content=>content.includes(name))}).map(path=>({path}))}}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{existsSync,readdirSync,readFileSync}from"node:fs";import{join}from"node:path";import{parse as parseYaml}from"yaml";const FIXED_ARBITER_WORKFLOWS=new Set(["issue-state.yml","drift-shadow.yml"]);export function isArbiterOwnedWorkflow(file){return file.startsWith("_")||/^\d{2}-.*\.ya?ml$/.test(file)?!0:FIXED_ARBITER_WORKFLOWS.has(file)}const SUPERSEDING={"pr-push":{candidates:["01-pr-fast.yml","02-pr-extended.yml"],recommendation:"retire this legacy CI workflow or fold its unique steps into the numbered PR set (it now double-runs on every PR/push)"},tags:{candidates:["05-release.yml"],recommendation:"retire this legacy release workflow or fold its unique steps into 05-release.yml (a version tag now triggers TWO signing/SBOM/release runs that race on one tag)"},schedule:{candidates:["06-nightly.yml","06-nightly-lite.yml","07-weekly.yml","07-weekly-lite.yml","08-monthly.yml","12-mutation-scheduled.yml"],recommendation:"retire this legacy scheduled workflow or fold it into the numbered nightly/weekly/monthly cadence"}},CLASS_ORDER=["pr-push","tags","schedule"];function isRecord(v){return typeof v=="object"&&v!==null&&!Array.isArray(v)}function hasKey(cfg,key){return isRecord(cfg)&&key in cfg}function classifyPush(cfg,classes){const onTags=hasKey(cfg,"tags")||hasKey(cfg,"tags-ignore"),onBranches=hasKey(cfg,"branches")||hasKey(cfg,"branches-ignore");onTags&&classes.add("tags"),(onBranches||!onTags)&&classes.add("pr-push")}function addEvent(name,cfg,classes){name==="pull_request"||name==="pull_request_target"?classes.add("pr-push"):name==="push"?classifyPush(cfg,classes):name==="schedule"&&classes.add("schedule")}export function classifyTriggers(onSpec){const classes=new Set;if(onSpec==null)return classes;if(typeof onSpec=="string")addEvent(onSpec,void 0,classes);else if(Array.isArray(onSpec))for(const e of onSpec)typeof e=="string"&&addEvent(e,void 0,classes);else if(isRecord(onSpec))for(const key of Object.keys(onSpec))addEvent(key,onSpec[key],classes);return classes}function readWorkflowOn(path){try{const parsed=parseYaml(readFileSync(path,"utf8"));return isRecord(parsed)?parsed.on??parsed[String(!0)]??null:null}catch{return null}}export function detectLegacyWorkflowCollisions(targetDir){const dir=join(targetDir,".github","workflows");if(!existsSync(dir))return[];const all=readdirSync(dir).filter(f=>/\.ya?ml$/.test(f)),present=new Set(all.filter(isArbiterOwnedWorkflow));if(present.size===0)return[];const legacy=all.filter(f=>!isArbiterOwnedWorkflow(f)).sort(),collisions=[];for(const file of legacy){const classes=classifyTriggers(readWorkflowOn(join(dir,file)));for(const cls of CLASS_ORDER){if(!classes.has(cls))continue;const supersededBy=SUPERSEDING[cls].candidates.find(c=>present.has(c));supersededBy&&collisions.push({legacyFile:file,triggerClass:cls,supersededBy,recommendation:SUPERSEDING[cls].recommendation})}}return collisions}const CLASS_LABEL={"pr-push":"PR/push CI",tags:"release on version tags",schedule:"scheduled run"};function formatLegacyCollisionWarning(collisions){if(collisions.length===0)return null;const lines=collisions.map(c=>` - ${c.legacyFile} (${CLASS_LABEL[c.triggerClass]}) collides with ${c.supersededBy} \u2014 `+c.recommendation);return`Warning: ${collisions.length} legacy workflow trigger collision(s) detected. arbiter update ADDED the numbered workflows but did NOT remove pre-existing workflows whose triggers now overlap \u2014 they will double-run, and a legacy release workflow will race a second signing/SBOM run on the same version tag. arbiter never auto-deletes your files; reconcile each collision below (retire the legacy workflow, or fold its unique steps into the numbered one):
|
|
3
|
+
${lines.join(`
|
|
4
|
+
`)}`}export function detectLegacyWorkflowCollisionWarning(targetDir){return formatLegacyCollisionWarning(detectLegacyWorkflowCollisions(targetDir))}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{createHash}from"node:crypto";import{existsSync,readFileSync,realpathSync}from"node:fs";import{tmpdir}from"node:os";import{join,isAbsolute,relative,resolve}from"node:path";import{sanitizeTaskId}from"../utils/task-id.js";import{mkdtempTranslated,rmTranslated,readFileTranslated}from"../utils/fs.js";import{runCli}from"../utils/run-cli.js";export const GATE_PASS_POLICY={schema:"arbiter-gate-pass-v3",defaultTtlMinutes:240,futureSkewMinutes:2,levelRank:{L0:0,L1:1,L2:2,L3:3},stringFields:["schema","head_sha","branch","task_id","timestamp","level","node_version","tree_hash","checkout_root","toolchain_fingerprint","gate_started_at","start_head_sha","start_tree_hash"],toolchainInputs:["package.json","package-lock.json","node_modules/.package-lock.json","yarn.lock","pnpm-lock.yaml",".nvmrc","pom.xml","build.gradle","build.gradle.kts","gradle/wrapper/gradle-wrapper.properties","pyproject.toml","poetry.lock","uv.lock","requirements.txt","requirements-dev.txt","go.mod","go.sum","Cargo.toml","Cargo.lock"]};function gitLine(root,args){try{const out=runCli("git",[...args],{cwd:root,timeoutMs:15e3}).stdout.trim();return out===""?null:out}catch{return null}}function checkoutRootOf(root){const top=gitLine(root,["rev-parse","--show-toplevel"]);if(top===null)return null;try{return realpathSync(top)}catch{return null}}function treeHashOf(root){const top=checkoutRootOf(root);if(top===null)return null;let indexDir=null;try{indexDir=mkdtempTranslated(join(tmpdir(),"arbiter-tree-index-"));const env={...process.env,GIT_INDEX_FILE:join(indexDir,"index")};runCli("git",["add","-A"],{cwd:top,env,timeoutMs:6e4}),runCli("git",["rm","-r","--cached","--ignore-unmatch","-q",".arbiter"],{cwd:top,env,timeoutMs:3e4});const out=runCli("git",["write-tree"],{cwd:top,env,timeoutMs:15e3}).stdout.trim();return out===""?null:out}catch{return null}finally{indexDir!==null&&rmTranslated(indexDir,{recursive:!0,force:!0})}}function toolchainFingerprintOf(root){const top=checkoutRootOf(root)??root,outer=createHash("sha256");for(const rel of GATE_PASS_POLICY.toolchainInputs){const path=join(top,...rel.split("/"));let entry="absent";try{existsSync(path)&&(entry=createHash("sha256").update(readFileSync(path)).digest("hex"))}catch{entry="unreadable"}outer.update(`${rel}
|
|
3
|
+
${entry}
|
|
4
|
+
`)}return`sha256:${outer.digest("hex")}`}function shapeProblem(fields){for(const field of GATE_PASS_POLICY.stringFields){const value=fields[field];if(typeof value!="string"||value.trim()==="")return`gate-pass marker field "${field}" is missing or empty \u2014 evidence from an older arbiter is not honoured; re-run the gate to stamp a current marker`}return fields.schema!==GATE_PASS_POLICY.schema?`gate-pass marker schema mismatch: expected "${GATE_PASS_POLICY.schema}", got "${String(fields.schema)}"`:fields.tree_was_clean_at_run_time!==!0?"gate-pass marker tree_was_clean_at_run_time must be true":null}function levelProblem(fields,minLevel){const level=String(fields.level),rank=GATE_PASS_POLICY.levelRank[level],required=GATE_PASS_POLICY.levelRank[minLevel];return typeof rank!="number"?`gate-pass marker level "${level}" is not a known gate level`:typeof required!="number"?`required gate level "${minLevel}" is not a known gate level`:rank<required?`gate-pass marker level "${level}" is below the required "${minLevel}"`:null}function agingProblem(fields,maxAgeMin,now){if(!Number.isFinite(maxAgeMin)||maxAgeMin<=0)return`gate-pass age budget must be a positive number, got ${JSON.stringify(maxAgeMin)}`;const ttl=fields.ttl_minutes;if(typeof ttl!="number"||!Number.isFinite(ttl)||ttl<=0)return`gate-pass marker ttl_minutes must be a positive finite number, got ${JSON.stringify(ttl)}`;const timestamp=String(fields.timestamp),stampedAt=Date.parse(timestamp);if(!Number.isFinite(stampedAt))return`gate-pass marker timestamp is not a valid date: "${timestamp}"`;const ageMin=(now-stampedAt)/6e4;if(ageMin<-GATE_PASS_POLICY.futureSkewMinutes)return`gate-pass marker timestamp is in the future: "${timestamp}"`;const budget=Math.min(maxAgeMin,ttl);return ageMin>budget?`gate-pass marker expired: ${Math.round(ageMin)} min old, budget ${budget} min`:null}function commitProblem(root,fields,taskId){const head=gitLine(root,["rev-parse","HEAD"]);if(head===null)return"gate-pass marker unverifiable: HEAD does not resolve";if(fields.head_sha!==head)return`gate-pass marker head_sha mismatch: expected "${head}", got "${String(fields.head_sha)}"`;const branch=gitLine(root,["rev-parse","--abbrev-ref","HEAD"]);return branch===null?"gate-pass marker unverifiable: branch does not resolve":fields.branch!==branch?`gate-pass marker branch mismatch: expected "${branch}", got "${String(fields.branch)}"`:typeof taskId=="string"&&taskId.trim()!==""&&fields.task_id!==taskId?`gate-pass marker task_id "${String(fields.task_id)}" does not match the current task "${taskId}" (anti-replay)`:null}function startEndProblem(fields){return fields.start_head_sha!==fields.head_sha?`gate-pass marker is unverifiable: the gate started on commit "${String(fields.start_head_sha)}" and finished on "${String(fields.head_sha)}" \u2014 it did not measure one tree from start to finish`:fields.start_tree_hash!==fields.tree_hash?`gate-pass marker is unverifiable: the working tree changed while the gate ran (started at tree "${String(fields.start_tree_hash)}", finished at "${String(fields.tree_hash)}")`:null}function identityProblem(root,fields){if(fields.node_version!==process.version)return`gate-pass marker node_version mismatch: expected "${process.version}", got "${String(fields.node_version)}"`;const checkoutRoot=checkoutRootOf(root);if(checkoutRoot===null)return"gate-pass marker unverifiable: checkout root does not resolve";if(fields.checkout_root!==checkoutRoot)return`gate-pass marker checkout_root mismatch: evidence was produced in "${String(fields.checkout_root)}", this checkout is "${checkoutRoot}" \u2014 evidence does not travel between worktrees`;if(fields.toolchain_fingerprint!==toolchainFingerprintOf(root))return"gate-pass marker toolchain_fingerprint mismatch \u2014 a lockfile or the installed toolchain changed since the gate ran";const treeHash=treeHashOf(root);return treeHash===null?"gate-pass marker unverifiable: tree hash does not resolve":fields.tree_hash!==treeHash?"gate-pass marker tree_hash mismatch \u2014 the working tree changed since the gate ran":null}export function verifyGatePassMarker(marker,opts){if(typeof marker!="object"||marker===null||Array.isArray(marker))return{ok:!1,reason:"gate-pass marker must be a JSON object"};const fields=marker,{root}=opts,checks=[()=>shapeProblem(fields),()=>levelProblem(fields,opts.minLevel??"L2"),()=>agingProblem(fields,opts.maxAgeMin??GATE_PASS_POLICY.defaultTtlMinutes,opts.now??Date.now()),()=>commitProblem(root,fields,opts.taskId),()=>identityProblem(root,fields),()=>startEndProblem(fields)];for(const check of checks){const reason=check();if(reason!==null)return{ok:!1,reason}}return{ok:!0}}function objectFields(value){if(typeof value!="object"||value===null||Array.isArray(value))throw new Error("done evidence must contain JSON objects");return value}function verifyPinnedFiles(value,root){if(!Array.isArray(value)||value.length===0)throw new Error("pinned_files is empty");for(const entry of value)verifyPinnedFile(entry,root)}function verifyPinnedFile(entry,root){const fields=objectFields(entry);if(typeof fields.path!="string"||typeof fields.sha256!="string")throw new Error("malformed pinned file");const path=resolve(root,fields.path),rel=relative(root,path);if(isAbsolute(fields.path)||rel===".."||rel.startsWith("../"))throw new Error("pinned file must remain inside the checkout");if(realpathSync(path)!==resolve(realpathSync(root),fields.path))throw new Error(`pinned file must not traverse symlinks: ${fields.path}`);if(createHash("sha256").update(readFileTranslated(path)).digest("hex")!==fields.sha256)throw new Error(`pinned file SHA mismatch: ${fields.path}`)}function verifyRuntimeReceipt(value,archetype){const rc=objectFields(value),suites=archetype==="backend-web-db"?["live-api-e2e"]:archetype==="frontend-spa"?["render-smoke","visual-regression"]:[];if(typeof rc.required!="boolean")throw new Error("runtime required must be boolean");if(suites.length>0&&(!rc.required||!suites.includes(String(rc.suite))))throw new Error(`required runtime evidence missing for ${archetype}`);if(rc.required?rc.passed!==!0:rc.passed!==null)throw new Error("runtime evidence has not passed or is malformed")}function verifyReceiptHeader(receipt,taskId){if(receipt.version!==2||receipt.task_id!==taskId||receipt.state!=="passed")throw new Error("v2 done receipt must be passed for the current task");if(receipt.all_green!==!0||receipt.no_overclaim!==!0||receipt.gate_level!=="L3")throw new Error("done receipt is not a green L3 qualification")}export function verifyDoneEvidenceReceipt(opts){try{const path=join(opts.root,".arbiter/evidence/done",`${sanitizeTaskId(opts.taskId)}.json`),receipt=objectFields(JSON.parse(readFileSync(path,"utf8")));verifyReceiptHeader(receipt,opts.taskId);const bytes=readFileSync(join(opts.root,".arbiter/gate-pass.json"));if(createHash("sha256").update(bytes).digest("hex")!==receipt.gate_marker_sha256)throw new Error("done receipt gate marker digest mismatch");const marker=objectFields(JSON.parse(bytes.toString("utf8")));for(const field of["head_sha","tree_hash","checkout_root","toolchain_fingerprint","node_version"])if(receipt[field]!==marker[field])throw new Error(`done receipt ${field} mismatch`);return verifyPinnedFiles(receipt.pinned_files,opts.root),verifyRuntimeReceipt(receipt.reality_contact,opts.archetype),verifyGatePassMarker(marker,{...opts,minLevel:"L3"})}catch(err){return{ok:!1,reason:`done receipt invalid: ${err instanceof Error?err.message:String(err)}`}}}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{runCli}from"../utils/run-cli.js";export function gitCwd(dir){return process.env.ARBITER_HOOK_GIT_CWD||dir||process.cwd()}export function shaExistsOnBranch(sha,dir){try{return runCli("git",["merge-base","--is-ancestor",sha,"HEAD"],{cwd:gitCwd(dir),timeoutMs:5e3}).exitCode===0}catch{return!1}}export function hasDirtyTestPaths(dir){try{const result=runCli("git",["status","--porcelain","--untracked-files=all","--","__tests__"],{cwd:gitCwd(dir),timeoutMs:5e3});return result.exitCode===0&&result.stdout.trim().length>0}catch{return!1}}export function pathExistsInCommit(sha,path,dir){try{const result=runCli("git",["ls-tree","--name-only",sha,path],{cwd:gitCwd(dir),timeoutMs:5e3});return result.exitCode===0&&result.stdout.trim().length>0}catch{return!1}}export function tddEvidenceProducedOnBranch(taskId,dir){const path=`.arbiter/evidence/tdd/${taskId}.json`;if(!pathExistsInCommit("HEAD",path,dir))return!1;try{if(runCli("git",["status","--porcelain","--untracked-files=all","--",path],{cwd:gitCwd(dir),timeoutMs:5e3}).stdout.trim().length>0)return!1;const base=runCli("git",["merge-base","origin/main","HEAD"],{cwd:gitCwd(dir),timeoutMs:5e3}).stdout.trim();return base.length===0?!1:runCli("git",["log","--format=%H",`${base}..HEAD`,"--",path],{cwd:gitCwd(dir),timeoutMs:5e3}).stdout.trim().length>0}catch{return!1}}function gitValue(args,dir){try{const result=runCli("git",[...args],{cwd:gitCwd(dir),timeoutMs:5e3}),out=result.stdout.trim();return result.exitCode===0&&out.length>0?out:"unknown"}catch{return"unknown"}}export function currentBranch(dir){return gitValue(["rev-parse","--abbrev-ref","HEAD"],dir)}export function headSha(dir){return gitValue(["rev-parse","HEAD"],dir)}export function blobShaInCommit(sha,path,dir){const out=gitValue(["rev-parse",`${sha}:${path}`],dir);return/^[0-9a-f]{40}$/i.test(out)?out:null}function isShallowRepository(dir){return gitValue(["rev-parse","--is-shallow-repository"],dir)==="true"}export function resolveEvidenceCommit(ev,dir){if(shaExistsOnBranch(ev.test_commit_sha,dir))return{sha:ev.test_commit_sha,healed:!1};if(isShallowRepository(dir))return{degraded:!0,reason:"git history is shallow; fetch full history (for GitHub Actions, set actions/checkout fetch-depth: 0) before verifying TDD evidence"};const blob=ev.test_blob_sha;if(blob===void 0)return null;const touching=gitValue(["log","--format=%H","--max-count=200","--",ev.test_path],dir);if(touching==="unknown")return null;for(const sha of touching.split(`
|
|
3
|
+
`).reverse())if(sha.length===40&&blobShaInCommit(sha,ev.test_path,dir)===blob)return{sha,healed:!0};return null}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{existsSync,readFileSync}from"node:fs";function isRecord(v){return typeof v=="object"&&v!==null&&!Array.isArray(v)}export function loadSummaryFile(path){if(!existsSync(path))return{ok:!1,reason:".evidence/SUMMARY.json not found"};let parsed;try{parsed=JSON.parse(readFileSync(path,"utf-8"))}catch(err){return{ok:!1,reason:`invalid JSON: ${err instanceof Error?err.message:String(err)}`}}return isRecord(parsed)?{ok:!0,body:parsed}:{ok:!1,reason:"SUMMARY.json root must be an object"}}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{createHash}from"node:crypto";import{existsSync,readdirSync,readFileSync}from"node:fs";import{join}from"node:path";const PROVENANCE_STRING_FIELDS=["model_id","agent_harness","harness_version","gate_manifest_hash","session_id"],CONFIG_HASH_FIELD_KINDS={agents_md:"string",claude_md:"string",skills:"stringArray"};function isStringArray(v){return Array.isArray(v)&&v.every(e=>typeof e=="string")}function validateConfigHashes(value){const errors=[];if(typeof value!="object"||value===null||Array.isArray(value))return errors.push("provenance.config_hashes: expected an object"),errors;const obj=value,allowed=new Set(Object.keys(CONFIG_HASH_FIELD_KINDS));for(const key of Object.keys(obj))allowed.has(key)||errors.push(`provenance.config_hashes: additional property "${key}" is not allowed`);for(const[field,kind]of Object.entries(CONFIG_HASH_FIELD_KINDS)){if(!(field in obj))continue;const val=obj[field];(kind==="string"?typeof val=="string":isStringArray(val))||errors.push(`provenance.config_hashes.${field}: invalid type`)}return errors}export function validateProvenance(value){if(typeof value!="object"||value===null||Array.isArray(value))return["provenance: expected an object"];const obj=value,errors=[],allowedTop=new Set([...PROVENANCE_STRING_FIELDS,"config_hashes"]);for(const key of Object.keys(obj))allowedTop.has(key)||errors.push(`provenance: additional property "${key}" is not allowed`);for(const field of PROVENANCE_STRING_FIELDS)field in obj&&typeof obj[field]!="string"&&errors.push(`provenance.${field}: expected type "string", got "${typeof obj[field]}"`);return"config_hashes"in obj&&errors.push(...validateConfigHashes(obj.config_hashes)),errors}function isTruthy(val){return!!val&&val!=="0"&&val!=="false"}function hashFile(path){if(existsSync(path))try{return createHash("sha256").update(readFileSync(path)).digest("hex")}catch{return}}function hashSkills(root){const skillsDir=join(root,".claude","skills");if(!existsSync(skillsDir))return;let entries;try{entries=readdirSync(skillsDir,{withFileTypes:!0}).filter(e=>e.isDirectory()).map(e=>e.name)}catch{return}const hashes=[];for(const name of entries){const h=hashFile(join(skillsDir,name,"SKILL.md"));h&&hashes.push(h)}return hashes.length>0?hashes:void 0}function buildConfigHashes(root){const agentsMd=hashFile(join(root,"AGENTS.md")),claudeMd=hashFile(join(root,".claude","CLAUDE.md")),skills=hashSkills(root);if(!agentsMd&&!claudeMd&&!skills)return;const result={};return agentsMd&&(result.agents_md=agentsMd),claudeMd&&(result.claude_md=claudeMd),skills&&(result.skills=skills),result}function parseHarnessVersion(execPath){if(!execPath)return;const parts=execPath.split("/"),idx=parts.indexOf("versions");if(!(idx===-1||idx+1>=parts.length))return parts[idx+1]}export function buildProvenance(root,env){const result={};isTruthy(env.CLAUDECODE)&&(result.agent_harness="claude-code");const harnessVersion=parseHarnessVersion(env.CLAUDE_CODE_EXECPATH);harnessVersion&&(result.harness_version=harnessVersion);const sessionId=env.CLAUDE_CODE_SESSION_ID;sessionId&&(result.session_id=sessionId);const configHashes=buildConfigHashes(root);configHashes&&(result.config_hashes=configHashes);const gateManifestHash=hashFile(join(root,"scripts","check-all.mjs"));return gateManifestHash&&(result.gate_manifest_hash=gateManifestHash),Object.keys(result).length>0?result:void 0}export function formatProvenance(p){const lines=[];for(const field of PROVENANCE_STRING_FIELDS){const val=p[field];val&&lines.push(`provenance.${field}: ${val}`)}return p.config_hashes?.agents_md&&lines.push(`provenance.config_hashes.agents_md: ${p.config_hashes.agents_md}`),p.config_hashes?.claude_md&&lines.push(`provenance.config_hashes.claude_md: ${p.config_hashes.claude_md}`),p.config_hashes?.skills&&p.config_hashes.skills.length>0&&lines.push(`provenance.config_hashes.skills: ${p.config_hashes.skills.length} skill(s)`),lines}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{validateProvenance}from"./provenance.js";const REQUIRED_FIELDS=["head_sha","head_sha_short","obs_gate","tests","coverage","mutation","security"];export function validateSummarySchema(body){const errors=[];for(const field of REQUIRED_FIELDS)(!(field in body)||body[field]===void 0||body[field]===null)&&errors.push(`missing required field: ${field}`);return"obs_gate"in body&&body.obs_gate!=="PASS"&&body.obs_gate!=="FAIL"&&errors.push(`obs_gate must be "PASS" or "FAIL", got: ${String(body.obs_gate)}`),body.provenance!==void 0&&errors.push(...validateProvenance(body.provenance)),errors.length>0?{ok:!1,errors}:{ok:!0}}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{existsSync}from"node:fs";import{tmpdir}from"node:os";import{join}from"node:path";import{CliError,runCli}from"../utils/run-cli.js";import{combineTestOutput,extractFailureIdentities,extractFailureSignature,repositoryRelativeLog}from"./tdd.js";import{gitCwd}from"./git-checks.js";import{mkdtempTranslated,rmTranslated,symlinkTranslated}from"../utils/fs.js";export const DEFAULT_REEXEC_TIMEOUT_MS=12e4;export function verifyRedExecution(ev,dir,timeoutMs=DEFAULT_REEXEC_TIMEOUT_MS){const testCommand=ev.test_command;if(testCommand===void 0||testCommand.length===0)return{ok:!1,reason:"evidence has no recorded test_command \u2014 legacy evidence predating #1957 cannot be re-verified against source; re-record with `arbiter task record-red` to upgrade"};const repoDir=gitCwd(dir),worktreeDir=freeTempPath();try{const added=addDetachedWorktree(repoDir,worktreeDir,ev.test_commit_sha);if(!added.ok)return added;linkNodeModules(dir??repoDir,worktreeDir);const freshLog=runTestCommand(testCommand,worktreeDir,timeoutMs);return compareFailure(ev,repositoryRelativeLog(freshLog,worktreeDir))}finally{removeDetachedWorktree(repoDir,worktreeDir)}}function compareFailure(ev,freshLog){const fresh=extractFailureIdentities(freshLog);if(fresh.length===0)return{ok:!1,reason:`test_command [${ev.test_command?.join(" ")??""}] did not fail when re-run at test_commit_sha ${ev.test_commit_sha} \u2014 the recorded RED phase could not be reproduced from source (false-green risk)`};const recordedSig=extractFailureSignature(ev.test_run_log);if(recordedSig===null||JSON.stringify(extractFailureIdentities(recordedSig.match))!==JSON.stringify(extractFailureIdentities(ev.observed_failure)))return{ok:!1,reason:"recorded observed_failure contradicts test_run_log"};const recorded=extractFailureIdentities(ev.test_run_log);return JSON.stringify(fresh)!==JSON.stringify(recorded)?{ok:!1,reason:`re-run at test_commit_sha ${ev.test_commit_sha} failed with ${JSON.stringify(fresh)}, not the recorded observed_failure identities ${JSON.stringify(recorded)} \u2014 a named failure may not have existed at that commit`}:{ok:!0}}function freeTempPath(){const d=mkdtempTranslated(join(tmpdir(),"arbiter-tdd-verify-"));return rmTranslated(d,{recursive:!0,force:!0}),d}function addDetachedWorktree(repoDir,worktreeDir,sha){try{const r=runCli("git",["worktree","add","--detach","--force",worktreeDir,sha],{cwd:repoDir,timeoutMs:3e4});return r.exitCode!==0?{ok:!1,reason:`failed to check out test_commit_sha ${sha} in an isolated worktree: ${r.stderr.trim()}`}:{ok:!0}}catch(err){const detail=err instanceof CliError?err.stderr||err.message:String(err);return{ok:!1,reason:`failed to check out test_commit_sha ${sha} in an isolated worktree: ${detail}`}}}function linkNodeModules(sourceDir,worktreeDir){const src=join(sourceDir,"node_modules"),dest=join(worktreeDir,"node_modules");if(!(!existsSync(src)||existsSync(dest)))try{symlinkTranslated(src,dest,"dir")}catch{}}function runTestCommand(testCommand,cwd,timeoutMs){const[cmd,...args]=testCommand;if(cmd===void 0)return"";try{const r=runCli(cmd,args,{cwd,timeoutMs});return r.exitCode>0?combineTestOutput(r.stdout,r.stderr):""}catch(err){return err instanceof CliError&&err.exitCode>0&&!err.timedOut&&!err.notFound&&!err.outputTruncated?combineTestOutput(err.stdout,err.stderr):""}}function removeDetachedWorktree(repoDir,worktreeDir){try{runCli("git",["worktree","remove","--force",worktreeDir],{cwd:repoDir,timeoutMs:3e4})}catch{}rmTranslated(worktreeDir,{recursive:!0,force:!0})}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{existsSync,readFileSync}from"node:fs";import{join,resolve}from"node:path";import{z}from"zod";import{writeFile,assertWritten}from"../utils/fs.js";export const TddEvidenceV1=z.object({$schemaVersion:z.literal(1),task_id:z.string().regex(/^#\d+$/,"task_id must start with # followed by digits"),test_path:z.string().min(1),test_commit_sha:z.string().length(40,"test_commit_sha must be exactly 40 hex characters"),test_blob_sha:z.string().length(40,"test_blob_sha must be exactly 40 hex characters").optional(),test_run_log:z.string(),observed_failure:z.string().min(1,"observed_failure must not be empty"),recorded_at:z.iso.datetime({message:"recorded_at must be ISO8601"}),test_command:z.array(z.string()).optional()}),FAILURE_SIGNATURES=[{framework:"vitest",pattern:/FAIL\s+\S+\.test\.[jt]sx?/m},{framework:"jest",pattern:/FAIL\s+\S+\.(spec|test)\.[jt]sx?/m},{framework:"cucumber",pattern:/\d+ scenarios? \(\d+ failed/m},{framework:"pytest",pattern:/={3,}\s*FAILURES\s*={3,}/m},{framework:"gradle",pattern:/FAILED\s*$|BUILD FAILED/m},{framework:"cargo",pattern:/test result: FAILED/m},{framework:"go",pattern:/--- FAIL:/m},{framework:"tap",pattern:/^# fail [1-9]\d*/m},{framework:"playwright",pattern:/^\s*[1-9]\d* failed\b/m}];const ANSI_SGR=/\x1b\[[0-9;]*m/g;export function extractFailureSignature(log){const plain=log.replace(ANSI_SGR,"");for(const entry of FAILURE_SIGNATURES){const m=plain.match(entry.pattern);if(m!==null)return{framework:entry.framework,match:m[0]}}return null}export function extractFailureIdentities(log){const plain=log.replace(ANSI_SGR,""),identities=new Set;for(const{framework,pattern}of FAILURE_SIGNATURES){const isJs=framework==="vitest"||framework==="jest",source=isJs?"^[ \\t]*FAIL[ \\t]+\\S+\\.(?:spec|test)\\.[jt]sx?\\b":pattern.source;for(const match of plain.matchAll(new RegExp(source,`${pattern.flags}g`))){let identity=match[0].trim().replace(/^FAIL\s+/,"FAIL ");if(isJs){const suffix=plain.slice(match.index+match[0].length).split(/\r?\n/,1)[0]??"";/^[ \t]+>/.test(suffix)&&(identity+=` ${suffix.trim()}`)}identities.add(identity)}}return[...identities].sort()}export function repositoryRelativeLog(log,dir){const root=resolve(dir);return[root,root.replaceAll("\\","/")].reduce((relative,prefix)=>relative.replaceAll(`${prefix}/`,"").replaceAll(`${prefix}\\`,"").replaceAll(prefix,"."),log)}export function combineTestOutput(stdout,stderr){return stdout+(stderr?`
|
|
3
|
+
${stderr}`:"")}export function tddEvidencePath(taskId,repoDir){return join(repoDir,".arbiter","evidence","tdd",`${taskId}.json`)}export function loadTddEvidence(taskId,repoDir){const p=tddEvidencePath(taskId,repoDir);if(!existsSync(p))return{ok:!1,reason:`TDD evidence not found at ${p}`};let raw;try{raw=JSON.parse(readFileSync(p,"utf-8"))}catch(err){return{ok:!1,reason:`invalid JSON in evidence file: ${err instanceof Error?err.message:String(err)}`}}const parsed=TddEvidenceV1.safeParse(raw);return parsed.success?{ok:!0,data:parsed.data}:{ok:!1,reason:`schema validation failed: ${parsed.error.issues.map(i=>i.message).join("; ")}`}}export function writeTddEvidence({repoDir,evidence}){const parsed=TddEvidenceV1.parse(evidence),p=tddEvidencePath(parsed.task_id,repoDir);if(existsSync(p)){const existing=loadTddEvidence(parsed.task_id,repoDir);if(existing.ok&&existing.data.task_id!==parsed.task_id)throw new Error(`refusing to overwrite ${p}: it belongs to task ${existing.data.task_id}, not ${parsed.task_id}`)}const result=writeFile(p,JSON.stringify(parsed,null,2)+`
|
|
4
|
+
`,{skipPreserveCheck:!0});return assertWritten(result,`TDD evidence for ${parsed.task_id}`),p}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{getExperiment}from"./registry.js";const EXPERIMENTAL_PREFIX_RE=/^--experimental\.([^=]+)(?:=.*)?$/;export function parseExperimentalArgv(argv){const remaining=[],flags={};for(const arg of argv){const match=EXPERIMENTAL_PREFIX_RE.exec(arg);if(match!==null){const name=match[1]??"";getExperiment(name),flags[name]=!0}else remaining.push(arg)}return{remaining,flags}}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
const EXPERIMENTS=[{name:"kit",stabilityTarget:"beta",addedIn:"0.1.0",promotionCriteria:"\u22656 months active + \u22653 user reports + zero P0 issues + Phase F complete",plannedReviewDate:"2026-11-18"}],experimentMap=new Map(EXPERIMENTS.map(e=>[e.name,e]));export function getExperiment(name){const record=experimentMap.get(name);if(record===void 0){const known=EXPERIMENTS.map(e=>e.name).join(", ")||"none registered";throw new Error(`[arbiter] Unknown experiment: "${name}". Known experiments: ${known}.`)}return record}export function isEnabled(name,flags){return getExperiment(name),flags[name]===!0}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{renderTemplate}from"../utils/render.js";import{writeFile,resolvedPath}from"../utils/fs.js";export const AGENT_NAMES=["codebase-scanner","red-team","context-checker","bridge-reviewer"];export function generateAgentsClaude(config,opts={dryRun:!1}){if(!config.tools.includes("claude"))return{files:[]};const data=config,base=config.targetDir,files=AGENT_NAMES.map(name=>writeFile(resolvedPath(base,".claude","agents",`${name}.md`),renderTemplate(`claude/agents/${name}.md.ejs`,data),{skipIfExists:!0,dryRun:opts.dryRun}));return files.push(writeFile(resolvedPath(base,".claude","agents","agent-write-classes.json"),renderTemplate("claude/agents/agent-write-classes.json",data),{skipIfExists:!0,dryRun:opts.dryRun})),{files}}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{renderTemplate}from"../utils/render.js";import{writeFile,resolvedPath}from"../utils/fs.js";import{getFilteredInvariants,getInvariantsByTier}from"../invariants/filter.js";import{TIER_LABELS}from"../invariants/tiers.js";export function renderAgentsMd(config,installedSkills=[],skippedGenerators=[]){const invariants=getFilteredInvariants({language:config.language,governanceLevel:config.governanceLevel,invariantTiers:config.invariantTiers,...config.includeExtendedInvariants?{includeExtendedInvariants:!0}:{},...config.projectInvariants!==void 0?{projectInvariants:config.projectInvariants}:{}}),invariantsByTier=getInvariantsByTier(invariants),data={...config,invariants,invariantsByTier,tierLabels:TIER_LABELS,installedSkills,skippedGenerators};return renderTemplate("agents-md/AGENTS.md.ejs",data)}export function generateAgentsMd(config,installedSkills=[],skippedGenerators=[],opts={dryRun:!1}){const content=renderAgentsMd(config,installedSkills,skippedGenerators);return writeFile(resolvedPath(config.targetDir,"AGENTS.md"),content,{backup:!0,dryRun:opts.dryRun})}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{renderTemplate}from"../utils/render.js";import{writeFile,resolvedPath}from"../utils/fs.js";function emitW6DualTrack(base,config,opts){return["check-suppression-rationale.mjs","check-suppression-expiry.mjs","check-secret-scan.mjs","check-drift.mjs","check-workflow-runners.mjs","check-workflow-docs-sync.mjs","check-workflow-test-integrity.mjs","check-secret-presence.mjs","check-continue-on-error.mjs","check-test-scope-tier.mjs","check-pr-size-gate.mjs","check-claude-md-lint.mjs","check-unwired-guards.mjs"].map(name=>writeFile(resolvedPath(base,"scripts",name),renderTemplate(`scripts/${name}.ejs`,config),{skipIfExists:!0,dryRun:opts.dryRun}))}function emitW6TrackBOnly(base,config,opts){return["check-workflow-sha-pinning.mjs","check-workflow-job-naming.mjs","check-min-test-execution.mjs"].map(name=>writeFile(resolvedPath(base,"scripts",name),renderTemplate(`scripts/${name}.ejs`,config),{skipIfExists:!0,dryRun:opts.dryRun}))}function githubSetupEnabled(config){return(config.permitGitHub??config.useGitHub)&&config.governanceLevel!=="L1"}function selfValidationEnabled(config){return config.enableSelfValidationHarness!==!1}function emitF4Validators(base,config,opts){const scripts=["check-validator-helptext.mjs"];return githubSetupEnabled(config)||scripts.push("check-action-pins.mjs","check-workflow-perms.mjs","check-ci-tiers.mjs"),selfValidationEnabled(config)||scripts.push("check-exit-code-contract.mjs","check-pipe-tee-hazard.mjs"),scripts.map(name=>writeFile(resolvedPath(base,"scripts",name),renderTemplate(`scripts/${name}.ejs`,config),{skipIfExists:!0,dryRun:opts.dryRun}))}function emitLiveSsotManifest(base,config,opts){if(config.liveSsot===void 0)return[];const content=JSON.stringify({surfaces:config.liveSsot.surfaces.map(s=>({path:s.path,kind:s.kind,...s.keys?{keys:s.keys}:{}}))},null,2)+`
|
|
3
|
+
`;return[writeFile(resolvedPath(base,".arbiter","live-ssot.json"),content,{skipIfExists:!0,dryRun:opts.dryRun})]}export function generateAntiDriftValidators(config,opts={dryRun:!1}){const base=config.targetDir;return{files:[...emitW6DualTrack(base,config,opts),...emitW6TrackBOnly(base,config,opts),...emitF4Validators(base,config,opts),...emitLiveSsotManifest(base,config,opts)]}}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{renderTemplate}from"../utils/render.js";import{writeFile,resolvedPath}from"../utils/fs.js";export function generateAntiProforma(config,opts={dryRun:!1}){const base=config.targetDir,data=config;return{files:[writeFile(resolvedPath(base,"scripts","check-anti-proforma.mjs"),renderTemplate("scripts/check-anti-proforma.mjs.ejs",data),{dryRun:opts.dryRun})]}}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{chmodTranslated,resolvedPath,unlinkTranslated,writeFile}from"../utils/fs.js";import{renderTemplate}from"../utils/render.js";import{formatContent}from"../utils/prettier-format.js";const SERVICE_ARCHETYPES=new Set(["backend-web-db"]),STACK_BY_LANGUAGE={typescript:{template:"api-e2e/ts-supertest.test.ts.ejs",filename:"api.e2e.test.ts",framework:"supertest",glob:"tests/api/**/*.test.ts"},go:{template:"api-e2e/go-httptest.go.ejs",filename:"api_e2e_test.go",framework:"httptest",glob:"tests/api/**/*_test.go"},java:{template:"api-e2e/java-restassured.java.ejs",filename:"ApiE2ETest.java",framework:"restassured",glob:"tests/api/**/*.java"},kotlin:{template:"api-e2e/kotlin-restassured.kt.ejs",filename:"ApiE2ETest.kt",framework:"restassured",glob:"tests/api/**/*.kt"},python:{template:"api-e2e/python-httpx.py.ejs",filename:"test_api_e2e.py",framework:"httpx",glob:"tests/api/**/*.py"}},POSTMAN_FALLBACK={template:"api-e2e/postman.collection.json.ejs",filename:"postman.collection.json",framework:"newman",glob:"tests/api/**/*.collection.json"};function suiteForLanguage(language){return STACK_BY_LANGUAGE[language]??POSTMAN_FALLBACK}export function generateApiE2e(config,opts={dryRun:!1}){const base=config.targetDir,required=SERVICE_ARCHETYPES.has(config.archetype),suite=suiteForLanguage(config.language),files=[],manifestData={archetype:config.archetype,required,suiteDir:"tests/api",framework:suite.framework,glob:suite.glob,suiteCount:required?1:0};if(files.push(writeFile(resolvedPath(base,"api-e2e.json"),renderTemplate("api-e2e/manifest.json.ejs",manifestData),{skipIfExists:!0,dryRun:opts.dryRun})),!required)return{files};const data=config,suitePath=resolvedPath(base,"tests","api",suite.filename),suiteContent=renderTemplate(suite.template,data);files.push(writeFile(suitePath,suite.filename.endsWith(".ts")?formatContent(suiteContent,suitePath,base):suiteContent,{skipIfExists:!0,dryRun:opts.dryRun})),files.push(writeFile(resolvedPath(base,"tests","api","README.md"),renderTemplate("api-e2e/README.md.ejs",data),{skipIfExists:!0,dryRun:opts.dryRun}));const runShPath=resolvedPath(base,"tests","api","run.sh"),runShResult=writeFile(runShPath,renderTemplate("api-e2e/run.sh.ejs",data),{skipIfExists:!0,dryRun:opts.dryRun});if(files.push(runShResult),!opts.dryRun&&runShResult.action!=="skipped")try{chmodTranslated(runShPath,493)}catch(err){try{unlinkTranslated(runShPath)}catch{}throw new Error(`chmod tests/api/run.sh failed: ${err.message}. File removed.`,{cause:err})}return{files}}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{renderTemplate}from"../utils/render.js";import{writeFile,resolvedPath}from"../utils/fs.js";import{mutatePackageJson}from"../utils/pkg.js";import{formatContent}from"../utils/prettier-format.js";function injectExpressPackageJson(targetDir,dryRun){mutatePackageJson(targetDir,dryRun,pkg=>{const deps=pkg.dependencies??{},devDeps=pkg.devDependencies??{};let changed=!1;return deps.express||(deps.express="^5.1.0",pkg.dependencies=deps,changed=!0),devDeps["@types/express"]||(devDeps["@types/express"]="^5.0.3",pkg.devDependencies=devDeps,changed=!0),devDeps.supertest||(devDeps.supertest="^7.1.4",pkg.devDependencies=devDeps,changed=!0),devDeps["@types/supertest"]||(devDeps["@types/supertest"]="^6.0.3",pkg.devDependencies=devDeps,changed=!0),changed})}export function generateApiMiddleware(config,opts={dryRun:!1}){if(!config.hasPublicApi)return{files:[]};const results=[],base=config.targetDir,data=config;if(config.language==="typescript"||config.language==="multi"){injectExpressPackageJson(base,opts.dryRun);const middlewareFiles=[["deprecation.ts","middleware/deprecation.ts.ejs"],["410-gone-handler.ts","middleware/410-gone-handler.ts.ejs"],["error-handler.ts","middleware/error-handler.ts.ejs"],["correlation-id.ts","middleware/correlation-id.ts.ejs"],["payload-size-limit.ts","middleware/payload-size-limit.ts.ejs"]];for(const[filename,tmpl]of middlewareFiles){const path=resolvedPath(base,"src","middleware",filename);results.push(writeFile(path,formatContent(renderTemplate(tmpl,data),path,base),{skipIfExists:!0,dryRun:opts.dryRun}))}const contractTestPath=resolvedPath(base,"__tests__","contract","error-shape.contract.test.ts");results.push(writeFile(contractTestPath,formatContent(renderTemplate("__tests__/contract/error-shape.contract.test.ts.ejs",data),contractTestPath,base),{skipIfExists:!0,dryRun:opts.dryRun}))}if((config.language==="java"||config.language==="multi")&&config.basePackage){const pkgPath=config.basePackage.replace(/\./g,"/");results.push(writeFile(resolvedPath(base,"src","main","java",pkgPath,"web","interceptor","DeprecationInterceptor.java"),renderTemplate("java/DeprecationInterceptor.java.ejs",data),{skipIfExists:!0,dryRun:opts.dryRun}))}return{files:results}}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{renderTemplate}from"../utils/render.js";import{writeFile,resolvedPath}from"../utils/fs.js";import{injectGradleWiring,safeApplyFromSnippet}from"../utils/gradle.js";function emitHexagonalSuite(config,base,packagePath,data,dryRun){const files=[];for(const name of["DomainPurityTest.java","DependencyFlowTest.java","PortsIndependenceTest.java","TestCoverageArchTest.java","NamingConventionsTest.java","AntiCyclicTest.java","NoH2ArchTest.java"])files.push(writeFile(resolvedPath(base,"src","test","java",packagePath,name),renderTemplate(`archunit/${name}.ejs`,data),{skipIfExists:!0,dryRun}));if(config.governanceLevel!=="L1")for(const name of["AntiProformaTest.java","AntiProformaExempt.java"])files.push(writeFile(resolvedPath(base,"src","test","java",packagePath,name),renderTemplate(`archunit/${name}.ejs`,data),{skipIfExists:!0,dryRun}));return config.buildTool==="maven"&&files.push(writeFile(resolvedPath(base,"docs","arch-test-deps-maven.md"),renderTemplate("archunit/arch-test-deps-maven.md.ejs",data),{skipIfExists:!0,dryRun})),files}function isSpring(framework){return framework==="spring-boot"||typeof framework=="string"&&framework.includes("spring")}function emitRestAssuredScaffolding(config,base,packagePath,data,dryRun){if(!config.hasDatabase||!config.hasPublicApi||!config.basePackage||!isSpring(config.framework))return[];const supportPath=config.basePackage.replace(/\./g,"/")+"/support";return[writeFile(resolvedPath(base,"src","test","java",supportPath,"RestAssuredBaseIT.java"),renderTemplate("archunit/RestAssuredBaseIT.java.ejs",data),{skipIfExists:!0,dryRun}),writeFile(resolvedPath(base,"src","test","java",packagePath,"RestAssuredArchTest.java"),renderTemplate("archunit/RestAssuredArchTest.java.ejs",data),{skipIfExists:!0,dryRun})]}function emitHexagonalConsolidated(base,packagePath,data,dryRun){return writeFile(resolvedPath(base,"src","test","java",packagePath,"HexagonalArchTest.java"),renderTemplate("java/archunit-hexagonal.ejs",data),{skipIfExists:!0,dryRun})}const KNOWN_STYLES=new Set(["hexagonal","layered","modular-monolith","none"]);export function generateArchUnit(config,opts={dryRun:!1}){if(config.language!=="java"&&config.language!=="kotlin")return{files:[]};if(!KNOWN_STYLES.has(config.architectureStyle))throw new Error(`archunit: unknown architectureStyle '${config.architectureStyle}'. Choose: hexagonal, layered, modular-monolith, or none.`);const base=config.targetDir,data=config,packagePath=config.basePackage?config.basePackage.replace(/\./g,"/")+"/architecture":"architecture",files=[];return config.basePackage&&files.push(writeFile(resolvedPath(base,"src","test","java",packagePath,"NoMockMvcTest.java"),renderTemplate("archunit/NoMockMvcTest.java.ejs",data),{skipIfExists:!0,dryRun:opts.dryRun})),config.architectureStyle!=="none"&&config.basePackage&&files.push(writeFile(resolvedPath(base,"src","test","java",packagePath,"ArchitectureTest.java"),renderTemplate("archunit/ArchitectureTest.java.ejs",data),{skipIfExists:!0,dryRun:opts.dryRun})),config.architectureStyle==="hexagonal"&&config.basePackage&&files.push(...emitHexagonalSuite(config,base,packagePath,data,opts.dryRun)),config.architectureStyle==="hexagonal"&&config.governanceLevel!=="L1"&&config.basePackage&&files.push(emitHexagonalConsolidated(base,packagePath,data,opts.dryRun)),files.push(...emitRestAssuredScaffolding(config,base,packagePath,data,opts.dryRun)),files.push(...emitAndWireGradleArchDeps(config,files.length,data,opts.dryRun)),{files}}function emitAndWireGradleArchDeps(config,emittedTestCount,data,dryRun){if(config.buildTool!=="gradle"||emittedTestCount===0)return[];const base=config.targetDir,result=writeFile(resolvedPath(base,"gradle","arch-test-deps.gradle"),renderTemplate("archunit/arch-test-deps.gradle.ejs",data),{skipIfExists:!0,dryRun}),apply=safeApplyFromSnippet(base,"gradle/arch-test-deps.gradle",{rootBuildSignatures:[/com\.tngtech\.archunit:archunit-junit5/]});return apply&&injectGradleWiring(base,dryRun,{snippets:[apply]}),[result]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{renderTemplate}from"../utils/render.js";import{writeFile,resolvedPath}from"../utils/fs.js";export function generateAuditToolchain(config,opts={dryRun:!1}){const results=[],base=config.targetDir,scriptPath=resolvedPath(base,"scripts","audit-toolchain.mjs");return results.push(writeFile(scriptPath,renderTemplate("scripts/audit-toolchain.mjs.ejs",config),{skipIfExists:!0,dryRun:opts.dryRun})),{files:results}}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{renderTemplate}from"../utils/render.js";import{writeFile,resolvedPath}from"../utils/fs.js";export function generateAuth(config,opts={dryRun:!1}){return!config.auth||config.auth.provider==="none"?{files:[]}:{files:[writeFile(resolvedPath(config.targetDir,"docs","AUTH_SETUP.md"),renderTemplate("auth/setup.md.ejs",config),{skipIfExists:!0,dryRun:opts.dryRun})]}}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{readdirSync,statSync}from"node:fs";import{join}from"node:path";import{renderTemplate}from"../utils/render.js";import{writeFile,resolvedPath}from"../utils/fs.js";import{formatContent}from"../utils/prettier-format.js";import{injectGradleWiring}from"../utils/gradle.js";const JAVA_BDD_TEST_DEPS=[{coordinate:"org.assertj:assertj-core:3.26.3"},{coordinate:"org.junit.jupiter:junit-jupiter:5.10.2"},{coordinate:"org.junit.platform:junit-platform-suite:1.10.2"},{coordinate:"io.cucumber:cucumber-java:7.18.0"},{coordinate:"io.cucumber:cucumber-junit-platform-engine:7.18.0"},{coordinate:"org.junit.platform:junit-platform-launcher:1.10.2",configuration:"testRuntimeOnly"}];function hasRealFeatureFiles(featuresDir){let entries;try{entries=readdirSync(featuresDir)}catch{return!1}for(const entry of entries){const full=join(featuresDir,entry);let isDir;try{isDir=statSync(full).isDirectory()}catch{continue}if(isDir){if(hasRealFeatureFiles(full))return!0}else if(entry.endsWith(".feature")&&entry!=="example.feature")return!0}return!1}function emitJavaBdd(base,data,config,dryRun){const testPkg=config.basePackage?`src/test/java/${config.basePackage.replace(/\./g,"/")}/example`:"src/test/java/example",bddPkg=config.basePackage?`src/test/java/${config.basePackage.replace(/\./g,"/")}/bdd`:"src/test/java/com/example/bdd";return hasRealFeatureFiles(resolvedPath(base,"src","test","resources","features"))?[]:[writeFile(resolvedPath(base,testPkg,"ExampleBehavioralTest.java"),renderTemplate("behavioral-tests/ExampleBehavioralTest.java.ejs",data),{skipIfExists:!0,dryRun}),writeFile(resolvedPath(base,bddPkg,"ExampleBddIT.java"),renderTemplate("behavioral-tests/bdd/ExampleBddIT.java.ejs",data),{skipIfExists:!0,dryRun}),writeFile(resolvedPath(base,bddPkg,"ExampleSteps.java"),renderTemplate("behavioral-tests/bdd/ExampleSteps.java.ejs",data),{skipIfExists:!0,dryRun}),writeFile(resolvedPath(base,"src","test","resources","features","example.feature"),renderTemplate("behavioral-tests/bdd/example.feature.ejs",data),{skipIfExists:!0,dryRun})]}function emitTypeScriptBdd(base,data,dryRun){const behPath=resolvedPath(base,"src","test","example.behavioral.test.ts"),stepsPath=resolvedPath(base,"features","step_definitions","example.steps.ts");return hasRealFeatureFiles(resolvedPath(base,"features"))?[]:[writeFile(behPath,formatContent(renderTemplate("behavioral-tests/example.behavioral.test.ts.ejs",data),behPath,base),{skipIfExists:!0,dryRun}),writeFile(stepsPath,formatContent(renderTemplate("behavioral-tests/bdd/example.steps.ts.ejs",data),stepsPath,base),{skipIfExists:!0,dryRun}),writeFile(resolvedPath(base,"features","example.feature"),renderTemplate("behavioral-tests/bdd/example.feature.ejs",data),{skipIfExists:!0,dryRun})]}function emitRustBdd(base,data,dryRun){return hasRealFeatureFiles(resolvedPath(base,"tests","features"))?[]:[writeFile(resolvedPath(base,"tests","example_behavioral_test.rs"),renderTemplate("behavioral-tests/example_behavioral_test.rs.ejs",data),{skipIfExists:!0,dryRun}),writeFile(resolvedPath(base,"tests","example_bdd_test.rs"),renderTemplate("behavioral-tests/bdd/example_bdd_test.rs.ejs",data),{skipIfExists:!0,dryRun}),writeFile(resolvedPath(base,"tests","features","example.feature"),renderTemplate("behavioral-tests/bdd/example.feature.ejs",data),{skipIfExists:!0,dryRun})]}function emitGoBdd(base,data,dryRun){return hasRealFeatureFiles(resolvedPath(base,"features"))?[]:[writeFile(resolvedPath(base,"internal","example_behavioral_test.go"),renderTemplate("behavioral-tests/example_behavioral_test.go.ejs",data),{skipIfExists:!0,dryRun}),writeFile(resolvedPath(base,"internal","bdd","example_test.go"),renderTemplate("behavioral-tests/bdd/example_test.go.ejs",data),{skipIfExists:!0,dryRun}),writeFile(resolvedPath(base,"features","example.feature"),renderTemplate("behavioral-tests/bdd/example.feature.ejs",data),{skipIfExists:!0,dryRun})]}function emitPythonBdd(base,data,dryRun){return hasRealFeatureFiles(resolvedPath(base,"tests","bdd","features"))?[]:[writeFile(resolvedPath(base,"tests","test_example_behavioral.py"),renderTemplate("behavioral-tests/test_example_behavioral.py.ejs",data),{skipIfExists:!0,dryRun}),writeFile(resolvedPath(base,"tests","bdd","test_example_bdd.py"),renderTemplate("behavioral-tests/bdd/test_example_bdd.py.ejs",data),{skipIfExists:!0,dryRun}),writeFile(resolvedPath(base,"tests","bdd","features","example.feature"),renderTemplate("behavioral-tests/bdd/example.feature.ejs",data),{skipIfExists:!0,dryRun})]}export function generateBehavioralTests(config,opts={dryRun:!1}){const base=config.targetDir,data=config,results=[];if(config.language==="java"||config.language==="multi"){const javaFiles=emitJavaBdd(base,data,config,opts.dryRun);results.push(...javaFiles),javaFiles.length>0&&config.buildTool==="gradle"&&injectGradleWiring(base,opts.dryRun,{dependencies:JAVA_BDD_TEST_DEPS})}return(config.language==="typescript"||config.language==="multi")&&results.push(...emitTypeScriptBdd(base,data,opts.dryRun)),config.language==="rust"&&results.push(...emitRustBdd(base,data,opts.dryRun)),config.language==="go"&&results.push(...emitGoBdd(base,data,opts.dryRun)),config.language==="python"&&results.push(...emitPythonBdd(base,data,opts.dryRun)),results.push(writeFile(resolvedPath(base,"docs","TESTING_POLICY.md"),renderTemplate("behavioral-tests/TESTING_POLICY.md.ejs",data),{skipIfExists:!0,dryRun:opts.dryRun}),writeFile(resolvedPath(base,"scripts","check-test-naming.mjs"),renderTemplate("scripts/check-test-naming.mjs.ejs",data),{skipIfExists:!1,dryRun:opts.dryRun})),config.archetype==="frontend-spa"&&(config.language==="typescript"||config.language==="multi")&&results.push(writeFile(resolvedPath(base,".eslintrc-playwright.json"),renderTemplate("behavioral-tests/eslint-playwright.json.ejs",data),{skipIfExists:!0,dryRun:opts.dryRun}),writeFile(resolvedPath(base,"eslint.config.playwright.mjs"),renderTemplate("behavioral-tests/eslint.config.playwright.mjs.ejs",data),{skipIfExists:!0,dryRun:opts.dryRun})),{files:results}}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{renderTemplate}from"../utils/render.js";import{writeFile,resolvedPath}from"../utils/fs.js";import{injectDevDependency}from"../utils/pkg.js";export function generateEslintBoundaries(config,opts={dryRun:!1}){if(config.language!=="typescript"&&config.language!=="multi")return{files:[]};const base=config.targetDir,data=config;return config.archetype==="frontend-spa"?(injectDevDependency(base,"eslint-plugin-boundaries","^7.0.2",opts.dryRun),{files:[writeFile(resolvedPath(base,".eslintrc-frontend-spa.cjs"),renderTemplate("boundaries/.eslintrc-frontend-spa.cjs.ejs",data),{skipIfExists:!0,dryRun:opts.dryRun}),writeFile(resolvedPath(base,"eslint.config.frontend-spa.mjs"),renderTemplate("boundaries/eslint.config.frontend-spa.mjs.ejs",data),{skipIfExists:!0,dryRun:opts.dryRun})]}):config.architectureStyle!=="hexagonal"?{files:[]}:(injectDevDependency(base,"eslint-plugin-boundaries","^7.0.2",opts.dryRun),{files:[writeFile(resolvedPath(base,".eslintrc-boundaries.cjs"),renderTemplate("boundaries/.eslintrc-boundaries.cjs.ejs",data),{skipIfExists:!0,dryRun:opts.dryRun}),writeFile(resolvedPath(base,"eslint.config.boundaries.mjs"),renderTemplate("boundaries/eslint.config.boundaries.mjs.ejs",data),{skipIfExists:!0,dryRun:opts.dryRun}),writeFile(resolvedPath(base,"scripts/check-boundaries.mjs"),renderTemplate("boundaries/check-boundaries.mjs.ejs",data),{skipIfExists:!0,dryRun:opts.dryRun})]})}
|