@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,1015 @@
|
|
|
1
|
+
// debt-lib.mjs — generated by arbiter
|
|
2
|
+
// Shared metric collection helpers used by capture-debt-baseline.mjs and debt-report.mjs.
|
|
3
|
+
// Re-generate with: arbiter update
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
import {
|
|
6
|
+
readFileSync, existsSync, readdirSync, rmSync, mkdirSync, chmodSync, renameSync, statSync,
|
|
7
|
+
} from 'node:fs';
|
|
8
|
+
import { createRequire } from 'node:module';
|
|
9
|
+
import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
10
|
+
import { walkRepo } from './lib/glob-walk.mjs';
|
|
11
|
+
|
|
12
|
+
// The `eslint <paths> --format json` output for this repo exceeds the Node
|
|
13
|
+
// spawnSync default maxBuffer (1 MiB); without a raised ceiling the child aborts
|
|
14
|
+
// with ENOBUFS, stdout is silently truncated, the JSON fails to parse, and the
|
|
15
|
+
// metric collapses to 0 (the dormant complexityViolations ratchet root cause,
|
|
16
|
+
// #1542). 64 MiB comfortably covers the full-repo eslint/knip JSON reports.
|
|
17
|
+
const RUN_MAX_BUFFER = 64 * 1024 * 1024;
|
|
18
|
+
|
|
19
|
+
function run(cmd, args, opts) {
|
|
20
|
+
return spawnSync(cmd, args, {
|
|
21
|
+
encoding: 'utf-8',
|
|
22
|
+
shell: false,
|
|
23
|
+
maxBuffer: RUN_MAX_BUFFER,
|
|
24
|
+
...opts,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// jscpd v5 ships its scanner as a prebuilt native `cpd` binary selected per
|
|
29
|
+
// platform by `jscpd/run-jscpd.js` (#1304). On glibc Linux it installs the
|
|
30
|
+
// `jscpd-linux-x64-gnu` package, but that binary is built against GLIBC ≥ 2.34;
|
|
31
|
+
// on an older CI image (glibc < 2.32) the loader rejects it — exit 1, no report
|
|
32
|
+
// — so the duplication scan failed closed even though jscpd "is installed". The
|
|
33
|
+
// musl x64 platform package is a *static-pie* ELF with no libc dependency, so
|
|
34
|
+
// it runs on any Linux regardless of glibc age. npm refuses to install a
|
|
35
|
+
// `libc:["musl"]` optional dep on a glibc host, so we materialise it on demand
|
|
36
|
+
// from the version-pinned registry into a local cache and run it directly.
|
|
37
|
+
// jscpd renamed its platform packages once already (`cpd-linux-x64-musl` ->
|
|
38
|
+
// `jscpd-linux-x64-musl` in 5.0.16, #2370), so the package name is derived from
|
|
39
|
+
// jscpd's own optionalDependencies rather than hardcoded.
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The optionalDependencies key jscpd declares for the musl x64 Linux platform
|
|
43
|
+
* package. Derived instead of hardcoded because jscpd has already renamed this
|
|
44
|
+
* package once (#2370).
|
|
45
|
+
* @param {{optionalDependencies?: Record<string, string>}} jscpdPkgJson - parsed jscpd package.json
|
|
46
|
+
* @returns {string | null}
|
|
47
|
+
*/
|
|
48
|
+
export function deriveStaticMuslPkgName(jscpdPkgJson) {
|
|
49
|
+
const optionalDeps = jscpdPkgJson?.optionalDependencies ?? {};
|
|
50
|
+
return Object.keys(optionalDeps).find((name) => name.endsWith('-linux-x64-musl')) ?? null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Materialise jscpd's static (musl, libc-independent) `cpd` binary into a local
|
|
55
|
+
* cache and return its path, or null if unavailable (no network / not Linux-x64
|
|
56
|
+
* / npm absent). Idempotent: a cached binary is reused. The version is pinned to
|
|
57
|
+
* the installed jscpd so the static binary always matches the gnu one (#1304).
|
|
58
|
+
* @param {string} jscpdPkgDir - dir of the resolved jscpd package (has version)
|
|
59
|
+
* @returns {string | null}
|
|
60
|
+
*/
|
|
61
|
+
function resolveStaticCpd(jscpdPkgDir) {
|
|
62
|
+
if (process.platform !== 'linux' || process.arch !== 'x64') return null;
|
|
63
|
+
let pkgJson;
|
|
64
|
+
try {
|
|
65
|
+
pkgJson = JSON.parse(readFileSync(join(jscpdPkgDir, 'package.json'), 'utf-8'));
|
|
66
|
+
} catch { return null; }
|
|
67
|
+
const version = pkgJson.version;
|
|
68
|
+
const staticPkgName = deriveStaticMuslPkgName(pkgJson);
|
|
69
|
+
if (!version || !staticPkgName) return null;
|
|
70
|
+
const cacheRoot = resolve(jscpdPkgDir, '..', '.cache', 'jscpd-static', version);
|
|
71
|
+
const cpdPath = join(cacheRoot, 'cpd');
|
|
72
|
+
if (existsSync(cpdPath)) return cpdPath;
|
|
73
|
+
// Fetch the version-pinned static package tarball and extract its binary.
|
|
74
|
+
let tmp;
|
|
75
|
+
try {
|
|
76
|
+
mkdirSync(cacheRoot, { recursive: true });
|
|
77
|
+
tmp = join(cacheRoot, '.pack');
|
|
78
|
+
mkdirSync(tmp, { recursive: true });
|
|
79
|
+
const pack = run('npm', ['pack', `${staticPkgName}@${version}`, '--silent'], { cwd: tmp });
|
|
80
|
+
if ((pack.status ?? 1) !== 0) return null;
|
|
81
|
+
const tgz = readdirSync(tmp).find((f) => f.endsWith('.tgz'));
|
|
82
|
+
if (!tgz) return null;
|
|
83
|
+
const untar = run('tar', ['xzf', tgz, '-C', tmp], { cwd: tmp });
|
|
84
|
+
if ((untar.status ?? 1) !== 0) return null;
|
|
85
|
+
// jscpd 5.0.16 ships the binary at package/bin/jscpd (was
|
|
86
|
+
// package/cpd-bin/cpd pre-rename, #2370). Try both so a consumer project
|
|
87
|
+
// pinned to an older jscpd (whose static package still uses the old
|
|
88
|
+
// layout) still resolves.
|
|
89
|
+
const extracted = [
|
|
90
|
+
join(tmp, 'package', 'bin', 'jscpd'),
|
|
91
|
+
join(tmp, 'package', 'cpd-bin', 'cpd'),
|
|
92
|
+
].find((p) => existsSync(p));
|
|
93
|
+
if (!extracted) return null;
|
|
94
|
+
renameSync(extracted, cpdPath);
|
|
95
|
+
chmodSync(cpdPath, 0o755);
|
|
96
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
97
|
+
return cpdPath;
|
|
98
|
+
} catch {
|
|
99
|
+
if (tmp) rmSync(tmp, { recursive: true, force: true });
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* True when a jscpd spawn result is the glibc-too-old failure: the native gnu
|
|
106
|
+
* `cpd` binary loaded but the dynamic linker could not satisfy its GLIBC version
|
|
107
|
+
* requirement (exit 1, empty report, `GLIBC_x.yy not found` on stderr) (#1304).
|
|
108
|
+
* @param {{status: number|null, stderr?: string}} r
|
|
109
|
+
* @returns {boolean}
|
|
110
|
+
*/
|
|
111
|
+
export function isGlibcIncompatible(r) {
|
|
112
|
+
return (r?.status ?? 0) !== 0 && /GLIBC_[\d.]+'? not found/.test(r?.stderr ?? '');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Resolve the default jscpd spawn (#1304). jscpd v5 runs a prebuilt native `cpd`
|
|
117
|
+
* binary chosen per platform by `jscpd/run-jscpd.js`. This resolves the installed
|
|
118
|
+
* jscpd launcher and runs it directly with the current node. On the docker-ci-build
|
|
119
|
+
* runner the glibc `cpd-linux-x64-gnu` binary is built against GLIBC ≥ 2.34 but the
|
|
120
|
+
* image's glibc is < 2.32, so the loader rejects it (exit 1, no report) and the scan
|
|
121
|
+
* failed closed even though jscpd is installed. On that glibc-too-old failure the
|
|
122
|
+
* spawn retries once with the static (musl, libc-independent) `cpd` — materialised
|
|
123
|
+
* on demand by resolveStaticCpd — so the scan actually runs and writes a report.
|
|
124
|
+
* If jscpd cannot be resolved at all (greenfield/baseline target without the
|
|
125
|
+
* devDependency) it falls back to the `npx --no-install jscpd` skip-spawn, whose
|
|
126
|
+
* missing-packages result drives the tool-not-installed (`{skipped:true}`) contract.
|
|
127
|
+
*
|
|
128
|
+
* The returned spawn accepts the SAME argv the caller built for the npx path
|
|
129
|
+
* (`['--no-install','jscpd',...paths,'--silent']`); the direct-binary path strips
|
|
130
|
+
* the npx-only `--no-install`/`jscpd` prefix tokens, the fallback passes them
|
|
131
|
+
* through. `resolveBin`, `resolveStatic`, and the per-call `spawnFn` are injectable
|
|
132
|
+
* so tests never shell to a real npx or native binary.
|
|
133
|
+
* @param {string} cwd
|
|
134
|
+
* @param {{resolveBin?: (cwd: string) => string, resolveStatic?: () => string | null}} [opts]
|
|
135
|
+
* @returns {(dir: string, args: string[], callOpts?: {spawnFn?: (cmd: string, args: string[], o: object) => {status: number|null, stdout: string, stderr: string}}) => ({status: number|null, stdout: string, stderr: string} | null)}
|
|
136
|
+
*/
|
|
137
|
+
export function resolveJscpdSpawn(cwd, opts = {}) {
|
|
138
|
+
const resolveBin =
|
|
139
|
+
opts.resolveBin ??
|
|
140
|
+
((dir) => {
|
|
141
|
+
// Resolve from the scanned project first (its node_modules), then fall
|
|
142
|
+
// back to this module's own require context — the self repo installs jscpd
|
|
143
|
+
// as a devDependency reachable from here even when the scan dir has no
|
|
144
|
+
// local node_modules (e.g. a temp fixture or a nested workspace).
|
|
145
|
+
let pkgPath;
|
|
146
|
+
try {
|
|
147
|
+
pkgPath = createRequire(join(resolve(dir), 'package.json')).resolve('jscpd/package.json');
|
|
148
|
+
} catch {
|
|
149
|
+
pkgPath = createRequire(import.meta.url).resolve('jscpd/package.json');
|
|
150
|
+
}
|
|
151
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
152
|
+
const binEntry = typeof pkg.bin === 'string' ? pkg.bin : pkg.bin?.jscpd;
|
|
153
|
+
if (!binEntry) throw new Error('jscpd package.json has no bin.jscpd entry');
|
|
154
|
+
return join(dirname(pkgPath), binEntry);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
let binPath;
|
|
158
|
+
try {
|
|
159
|
+
binPath = resolveBin(cwd);
|
|
160
|
+
} catch {
|
|
161
|
+
// jscpd not installed/resolvable — fall back to npx --no-install jscpd, whose
|
|
162
|
+
// ENOENT/"missing packages" result drives jscpdScan's skip contract.
|
|
163
|
+
return (dir, args, callOpts = {}) => {
|
|
164
|
+
const spawnFn = callOpts.spawnFn;
|
|
165
|
+
if (spawnFn) return spawnFn('npx', args, { cwd: dir });
|
|
166
|
+
const r = spawnOrSkip('duplicationPercentage', 'jscpd', 'npx', args, { cwd: dir });
|
|
167
|
+
return r === null ? null : r;
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const jscpdPkgDir = dirname(binPath);
|
|
172
|
+
const resolveStatic = opts.resolveStatic ?? (() => resolveStaticCpd(jscpdPkgDir));
|
|
173
|
+
// Direct binary: drop the npx-only prefix tokens so jscpd sees only its own
|
|
174
|
+
// positional args (paths + flags). The launcher (run-jscpd.js) selects the
|
|
175
|
+
// platform-native `cpd`; if that binary is glibc-incompatible on this runner
|
|
176
|
+
// (#1304) retry once with the static (musl, libc-independent) `cpd` so the
|
|
177
|
+
// scan ACTUALLY runs and writes a report — fail-closed stays intact when even
|
|
178
|
+
// the static binary is unavailable (the error path is unchanged).
|
|
179
|
+
return (dir, args, callOpts = {}) => {
|
|
180
|
+
const jscpdArgs = args.filter((a) => a !== '--no-install' && a !== 'jscpd');
|
|
181
|
+
const spawnFn = callOpts.spawnFn ?? ((cmd, a, o) => run(cmd, a, o));
|
|
182
|
+
const r = spawnFn(process.execPath, [binPath, ...jscpdArgs], { cwd: dir });
|
|
183
|
+
if (!isGlibcIncompatible(r)) return r;
|
|
184
|
+
const staticCpd = resolveStatic();
|
|
185
|
+
if (!staticCpd) return r;
|
|
186
|
+
return spawnFn(staticCpd, jscpdArgs, { cwd: dir });
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Run a command. If the binary is not installed (ENOENT), log a warning to
|
|
192
|
+
* stderr and return null. Callers skip null-result metrics entirely.
|
|
193
|
+
* @param {string} name - metric key for the warning message
|
|
194
|
+
* @param {string} tool - human name of the missing tool
|
|
195
|
+
* @param {string} cmd - executable
|
|
196
|
+
* @param {string[]} args
|
|
197
|
+
* @param {object} [opts]
|
|
198
|
+
* @returns {import('child_process').SpawnSyncReturns<string> | null}
|
|
199
|
+
*/
|
|
200
|
+
/**
|
|
201
|
+
* Whether npx declined to run because its requested package is unavailable.
|
|
202
|
+
* This is the npx equivalent of an ENOENT spawn failure: no measurement was
|
|
203
|
+
* attempted, so callers must omit the metric rather than fabricate a value or
|
|
204
|
+
* report a failed collection.
|
|
205
|
+
* @param {import('child_process').SpawnSyncReturns<string>} result
|
|
206
|
+
* @returns {boolean}
|
|
207
|
+
*/
|
|
208
|
+
export function isNpxMissingPackages(result) {
|
|
209
|
+
return (result.status ?? 0) !== 0 && /npx canceled due to missing packages/.test(result.stderr ?? '');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function spawnOrSkip(name, tool, cmd, args, opts) {
|
|
213
|
+
const r = run(cmd, args, opts ?? {});
|
|
214
|
+
if (r.error?.code === 'ENOENT' || isNpxMissingPackages(r)) {
|
|
215
|
+
process.stderr.write(`[baseline] skip ${name} — ${tool} not installed\n`);
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
return r;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Count TO-DO markers across common source file extensions in cwd.
|
|
223
|
+
* Uses walkRepo (glob-walk.mjs) instead of raw `grep -r .` so nested worktree
|
|
224
|
+
* checkouts (e.g. `.claude/worktrees/**`) are never double-counted (#1734/#1752).
|
|
225
|
+
* @param {string} cwd
|
|
226
|
+
* @returns {number}
|
|
227
|
+
*/
|
|
228
|
+
export function countTodos(cwd) {
|
|
229
|
+
const MARKER_EXTENSIONS = new Set(['.js', '.ts', '.java', '.go', '.py', '.rs']);
|
|
230
|
+
const EXTRA_SKIP_SEGMENTS = new Set(['target', '.arbiter', '.temp']);
|
|
231
|
+
// arbiter-suppress(INV-21, until=2099-01-01, reason="self-referential marker regex inside the TODO-counting collector itself, not an unresolved TODO", owner=@luca)
|
|
232
|
+
const MARKER_RE = /\bTODO\b/;
|
|
233
|
+
let count = 0;
|
|
234
|
+
for (const rel of walkRepo(cwd)) {
|
|
235
|
+
const dot = rel.lastIndexOf('.');
|
|
236
|
+
if (dot === -1 || !MARKER_EXTENSIONS.has(rel.slice(dot))) continue;
|
|
237
|
+
if (rel.split('/').some((seg) => EXTRA_SKIP_SEGMENTS.has(seg))) continue;
|
|
238
|
+
try {
|
|
239
|
+
const lines = readFileSync(resolve(cwd, rel), 'utf-8').split('\n');
|
|
240
|
+
count += lines.filter((l) => MARKER_RE.test(l)).length;
|
|
241
|
+
} catch { /* skip unreadable files */ }
|
|
242
|
+
}
|
|
243
|
+
return count;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Return the short git commit hash for HEAD, or 'unknown'.
|
|
248
|
+
* @param {string} cwd
|
|
249
|
+
* @returns {string}
|
|
250
|
+
*/
|
|
251
|
+
export function getCommit(cwd) {
|
|
252
|
+
const r = run('git', ['rev-parse', '--short', 'HEAD'], { cwd });
|
|
253
|
+
return r.status === 0 ? r.stdout.trim() : 'unknown';
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ── Finding-hygiene metrics (#1405) ──────────────────────────────────────────
|
|
257
|
+
// The incidental-findings spool (`.arbiter/findings/*.jsonl`, #1401) is the SSOT
|
|
258
|
+
// for un-promoted findings. These metrics make the debt ratchet reward DRAINING
|
|
259
|
+
// the spool (lower-is-better) and never let merely FILING findings inflate a
|
|
260
|
+
// green signal (anti-gaming, INV-114). Spool ABSENT → both metrics are OMITTED
|
|
261
|
+
// (treated as not-applicable / "missing tool"), NEVER recorded as a regression.
|
|
262
|
+
|
|
263
|
+
/** Relative path of the per-shard findings spool directory (#1401). */
|
|
264
|
+
const FINDINGS_DIR = '.arbiter/findings';
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Read the findings spool and return open (un-promoted) finding metrics.
|
|
268
|
+
* Returns `{}` when the spool directory is absent (metric omitted = NA, never a
|
|
269
|
+
* regression). When present:
|
|
270
|
+
* - openFindingsCount — distinct fingerprints across all shards (dedup-safe)
|
|
271
|
+
* - unpromotedFindingsAge — age in whole days of the OLDEST open finding (0 when drained)
|
|
272
|
+
* Malformed JSONL lines are skipped; a missing/empty spool yields count 0, age 0.
|
|
273
|
+
* @param {string} cwd
|
|
274
|
+
* @returns {Record<string, {value: number, unit: string, direction: string}>}
|
|
275
|
+
*/
|
|
276
|
+
export function collectFindingsMetrics(cwd) {
|
|
277
|
+
const findingsDir = resolve(cwd, FINDINGS_DIR);
|
|
278
|
+
if (!existsSync(findingsDir)) return {};
|
|
279
|
+
let shards;
|
|
280
|
+
try {
|
|
281
|
+
shards = readdirSync(findingsDir).filter((f) => f.endsWith('.jsonl'));
|
|
282
|
+
} catch {
|
|
283
|
+
return {};
|
|
284
|
+
}
|
|
285
|
+
const fingerprints = new Set();
|
|
286
|
+
let oldestTs = null;
|
|
287
|
+
for (const shard of shards) {
|
|
288
|
+
let text;
|
|
289
|
+
try {
|
|
290
|
+
text = readFileSync(join(findingsDir, shard), 'utf-8');
|
|
291
|
+
} catch {
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
for (const line of text.split('\n')) {
|
|
295
|
+
const trimmed = line.trim();
|
|
296
|
+
if (trimmed.length === 0) continue;
|
|
297
|
+
let entry;
|
|
298
|
+
try {
|
|
299
|
+
entry = JSON.parse(trimmed);
|
|
300
|
+
} catch {
|
|
301
|
+
continue; // malformed line — resilient skip
|
|
302
|
+
}
|
|
303
|
+
if (entry === null || typeof entry !== 'object') continue;
|
|
304
|
+
const fp = typeof entry.fingerprint === 'string' ? entry.fingerprint : trimmed;
|
|
305
|
+
fingerprints.add(fp);
|
|
306
|
+
const ts = typeof entry.ts === 'string' ? Date.parse(entry.ts) : NaN;
|
|
307
|
+
if (!Number.isNaN(ts) && (oldestTs === null || ts < oldestTs)) oldestTs = ts;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
const ageDays = oldestTs === null ? 0 : Math.floor((Date.now() - oldestTs) / (24 * 60 * 60 * 1000));
|
|
311
|
+
return {
|
|
312
|
+
openFindingsCount: { value: fingerprints.size, unit: 'count', direction: 'lower-is-better' },
|
|
313
|
+
unpromotedFindingsAge: { value: ageDays < 0 ? 0 : ageDays, unit: 'days', direction: 'lower-is-better' },
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Baseline key-drop guard (#1286): a recapture must never silently delete a
|
|
319
|
+
* metric that the prior baseline tracked (e.g. a jscpd hiccup during capture
|
|
320
|
+
* would otherwise remove duplicationPercentage and disable the ratchet).
|
|
321
|
+
* New keys are always allowed.
|
|
322
|
+
* @param {Record<string, unknown>} priorMetrics
|
|
323
|
+
* @param {Record<string, unknown>} nextMetrics
|
|
324
|
+
*/
|
|
325
|
+
export function assertKeyParity(priorMetrics, nextMetrics) {
|
|
326
|
+
const dropped = Object.keys(priorMetrics ?? {}).filter((k) => !(k in (nextMetrics ?? {})));
|
|
327
|
+
if (dropped.length > 0) {
|
|
328
|
+
throw new Error(
|
|
329
|
+
`baseline recapture would drop metric(s): ${dropped.join(', ')} — ` +
|
|
330
|
+
'refusing to write (fail-closed). Fix the collector or remove the key deliberately.',
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// ─── Per-stack metric collectors ─────────────────────────────────────────────
|
|
336
|
+
// Each language block exports a single `collectMetrics(cwd)` function.
|
|
337
|
+
// EJS conditionals select the correct block at generation time.
|
|
338
|
+
|
|
339
|
+
<% if (language === 'typescript') { %>
|
|
340
|
+
/**
|
|
341
|
+
* Fail-closed jscpd v5 scan (#1286). jscpd v5 silently ignores the config
|
|
342
|
+
* `pattern`/`path` keys and exits 0 on a 0-file scan, writing a 0% report —
|
|
343
|
+
* so the fileset MUST be passed as positional CLI args. `.jscpd.json#path`
|
|
344
|
+
* is the script-private SSOT for that fileset (jscpd itself disregards the
|
|
345
|
+
* key; this helper and check-duplication.mjs read it).
|
|
346
|
+
* A scan that ran but covered 0 sources is an ERROR, never a recorded 0%.
|
|
347
|
+
* @param {string} cwd
|
|
348
|
+
* @param {{spawn?: (cwd: string, args: string[]) => {status: number|null, stdout: string, stderr: string}}} [opts]
|
|
349
|
+
* spawn is injectable for tests; production default shells `npx --no-install jscpd`.
|
|
350
|
+
* @returns {{error?: string, skipped?: boolean, legacyNoReport?: boolean, status?: number, sources?: number, percentage?: number}}
|
|
351
|
+
*/
|
|
352
|
+
export function jscpdScan(cwd, opts = {}) {
|
|
353
|
+
const configPath = resolve(cwd, '.jscpd.json');
|
|
354
|
+
let cfg;
|
|
355
|
+
try {
|
|
356
|
+
cfg = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
357
|
+
} catch (err) {
|
|
358
|
+
return { error: `.jscpd.json unreadable at ${configPath}: ${err.message}` };
|
|
359
|
+
}
|
|
360
|
+
const paths = Array.isArray(cfg.path) ? cfg.path.filter((p) => typeof p === 'string' && p) : [];
|
|
361
|
+
if (paths.length === 0) {
|
|
362
|
+
return {
|
|
363
|
+
error: cfg.pattern
|
|
364
|
+
? 'legacy v4 .jscpd.json: `pattern` is silently ignored by jscpd v5 — ' +
|
|
365
|
+
'replace it with a "path" array (the scan fileset, passed as positional args). See #1286.'
|
|
366
|
+
: '.jscpd.json has no "path" array — required as the jscpd v5 fileset SSOT (#1286).',
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
// Without the json reporter, v5 exits 0 writing no report — indistinguishable
|
|
370
|
+
// from the v4 zero-clones case, so exit-0-plus-no-report could no longer be
|
|
371
|
+
// trusted as a v4 signal. Reject the config up front (fail-closed, #1286).
|
|
372
|
+
if (!Array.isArray(cfg.reporters) || !cfg.reporters.includes('json')) {
|
|
373
|
+
return { error: '.jscpd.json "reporters" must include "json" — the scan is parsed from report/jscpd-report.json (#1286).' };
|
|
374
|
+
}
|
|
375
|
+
// Honor a custom `output` dir; default matches jscpd's `report/`.
|
|
376
|
+
const reportPath = resolve(cwd, typeof cfg.output === 'string' ? cfg.output : 'report', 'jscpd-report.json');
|
|
377
|
+
// Stale-report poisoning guard: a leftover report from a previous run must
|
|
378
|
+
// never be parsed as if it were produced by this invocation.
|
|
379
|
+
rmSync(reportPath, { force: true });
|
|
380
|
+
const spawn = opts.spawn ?? resolveJscpdSpawn(cwd);
|
|
381
|
+
const r = spawn(cwd, ['--no-install', 'jscpd', ...paths, '--silent']);
|
|
382
|
+
if (r === null) return { skipped: true };
|
|
383
|
+
// `npx --no-install` exits 1 (not ENOENT) when the package is absent — that is
|
|
384
|
+
// the tool-not-installed case (greenfield target without jscpd), same contract
|
|
385
|
+
// as spawnOrSkip: skip the collector, never a failed-scan error.
|
|
386
|
+
if (isNpxMissingPackages(r)) {
|
|
387
|
+
process.stderr.write('[baseline] skip duplicationPercentage — jscpd not installed\n');
|
|
388
|
+
return { skipped: true };
|
|
389
|
+
}
|
|
390
|
+
if (!existsSync(reportPath)) {
|
|
391
|
+
if ((r.status ?? 0) === 0) {
|
|
392
|
+
// jscpd v4 writes the json report only when clones exist; v5 always
|
|
393
|
+
// writes it. Exit 0 + no report can only be a v4 binary (brownfield
|
|
394
|
+
// project not yet bumped) with zero clones.
|
|
395
|
+
return { legacyNoReport: true, status: 0 };
|
|
396
|
+
}
|
|
397
|
+
// Surface jscpd's own stderr so the cause is visible in the gate output
|
|
398
|
+
// (e.g. a glibc-too-old runner image, #1304) instead of an opaque exit code.
|
|
399
|
+
const why = (r.stderr ?? '').trim().split('\n').slice(0, 3).join(' | ');
|
|
400
|
+
return {
|
|
401
|
+
error:
|
|
402
|
+
`jscpd failed (exit ${r.status}) and wrote no report — fail-closed (#1286)` +
|
|
403
|
+
(why ? `: ${why}` : ''),
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
let dup;
|
|
407
|
+
try {
|
|
408
|
+
dup = JSON.parse(readFileSync(reportPath, 'utf-8'));
|
|
409
|
+
} catch (err) {
|
|
410
|
+
return { error: `jscpd report unreadable: ${err.message}` };
|
|
411
|
+
}
|
|
412
|
+
const total = dup.statistics?.total ?? {};
|
|
413
|
+
const sources = total.sources ?? 0;
|
|
414
|
+
if (sources === 0) {
|
|
415
|
+
return {
|
|
416
|
+
error:
|
|
417
|
+
'jscpd scanned 0 source files — fileset/config drift; refusing to record 0% ' +
|
|
418
|
+
'(fail-closed, CANON-22 / #1286). Check .jscpd.json "path" entries exist.',
|
|
419
|
+
status: r.status ?? 0,
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
if (typeof total.percentage !== 'number') {
|
|
423
|
+
return {
|
|
424
|
+
error:
|
|
425
|
+
'jscpd report has no numeric statistics.total.percentage — schema drift; refusing to record 0% (#1286).',
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
return { status: r.status ?? 0, sources, percentage: total.percentage };
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Enriched vitest-coverage failure diagnostic (#2226). The bare ENOENT named no
|
|
433
|
+
* cause: vitest exits 1 for many reasons (a failing test, a missing provider, a
|
|
434
|
+
* broken reporter) and writes no json-summary for any of them. This names the
|
|
435
|
+
* cause from the run's OWN output:
|
|
436
|
+
* - stdout (vitest prints its version on the RUN line and the Failed Tests
|
|
437
|
+
* summary at the end) — the stderr-only tail misled wave-1, it can be a
|
|
438
|
+
* PASSING test's incidental stderr;
|
|
439
|
+
* - whether .coverage-tmp/ was created at all (stale-guard vs never-ran);
|
|
440
|
+
* - exit status/signal.
|
|
441
|
+
* @param {unknown} err - error thrown while reading/parsing the summary
|
|
442
|
+
* @param {{status: number|null, signal?: string, stdout?: string, stderr?: string}} raw
|
|
443
|
+
* the spawned vitest result
|
|
444
|
+
* @param {string} cwd - project root (probes .coverage-tmp/)
|
|
445
|
+
* @returns {string}
|
|
446
|
+
*/
|
|
447
|
+
// eslint-disable-next-line no-control-regex -- strips ANSI SGR codes (\x1b[...m)
|
|
448
|
+
const ANSI_SGR = /\x1b\[[0-9;]*m/g;
|
|
449
|
+
|
|
450
|
+
function coverageFailureReason(err, raw, cwd) {
|
|
451
|
+
const clean = (s = '') => s.replace(ANSI_SGR, '');
|
|
452
|
+
const lines = (s) => clean(s).split('\n').filter(Boolean);
|
|
453
|
+
const stdoutTail = lines(raw.stdout).slice(-3).join(' | ');
|
|
454
|
+
const stderrTail = lines(raw.stderr).slice(-3).join(' | ');
|
|
455
|
+
const outcome = [
|
|
456
|
+
raw.status !== null ? `exit ${raw.status}` : '',
|
|
457
|
+
raw.signal ? `signal ${raw.signal}` : '',
|
|
458
|
+
].filter(Boolean).join(', ');
|
|
459
|
+
const version =
|
|
460
|
+
lines(raw.stdout).find((l) => /^RUN\s+v\d+\.\d+\.\d+/.test(l))?.trim() ?? 'vitest version unknown';
|
|
461
|
+
const tmpDir = resolve(cwd, '.coverage-tmp');
|
|
462
|
+
const tmpState = existsSync(tmpDir)
|
|
463
|
+
? existsSync(join(tmpDir, 'coverage-summary.json'))
|
|
464
|
+
? '.coverage-tmp created, summary present'
|
|
465
|
+
: '.coverage-tmp created, no summary'
|
|
466
|
+
: '.coverage-tmp NOT created';
|
|
467
|
+
return [
|
|
468
|
+
`vitest coverage summary unreadable: ${err instanceof Error ? err.message : String(err)}`,
|
|
469
|
+
outcome,
|
|
470
|
+
version,
|
|
471
|
+
tmpState,
|
|
472
|
+
stdoutTail ? `stdout: ${stdoutTail}` : '',
|
|
473
|
+
stderrTail ? `stderr: ${stderrTail}` : '',
|
|
474
|
+
].filter(Boolean).join(' — ');
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function readReusedCoverageSummary(cwd, opts) {
|
|
478
|
+
const rawPath = opts.coverageSummaryPath;
|
|
479
|
+
if (typeof rawPath !== 'string' || rawPath.length === 0) {
|
|
480
|
+
throw new Error('coverage summary path is missing');
|
|
481
|
+
}
|
|
482
|
+
const summaryPath = resolve(cwd, rawPath);
|
|
483
|
+
if (isAbsolute(rawPath) || relative(cwd, summaryPath).startsWith('..')) {
|
|
484
|
+
throw new Error('coverage summary path must stay inside the project root');
|
|
485
|
+
}
|
|
486
|
+
const stat = statSync(summaryPath);
|
|
487
|
+
if (!stat.isFile()) throw new Error(`coverage summary is not a file: ${summaryPath}`);
|
|
488
|
+
if (opts.coverageStartedAt !== undefined) {
|
|
489
|
+
if (!Number.isFinite(opts.coverageStartedAt)) {
|
|
490
|
+
throw new Error('coverage summary freshness timestamp is invalid');
|
|
491
|
+
}
|
|
492
|
+
if (stat.mtimeMs < opts.coverageStartedAt) {
|
|
493
|
+
throw new Error(`coverage summary is stale: ${summaryPath}`);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
return JSON.parse(readFileSync(summaryPath, 'utf-8'));
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function coverageMetricsFromSummary(summary) {
|
|
500
|
+
if (typeof summary.total?.lines?.pct !== 'number') {
|
|
501
|
+
throw new Error('coverage summary has no numeric total.lines.pct');
|
|
502
|
+
}
|
|
503
|
+
return {
|
|
504
|
+
coverageLine: { value: summary.total.lines.pct, unit: 'percent', direction: 'higher-is-better' },
|
|
505
|
+
<% if (metricsProfile.includeBranchCoverage) { %>
|
|
506
|
+
coverageBranch: { value: summary.total.branches?.pct ?? 0, unit: 'percent', direction: 'higher-is-better' },
|
|
507
|
+
<% } %>
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Collect TypeScript/JavaScript debt metrics.
|
|
513
|
+
* @param {string} cwd
|
|
514
|
+
* @param {Array<{metric: string, reason: string}>} [collectionErrors]
|
|
515
|
+
* Sink for tool-ran-but-collection-failed events (distinct from
|
|
516
|
+
* tool-not-installed, which soft-skips). debt-report --gate hard-fails on
|
|
517
|
+
* any entry (#1286, fail-closed).
|
|
518
|
+
* @param {{spawnCoverage?: (cwd: string, args: string[]) => {status: number|null, stdout: string, stderr: string} | null, coverageSummaryPath?: string, coverageStartedAt?: number}} [opts]
|
|
519
|
+
* spawnCoverage is injectable for tests, mirroring jscpdScan's opts.spawn
|
|
520
|
+
* seam: it replaces the `npx vitest run --coverage` invocation so a unit test
|
|
521
|
+
* can deterministically simulate "vitest ran, no summary written" (the
|
|
522
|
+
* CI-only ENOENT of #2226). Return null to soft-skip (tool-not-installed).
|
|
523
|
+
* @returns {Record<string, {value: number, unit: string, direction: string}>}
|
|
524
|
+
*/
|
|
525
|
+
export function collectMetrics(cwd, collectionErrors = [], opts = {}) {
|
|
526
|
+
const metrics = {};
|
|
527
|
+
|
|
528
|
+
// ── Coverage (vitest json-summary reporter) ───────────────────────────────
|
|
529
|
+
if (opts.coverageSummaryPath !== undefined) {
|
|
530
|
+
try {
|
|
531
|
+
Object.assign(metrics, coverageMetricsFromSummary(readReusedCoverageSummary(cwd, opts)));
|
|
532
|
+
} catch (err) {
|
|
533
|
+
const reason = `gate coverage summary unreadable: ${err instanceof Error ? err.message : String(err)}`;
|
|
534
|
+
collectionErrors.push({ metric: 'coverageLine', reason });
|
|
535
|
+
process.stderr.write(`[baseline] ERROR: ${reason}\n`);
|
|
536
|
+
}
|
|
537
|
+
} else {
|
|
538
|
+
// Stale-report poisoning guard, mirroring jscpdScan's reportPath guard above: a leftover .coverage-tmp from a previous run (this box's self-hosted CI workdir persists between jobs) must never be read as this run's output.
|
|
539
|
+
rmSync(resolve(cwd, '.coverage-tmp'), { recursive: true, force: true });
|
|
540
|
+
const spawnCoverage =
|
|
541
|
+
opts.spawnCoverage ??
|
|
542
|
+
((dir, args) => spawnOrSkip('coverageLine', 'vitest', 'npx', args, { cwd: dir }));
|
|
543
|
+
const coverageRaw = spawnCoverage(cwd,
|
|
544
|
+
['vitest', 'run', '--coverage', '--coverage.reporter=json-summary', '--coverage.reportsDirectory=.coverage-tmp']);
|
|
545
|
+
if (coverageRaw !== null) {
|
|
546
|
+
try {
|
|
547
|
+
const summary = JSON.parse(readFileSync(resolve(cwd, '.coverage-tmp/coverage-summary.json'), 'utf-8'));
|
|
548
|
+
Object.assign(metrics, coverageMetricsFromSummary(summary));
|
|
549
|
+
} catch (err) {
|
|
550
|
+
const reason = coverageFailureReason(err, coverageRaw, cwd);
|
|
551
|
+
collectionErrors.push({ metric: 'coverageLine', reason });
|
|
552
|
+
process.stderr.write(`[baseline] ERROR: ${reason}\n`);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// ── ESLint errors (severity 2) ────────────────────────────────────────────
|
|
558
|
+
const eslintRaw = spawnOrSkip('eslintErrors', 'eslint', 'npx', ['eslint', 'src', '--format', 'json'], { cwd });
|
|
559
|
+
if (eslintRaw !== null) {
|
|
560
|
+
try {
|
|
561
|
+
const report = JSON.parse(eslintRaw.stdout || '[]');
|
|
562
|
+
metrics.eslintErrors = {
|
|
563
|
+
value: report.flatMap(f => f.messages).filter(m => m.severity === 2).length,
|
|
564
|
+
unit: 'count', direction: 'lower-is-better',
|
|
565
|
+
};
|
|
566
|
+
} catch (err) {
|
|
567
|
+
const reason = `eslint report unreadable: ${err instanceof Error ? err.message : String(err)}`;
|
|
568
|
+
collectionErrors.push({ metric: 'eslintErrors', reason });
|
|
569
|
+
process.stderr.write(`[baseline] ERROR: ${reason}\n`);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// ── TSC strict errors ─────────────────────────────────────────────────────
|
|
574
|
+
const tscRaw = spawnOrSkip('tscStrictErrors', 'tsc', 'npx', ['tsc', '--noEmit'], { cwd });
|
|
575
|
+
if (tscRaw !== null) {
|
|
576
|
+
const output = (tscRaw.stdout ?? '') + (tscRaw.stderr ?? '');
|
|
577
|
+
metrics.tscStrictErrors = {
|
|
578
|
+
value: output.split('\n').filter(l => l.includes('error TS')).length,
|
|
579
|
+
unit: 'count', direction: 'lower-is-better',
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// ── Complexity violations (eslint rule) ───────────────────────────────────
|
|
584
|
+
// Scan `scripts` alongside `src`: the gate-enforcement layer is the
|
|
585
|
+
// highest-leverage code in the repo and was previously exempt from the
|
|
586
|
+
// complexity ratchet (#1523/#1542). The baseline grandfathers the current
|
|
587
|
+
// offenders; the ratchet blocks any net increase and admits gradual burn-down.
|
|
588
|
+
const eslintComplexRaw = spawnOrSkip('complexityViolations', 'eslint', 'npx',
|
|
589
|
+
['eslint', 'src', 'scripts', '--format', 'json', '--rule', '{"complexity":["warn",10]}'], { cwd });
|
|
590
|
+
if (eslintComplexRaw !== null) {
|
|
591
|
+
try {
|
|
592
|
+
const report = JSON.parse(eslintComplexRaw.stdout || '[]');
|
|
593
|
+
metrics.complexityViolations = {
|
|
594
|
+
value: report.flatMap(f => f.messages).filter(m => m.ruleId === 'complexity').length,
|
|
595
|
+
unit: 'count', direction: 'lower-is-better',
|
|
596
|
+
};
|
|
597
|
+
} catch (err) {
|
|
598
|
+
const reason = `eslint complexity report unreadable: ${err instanceof Error ? err.message : String(err)}`;
|
|
599
|
+
collectionErrors.push({ metric: 'complexityViolations', reason });
|
|
600
|
+
process.stderr.write(`[baseline] ERROR: ${reason}\n`);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// ── Dead code (knip) ──────────────────────────────────────────────────────
|
|
605
|
+
const knipRaw = spawnOrSkip('deadCode', 'knip', 'npx', ['knip', '--reporter', 'json'], { cwd });
|
|
606
|
+
if (knipRaw !== null) {
|
|
607
|
+
try {
|
|
608
|
+
const report = JSON.parse(knipRaw.stdout || '{}');
|
|
609
|
+
metrics.deadCode = {
|
|
610
|
+
value: (report.files ?? []).length + (report.exports ?? []).length,
|
|
611
|
+
unit: 'count', direction: 'lower-is-better',
|
|
612
|
+
};
|
|
613
|
+
} catch (err) {
|
|
614
|
+
const reason = `knip report unreadable: ${err instanceof Error ? err.message : String(err)}`;
|
|
615
|
+
collectionErrors.push({ metric: 'deadCode', reason });
|
|
616
|
+
process.stderr.write(`[baseline] ERROR: ${reason}\n`);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// ── Duplication (jscpd) — CANON-22 DRY ratchet (Lehman entropy) ────────────
|
|
621
|
+
// jscpdScan handles the v5 fileset contract (positional paths from
|
|
622
|
+
// .jscpd.json#path, stale-report cleanup, 0-source rejection). A scan that
|
|
623
|
+
// ran but failed is surfaced via collectionErrors so debt-report --gate can
|
|
624
|
+
// hard-fail instead of soft-skipping as "missing tool" (#1286, fail-closed).
|
|
625
|
+
{
|
|
626
|
+
const scan = jscpdScan(cwd);
|
|
627
|
+
if (scan.skipped) {
|
|
628
|
+
// npx/tooling absent on this machine — genuine soft-skip (ENOENT path).
|
|
629
|
+
} else if (scan.error) {
|
|
630
|
+
collectionErrors.push({ metric: 'duplicationPercentage', reason: scan.error });
|
|
631
|
+
process.stderr.write(`[baseline] ERROR: ${scan.error}\n`);
|
|
632
|
+
} else if (scan.legacyNoReport) {
|
|
633
|
+
// v4 binary, zero clones (v5 always writes the report) — genuinely 0%.
|
|
634
|
+
metrics.duplicationPercentage = { value: 0, unit: 'percent', direction: 'lower-is-better' };
|
|
635
|
+
} else {
|
|
636
|
+
metrics.duplicationPercentage = {
|
|
637
|
+
value: scan.percentage,
|
|
638
|
+
unit: 'percent', direction: 'lower-is-better',
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
<% if (metricsProfile.includeBundleSize) { %>
|
|
644
|
+
// ── Bundle size (frontend-spa only) ───────────────────────────────────────
|
|
645
|
+
if (existsSync(resolve(cwd, 'dist'))) {
|
|
646
|
+
const duRaw = run('du', ['-sb', 'dist/'], { cwd });
|
|
647
|
+
if (!duRaw.error && duRaw.status === 0) {
|
|
648
|
+
const match = duRaw.stdout.match(/^(\d+)/);
|
|
649
|
+
if (match) {
|
|
650
|
+
metrics.bundleSizeKb = {
|
|
651
|
+
value: Math.round(parseInt(match[1]) / 1024),
|
|
652
|
+
unit: 'kb', direction: 'lower-is-better',
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
<% } %>
|
|
658
|
+
<% if (metricsProfile.includePublicApiSurface) { %>
|
|
659
|
+
// ── Public API surface (library archetype — count of top-level exports) ───
|
|
660
|
+
{
|
|
661
|
+
if (existsSync(resolve(cwd, 'src'))) {
|
|
662
|
+
const grepOut = run('grep', ['-rh', '--include=*.ts', '--include=*.mts', '^export', 'src/'], {
|
|
663
|
+
cwd,
|
|
664
|
+
});
|
|
665
|
+
if (grepOut.error || (grepOut.status !== 0 && grepOut.status !== 1)) {
|
|
666
|
+
const reason = `public API grep failed${grepOut.error?.message ? `: ${grepOut.error.message}` : ` (exit ${grepOut.status})`}`;
|
|
667
|
+
collectionErrors.push({ metric: 'publicApiSurface', reason });
|
|
668
|
+
process.stderr.write(`[baseline] ERROR: ${reason}\n`);
|
|
669
|
+
} else {
|
|
670
|
+
metrics.publicApiSurface = {
|
|
671
|
+
value: grepOut.stdout ? grepOut.stdout.split('\n').filter(Boolean).length : 0,
|
|
672
|
+
unit: 'count', direction: 'lower-is-better',
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
<% } %>
|
|
678
|
+
|
|
679
|
+
// ── Finding-hygiene (un-promoted findings spool, #1405) ───────────────────
|
|
680
|
+
// Language-agnostic: reads `.arbiter/findings/*.jsonl`. Spool absent → omitted
|
|
681
|
+
// (NA), so a project that never filed a finding is not penalised. Rewards
|
|
682
|
+
// DRAINING (lower-is-better); filing more findings is a regression (anti-gaming).
|
|
683
|
+
Object.assign(metrics, collectFindingsMetrics(cwd));
|
|
684
|
+
|
|
685
|
+
return metrics;
|
|
686
|
+
}
|
|
687
|
+
<% } else if (language === 'rust') { %>
|
|
688
|
+
/**
|
|
689
|
+
* Collect Rust debt metrics.
|
|
690
|
+
* @param {string} cwd
|
|
691
|
+
* @returns {Record<string, {value: number, unit: string, direction: string}>}
|
|
692
|
+
*/
|
|
693
|
+
export function collectMetrics(cwd) {
|
|
694
|
+
const metrics = {};
|
|
695
|
+
|
|
696
|
+
// ── Coverage (cargo tarpaulin) ────────────────────────────────────────────
|
|
697
|
+
run('cargo', ['tarpaulin', '--out', 'Json', '--output-dir', '.coverage-tmp'], { cwd });
|
|
698
|
+
try {
|
|
699
|
+
const data = JSON.parse(readFileSync(resolve(cwd, '.coverage-tmp/tarpaulin-report.json'), 'utf-8'));
|
|
700
|
+
metrics.coverageLine = { value: data.coverage, unit: 'percent', direction: 'higher-is-better' };
|
|
701
|
+
} catch { metrics.coverageLine = { value: 0, unit: 'percent', direction: 'higher-is-better' }; }
|
|
702
|
+
|
|
703
|
+
// ── Clippy errors + complexity + dead code ────────────────────────────────
|
|
704
|
+
const clippyRaw = spawnOrSkip('clippyDenyCount', 'cargo', 'cargo',
|
|
705
|
+
['clippy', '--message-format=json', '--', '-W', 'clippy::cognitive_complexity', '-W', 'dead_code'], { cwd });
|
|
706
|
+
if (clippyRaw !== null) {
|
|
707
|
+
try {
|
|
708
|
+
const lines = (clippyRaw.stdout ?? '').split('\n').filter(Boolean);
|
|
709
|
+
const msgs = lines.map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
|
|
710
|
+
metrics.clippyDenyCount = {
|
|
711
|
+
value: msgs.filter(m => m.reason === 'compiler-message' && m.message?.level === 'error').length,
|
|
712
|
+
unit: 'count', direction: 'lower-is-better',
|
|
713
|
+
};
|
|
714
|
+
metrics.complexityViolations = {
|
|
715
|
+
value: msgs.filter(m => m.reason === 'compiler-message' && m.message?.code?.code?.includes('cognitive_complexity')).length,
|
|
716
|
+
unit: 'count', direction: 'lower-is-better',
|
|
717
|
+
};
|
|
718
|
+
metrics.deadCode = {
|
|
719
|
+
value: msgs.filter(m => m.reason === 'compiler-message' && m.message?.code?.code === 'dead_code').length,
|
|
720
|
+
unit: 'count', direction: 'lower-is-better',
|
|
721
|
+
};
|
|
722
|
+
} catch {
|
|
723
|
+
metrics.clippyDenyCount = { value: 0, unit: 'count', direction: 'lower-is-better' };
|
|
724
|
+
metrics.complexityViolations = { value: 0, unit: 'count', direction: 'lower-is-better' };
|
|
725
|
+
metrics.deadCode = { value: 0, unit: 'count', direction: 'lower-is-better' };
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// ── cargo audit advisories ────────────────────────────────────────────────
|
|
730
|
+
const auditRaw = spawnOrSkip('cargoAuditAdvisories', 'cargo-audit', 'cargo', ['audit', '--json'], { cwd });
|
|
731
|
+
if (auditRaw !== null) {
|
|
732
|
+
try {
|
|
733
|
+
const report = JSON.parse(auditRaw.stdout || '{}');
|
|
734
|
+
metrics.cargoAuditAdvisories = {
|
|
735
|
+
value: report.vulnerabilities?.found ?? 0,
|
|
736
|
+
unit: 'count', direction: 'lower-is-better',
|
|
737
|
+
};
|
|
738
|
+
} catch { metrics.cargoAuditAdvisories = { value: 0, unit: 'count', direction: 'lower-is-better' }; }
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// ── Finding-hygiene (un-promoted findings spool, #1405) ───────────────────
|
|
742
|
+
Object.assign(metrics, collectFindingsMetrics(cwd));
|
|
743
|
+
|
|
744
|
+
return metrics;
|
|
745
|
+
}
|
|
746
|
+
<% } else if (language === 'java') { %>
|
|
747
|
+
/**
|
|
748
|
+
* Collect Java debt metrics.
|
|
749
|
+
* @param {string} cwd
|
|
750
|
+
* @returns {Record<string, {value: number, unit: string, direction: string}>}
|
|
751
|
+
*/
|
|
752
|
+
export function collectMetrics(cwd) {
|
|
753
|
+
const metrics = {};
|
|
754
|
+
const gradleOrMvn = '<%= buildTool === "gradle" ? "./gradlew" : "mvn" %>';
|
|
755
|
+
const pmdTask = '<%= buildTool === "gradle" ? "pmdMain" : "pmd:pmd" %>';
|
|
756
|
+
const checkstyleTask = '<%= buildTool === "gradle" ? "checkstyleMain" : "checkstyle:checkstyle" %>';
|
|
757
|
+
|
|
758
|
+
// ── JaCoCo line coverage ──────────────────────────────────────────────────
|
|
759
|
+
try {
|
|
760
|
+
const report = readFileSync(resolve(cwd, 'build/reports/jacoco/test/jacocoTestReport.xml'), 'utf-8');
|
|
761
|
+
const lineMatch = report.match(/type="LINE"[^/]*missed="(\d+)"[^/]*covered="(\d+)"/);
|
|
762
|
+
if (lineMatch) {
|
|
763
|
+
const missed = parseInt(lineMatch[1]), covered = parseInt(lineMatch[2]);
|
|
764
|
+
const pct = covered + missed > 0 ? (covered / (covered + missed)) * 100 : 0;
|
|
765
|
+
metrics.coverageLine = { value: Math.round(pct * 10) / 10, unit: 'percent', direction: 'higher-is-better' };
|
|
766
|
+
} else { metrics.coverageLine = { value: 0, unit: 'percent', direction: 'higher-is-better' }; }
|
|
767
|
+
<% if (metricsProfile.includeBranchCoverage) { %>
|
|
768
|
+
const branchMatch = report.match(/type="BRANCH"[^/]*missed="(\d+)"[^/]*covered="(\d+)"/);
|
|
769
|
+
if (branchMatch) {
|
|
770
|
+
const missed = parseInt(branchMatch[1]), covered = parseInt(branchMatch[2]);
|
|
771
|
+
const pct = covered + missed > 0 ? (covered / (covered + missed)) * 100 : 0;
|
|
772
|
+
metrics.coverageBranch = { value: Math.round(pct * 10) / 10, unit: 'percent', direction: 'higher-is-better' };
|
|
773
|
+
}
|
|
774
|
+
<% } %>
|
|
775
|
+
} catch { metrics.coverageLine = { value: 0, unit: 'percent', direction: 'higher-is-better' }; }
|
|
776
|
+
|
|
777
|
+
// ── PMD violations ────────────────────────────────────────────────────────
|
|
778
|
+
const pmdBuildRaw = spawnOrSkip('pmdViolations', gradleOrMvn, gradleOrMvn, [pmdTask, '-q'], { cwd });
|
|
779
|
+
if (pmdBuildRaw !== null) {
|
|
780
|
+
try {
|
|
781
|
+
const pmdXmlPaths = [
|
|
782
|
+
resolve(cwd, 'build/reports/pmd/main.xml'),
|
|
783
|
+
resolve(cwd, 'target/pmd.xml'),
|
|
784
|
+
];
|
|
785
|
+
let found = false;
|
|
786
|
+
for (const p of pmdXmlPaths) {
|
|
787
|
+
if (existsSync(p)) {
|
|
788
|
+
const xml = readFileSync(p, 'utf-8');
|
|
789
|
+
const matches = xml.match(/<violation\b/g);
|
|
790
|
+
metrics.pmdViolations = { value: matches ? matches.length : 0, unit: 'count', direction: 'lower-is-better' };
|
|
791
|
+
found = true; break;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
if (!found) metrics.pmdViolations = { value: 0, unit: 'count', direction: 'lower-is-better' };
|
|
795
|
+
} catch { metrics.pmdViolations = { value: 0, unit: 'count', direction: 'lower-is-better' }; }
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// ── Checkstyle violations ─────────────────────────────────────────────────
|
|
799
|
+
const csBuildRaw = spawnOrSkip('checkstyleViolations', gradleOrMvn, gradleOrMvn, [checkstyleTask, '-q'], { cwd });
|
|
800
|
+
if (csBuildRaw !== null) {
|
|
801
|
+
try {
|
|
802
|
+
const csXmlPaths = [
|
|
803
|
+
resolve(cwd, 'build/reports/checkstyle/main.xml'),
|
|
804
|
+
resolve(cwd, 'target/checkstyle-result.xml'),
|
|
805
|
+
];
|
|
806
|
+
let found = false;
|
|
807
|
+
for (const p of csXmlPaths) {
|
|
808
|
+
if (existsSync(p)) {
|
|
809
|
+
const xml = readFileSync(p, 'utf-8');
|
|
810
|
+
const matches = xml.match(/<error\b/g);
|
|
811
|
+
metrics.checkstyleViolations = { value: matches ? matches.length : 0, unit: 'count', direction: 'lower-is-better' };
|
|
812
|
+
found = true; break;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
if (!found) metrics.checkstyleViolations = { value: 0, unit: 'count', direction: 'lower-is-better' };
|
|
816
|
+
} catch { metrics.checkstyleViolations = { value: 0, unit: 'count', direction: 'lower-is-better' }; }
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
<% if (metricsProfile.spotbugsEnabled) { %>
|
|
820
|
+
// ── SpotBugs violations ───────────────────────────────────────────────────
|
|
821
|
+
const sbBuildRaw = spawnOrSkip('spotbugsViolations', gradleOrMvn, gradleOrMvn, ['spotbugsMain', '-q'], { cwd });
|
|
822
|
+
if (sbBuildRaw !== null) {
|
|
823
|
+
try {
|
|
824
|
+
const sbXmlPaths = [
|
|
825
|
+
resolve(cwd, 'build/reports/spotbugs/main.xml'),
|
|
826
|
+
resolve(cwd, 'build/reports/spotbugs/spotbugsMain.xml'),
|
|
827
|
+
];
|
|
828
|
+
let found = false;
|
|
829
|
+
for (const p of sbXmlPaths) {
|
|
830
|
+
if (existsSync(p)) {
|
|
831
|
+
const xml = readFileSync(p, 'utf-8');
|
|
832
|
+
const matches = xml.match(/<BugInstance\b/g);
|
|
833
|
+
metrics.spotbugsViolations = { value: matches ? matches.length : 0, unit: 'count', direction: 'lower-is-better' };
|
|
834
|
+
found = true; break;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
if (!found) metrics.spotbugsViolations = { value: 0, unit: 'count', direction: 'lower-is-better' };
|
|
838
|
+
} catch { metrics.spotbugsViolations = { value: 0, unit: 'count', direction: 'lower-is-better' }; }
|
|
839
|
+
}
|
|
840
|
+
<% } %>
|
|
841
|
+
|
|
842
|
+
<% if (metricsProfile.archunitEnabled) { %>
|
|
843
|
+
// ── ArchUnit failing rule count ───────────────────────────────────────────
|
|
844
|
+
const archBuildRaw = spawnOrSkip('archunitFailingRules', gradleOrMvn, gradleOrMvn,
|
|
845
|
+
['test', '-q', '--tests', '*ArchitectureTest*'], { cwd });
|
|
846
|
+
if (archBuildRaw !== null) {
|
|
847
|
+
try {
|
|
848
|
+
const testResultsDir = resolve(cwd, 'build/test-results/test');
|
|
849
|
+
if (existsSync(testResultsDir)) {
|
|
850
|
+
const files = readdirSync(testResultsDir).filter(f => f.includes('ArchitectureTest') && f.endsWith('.xml'));
|
|
851
|
+
const failingRules = [];
|
|
852
|
+
for (const f of files) {
|
|
853
|
+
const xml = readFileSync(resolve(testResultsDir, f), 'utf-8');
|
|
854
|
+
const methodMatches = [...xml.matchAll(/<testcase[^>]*name="([^"]+)"[^>]*>[^<]*<failure/g)];
|
|
855
|
+
for (const m of methodMatches) failingRules.push(m[1]);
|
|
856
|
+
}
|
|
857
|
+
metrics.archunitFailingRules = {
|
|
858
|
+
value: failingRules.length,
|
|
859
|
+
unit: 'count', direction: 'lower-is-better',
|
|
860
|
+
...(failingRules.length > 0 ? { items: failingRules } : {}),
|
|
861
|
+
};
|
|
862
|
+
} else {
|
|
863
|
+
metrics.archunitFailingRules = { value: 0, unit: 'count', direction: 'lower-is-better' };
|
|
864
|
+
}
|
|
865
|
+
} catch { metrics.archunitFailingRules = { value: 0, unit: 'count', direction: 'lower-is-better' }; }
|
|
866
|
+
}
|
|
867
|
+
<% } %>
|
|
868
|
+
|
|
869
|
+
// ── Finding-hygiene (un-promoted findings spool, #1405) ───────────────────
|
|
870
|
+
Object.assign(metrics, collectFindingsMetrics(cwd));
|
|
871
|
+
|
|
872
|
+
return metrics;
|
|
873
|
+
}
|
|
874
|
+
<% } else if (language === 'go') { %>
|
|
875
|
+
/**
|
|
876
|
+
* Collect Go debt metrics.
|
|
877
|
+
* @param {string} cwd
|
|
878
|
+
* @returns {Record<string, {value: number, unit: string, direction: string}>}
|
|
879
|
+
*/
|
|
880
|
+
export function collectMetrics(cwd) {
|
|
881
|
+
const metrics = {};
|
|
882
|
+
|
|
883
|
+
// ── Coverage (go test -cover) ─────────────────────────────────────────────
|
|
884
|
+
// Enumerate project packages with `go list ./...` and exclude import paths
|
|
885
|
+
// containing /node_modules/ — npm packages that ship Go source without a
|
|
886
|
+
// nested go.mod (e.g. `flatted`) are otherwise measured as project code,
|
|
887
|
+
// making the debt ratchet dependency-layout-sensitive (#1950). Package paths
|
|
888
|
+
// are passed as separate spawn arguments (shell:false), never concatenated.
|
|
889
|
+
// Fail closed if enumeration fails or yields no usable packages.
|
|
890
|
+
const goList = run('go', ['list', './...'], { cwd });
|
|
891
|
+
if ((goList.status ?? 1) !== 0) {
|
|
892
|
+
throw new Error(
|
|
893
|
+
`go list ./... failed (exit ${goList.status}): ${goList.stderr ?? ''} — refusing to write (fail-closed)`,
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
const goPkgs = (goList.stdout ?? '')
|
|
897
|
+
.split('\n')
|
|
898
|
+
.map((l) => l.trim())
|
|
899
|
+
.filter((l) => l.length > 0 && !l.includes('/node_modules/'));
|
|
900
|
+
if (goPkgs.length === 0) {
|
|
901
|
+
throw new Error(
|
|
902
|
+
'go list ./... returned no project packages after excluding /node_modules/ — refusing to write (fail-closed)',
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
run('go', ['test', '-coverprofile=.coverage-tmp.out', ...goPkgs], { cwd });
|
|
906
|
+
const goCoverRaw = run('go', ['tool', 'cover', '-func=.coverage-tmp.out'], { cwd });
|
|
907
|
+
try {
|
|
908
|
+
const match = goCoverRaw.stdout.match(/total:\s+\(statements\)\s+([\d.]+)%/);
|
|
909
|
+
metrics.coverageLine = { value: match ? parseFloat(match[1]) : 0, unit: 'percent', direction: 'higher-is-better' };
|
|
910
|
+
} catch { metrics.coverageLine = { value: 0, unit: 'percent', direction: 'higher-is-better' }; }
|
|
911
|
+
|
|
912
|
+
// ── golangci-lint violations ──────────────────────────────────────────────
|
|
913
|
+
const golintRaw = spawnOrSkip('golangciViolations', 'golangci-lint', 'golangci-lint',
|
|
914
|
+
['run', '--out-format=json'], { cwd });
|
|
915
|
+
if (golintRaw !== null) {
|
|
916
|
+
try {
|
|
917
|
+
const report = JSON.parse(golintRaw.stdout || '{"Issues":[]}');
|
|
918
|
+
metrics.golangciViolations = {
|
|
919
|
+
value: (report.Issues ?? []).length,
|
|
920
|
+
unit: 'count', direction: 'lower-is-better',
|
|
921
|
+
};
|
|
922
|
+
} catch { metrics.golangciViolations = { value: 0, unit: 'count', direction: 'lower-is-better' }; }
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// ── govulncheck advisories ────────────────────────────────────────────────
|
|
926
|
+
const govulnRaw = spawnOrSkip('govulncheckAdvisories', 'govulncheck', 'govulncheck',
|
|
927
|
+
['-json', './...'], { cwd });
|
|
928
|
+
if (govulnRaw !== null) {
|
|
929
|
+
try {
|
|
930
|
+
const lines = (govulnRaw.stdout ?? '').split('\n').filter(Boolean);
|
|
931
|
+
const osvIds = new Set();
|
|
932
|
+
for (const l of lines) {
|
|
933
|
+
try {
|
|
934
|
+
const obj = JSON.parse(l);
|
|
935
|
+
if (obj.osv?.id) osvIds.add(obj.osv.id);
|
|
936
|
+
} catch { /* skip malformed line */ }
|
|
937
|
+
}
|
|
938
|
+
metrics.govulncheckAdvisories = { value: osvIds.size, unit: 'count', direction: 'lower-is-better' };
|
|
939
|
+
} catch { metrics.govulncheckAdvisories = { value: 0, unit: 'count', direction: 'lower-is-better' }; }
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// ── Finding-hygiene (un-promoted findings spool, #1405) ───────────────────
|
|
943
|
+
Object.assign(metrics, collectFindingsMetrics(cwd));
|
|
944
|
+
|
|
945
|
+
return metrics;
|
|
946
|
+
}
|
|
947
|
+
<% } else if (language === 'python') { %>
|
|
948
|
+
/**
|
|
949
|
+
* Collect Python debt metrics.
|
|
950
|
+
* @param {string} cwd
|
|
951
|
+
* @returns {Record<string, {value: number, unit: string, direction: string}>}
|
|
952
|
+
*/
|
|
953
|
+
export function collectMetrics(cwd) {
|
|
954
|
+
const metrics = {};
|
|
955
|
+
|
|
956
|
+
// ── Coverage (pytest --cov) ───────────────────────────────────────────────
|
|
957
|
+
run('pytest', ['--cov', '--cov-report=json', '--cov-report=term'], { cwd });
|
|
958
|
+
try {
|
|
959
|
+
const data = JSON.parse(readFileSync(resolve(cwd, 'coverage.json'), 'utf-8'));
|
|
960
|
+
metrics.coverageLine = { value: data.totals?.percent_covered ?? 0, unit: 'percent', direction: 'higher-is-better' };
|
|
961
|
+
} catch { metrics.coverageLine = { value: 0, unit: 'percent', direction: 'higher-is-better' }; }
|
|
962
|
+
|
|
963
|
+
// ── ruff errors (all rules) ───────────────────────────────────────────────
|
|
964
|
+
const ruffRaw = spawnOrSkip('ruffErrors', 'ruff', 'ruff',
|
|
965
|
+
['check', '.', '--output-format', 'json'], { cwd });
|
|
966
|
+
if (ruffRaw !== null) {
|
|
967
|
+
try {
|
|
968
|
+
const violations = JSON.parse(ruffRaw.stdout || '[]');
|
|
969
|
+
metrics.ruffErrors = { value: violations.length, unit: 'count', direction: 'lower-is-better' };
|
|
970
|
+
} catch { metrics.ruffErrors = { value: 0, unit: 'count', direction: 'lower-is-better' }; }
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// ── mypy strict errors (skip if no mypy config) ───────────────────────────
|
|
974
|
+
const mypyConfigExists = existsSync(resolve(cwd, 'mypy.ini'))
|
|
975
|
+
|| existsSync(resolve(cwd, '.mypy.ini'))
|
|
976
|
+
|| existsSync(resolve(cwd, 'setup.cfg'));
|
|
977
|
+
if (mypyConfigExists) {
|
|
978
|
+
const mypyRaw = spawnOrSkip('mypyStrictErrors', 'mypy', 'mypy',
|
|
979
|
+
['--strict', '.', '--no-color-output'], { cwd });
|
|
980
|
+
if (mypyRaw !== null) {
|
|
981
|
+
const errorLines = (mypyRaw.stdout ?? '').split('\n').filter(l => l.includes(': error:'));
|
|
982
|
+
metrics.mypyStrictErrors = { value: errorLines.length, unit: 'count', direction: 'lower-is-better' };
|
|
983
|
+
}
|
|
984
|
+
} else {
|
|
985
|
+
metrics.mypyStrictErrors = { value: 0, unit: 'count', direction: 'lower-is-better' };
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// ── pip-audit advisories ──────────────────────────────────────────────────
|
|
989
|
+
const pipAuditRaw = spawnOrSkip('pipAuditAdvisories', 'pip-audit', 'pip-audit',
|
|
990
|
+
['--format', 'json'], { cwd });
|
|
991
|
+
if (pipAuditRaw !== null) {
|
|
992
|
+
try {
|
|
993
|
+
const report = JSON.parse(pipAuditRaw.stdout || '{"dependencies":[]}');
|
|
994
|
+
const vulnCount = (report.dependencies ?? []).reduce(
|
|
995
|
+
(acc, dep) => acc + (dep.vulns?.length ?? 0), 0,
|
|
996
|
+
);
|
|
997
|
+
metrics.pipAuditAdvisories = { value: vulnCount, unit: 'count', direction: 'lower-is-better' };
|
|
998
|
+
} catch { metrics.pipAuditAdvisories = { value: 0, unit: 'count', direction: 'lower-is-better' }; }
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
// ── Finding-hygiene (un-promoted findings spool, #1405) ───────────────────
|
|
1002
|
+
Object.assign(metrics, collectFindingsMetrics(cwd));
|
|
1003
|
+
|
|
1004
|
+
return metrics;
|
|
1005
|
+
}
|
|
1006
|
+
<% } else { %>
|
|
1007
|
+
/**
|
|
1008
|
+
* Unknown language — no stack-specific metrics collected.
|
|
1009
|
+
* @param {string} _cwd
|
|
1010
|
+
* @returns {Record<string, never>}
|
|
1011
|
+
*/
|
|
1012
|
+
export function collectMetrics(_cwd) {
|
|
1013
|
+
return {};
|
|
1014
|
+
}
|
|
1015
|
+
<% } %>
|