@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,79 @@
|
|
|
1
|
+
import { type ArbiterConfigV2 } from '../config/schema.js';
|
|
2
|
+
export type { ArbiterConfigV2 };
|
|
3
|
+
export type ArbiterConfig = ArbiterConfigV2;
|
|
4
|
+
/**
|
|
5
|
+
* Persist `arbiter.json` under a robust advisory lock.
|
|
6
|
+
*
|
|
7
|
+
* Mutual exclusion across the whole config-writer set is the COMMAND-level
|
|
8
|
+
* `.arbiter/.lock`: every config-mutating command — init, configure, plugin,
|
|
9
|
+
* upgrade-level, update AND kit-install (#1617) — acquires `.arbiter/.lock`
|
|
10
|
+
* around its entire `loadConfig → mutate → save`, so no two of them can
|
|
11
|
+
* lost-update each other's `arbiter.json`. `acquireLock` is reentrant (#1617),
|
|
12
|
+
* so holding that command lock and re-entering it from a nested writer is a
|
|
13
|
+
* ref-counted no-op rather than a self-deadlock.
|
|
14
|
+
*
|
|
15
|
+
* Within that boundary `saveConfig` ALSO serialises through `.arbiter/kit.lock`
|
|
16
|
+
* via the crash-safe `acquireLock` primitive (file-lock.ts): it performs
|
|
17
|
+
* stale-takeover (a lock orphaned by a crashed/SIGKILL'd run is reclaimed
|
|
18
|
+
* instead of bricking every future write), registers exit/SIGINT/SIGTERM/SIGHUP
|
|
19
|
+
* cleanup, refuses symlinked lock paths, and raises actionable `E_LOCK_*` errors
|
|
20
|
+
* that point at `arbiter doctor recover-lock`. `kit.lock` is a distinct inner
|
|
21
|
+
* lock from `.arbiter/.lock`; `doctor` is taught about both (#1517).
|
|
22
|
+
*
|
|
23
|
+
* #2541: `arbiter.json` is never a generator-emitted target — no `src/generators/*.ts`
|
|
24
|
+
* writes it, and `arbiter update`'s own docs (docs/REFERENCE/file-stability.md) record
|
|
25
|
+
* that it is "never recorded as a manifest entry"; its user-edit protection is the
|
|
26
|
+
* load→mutate→save merge every caller (`configure`, `plugin`, `update`, `upgrade-level`,
|
|
27
|
+
* `init`) already performs at the JS-object level, not `writeFile`'s template-regeneration
|
|
28
|
+
* machinery. It is therefore written with `skipPreserveCheck`, exempting it from the
|
|
29
|
+
* `arbiter:preserve` marker (a config VALUE that happens to contain that literal
|
|
30
|
+
* substring must not permanently freeze the user's own config out from under `arbiter
|
|
31
|
+
* configure`/`update`). The returned `WriteResult` is asserted via `assertWritten` so a
|
|
32
|
+
* write that still did not land — for whatever reason — is a loud failure, never a
|
|
33
|
+
* silently-skipped success.
|
|
34
|
+
*/
|
|
35
|
+
export declare function saveConfig(dir: string, config: ArbiterConfig): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Persist `arbiter.json` AND its `.arbiter-generated.json` snapshot.
|
|
38
|
+
*
|
|
39
|
+
* Unlike `saveConfig` this takes no inner lock of its own: its sole caller is
|
|
40
|
+
* `arbiter update`, which holds the command-level `.arbiter/.lock` (reentrant,
|
|
41
|
+
* #1617) across the whole read-modify-write — the same lock kit-install and the
|
|
42
|
+
* other config writers hold, so update and kit-install are mutually excluded and
|
|
43
|
+
* cannot lost-update each other (#1617). Callers MUST hold `.arbiter/.lock`.
|
|
44
|
+
*
|
|
45
|
+
* #2541: see `saveConfig` above for why `arbiter.json` is written with
|
|
46
|
+
* `skipPreserveCheck` and its `WriteResult` asserted. `writeSnapshot` (below) applies
|
|
47
|
+
* the same treatment to `.arbiter-generated.json`.
|
|
48
|
+
*/
|
|
49
|
+
export declare function saveConfigAndSnapshot(dir: string, config: ArbiterConfig): void;
|
|
50
|
+
/**
|
|
51
|
+
* Write only the snapshot envelope (`.arbiter-generated.json`) without
|
|
52
|
+
* touching `arbiter.json`. Used by `arbiter doctor --repair-state` so
|
|
53
|
+
* repair never clobbers the source-of-truth config (#619).
|
|
54
|
+
*
|
|
55
|
+
* #2541: `.arbiter-generated.json` is documented (docs/REFERENCE/file-stability.md) as
|
|
56
|
+
* a machine-written state file — "No" user-editable, migrated automatically by
|
|
57
|
+
* `arbiter update`'s schema migration registry, and (like `arbiter.json`) never a
|
|
58
|
+
* generator-emitted target and never recorded in the generated-file manifest. It is
|
|
59
|
+
* generation provenance, the same class as TDD evidence and the task-state document —
|
|
60
|
+
* so it is written with `skipPreserveCheck`, and the `WriteResult` is asserted via
|
|
61
|
+
* `assertWritten` so a withheld write (or any other reason the bytes did not land) is a
|
|
62
|
+
* loud failure rather than a silent no-op that leaves stale provenance on disk.
|
|
63
|
+
*/
|
|
64
|
+
export declare function writeSnapshot(dir: string, config: ArbiterConfig): void;
|
|
65
|
+
/**
|
|
66
|
+
* Read `.arbiter-generated.json` and return the inner config.
|
|
67
|
+
*
|
|
68
|
+
* Behavior:
|
|
69
|
+
* - Missing file → null (legitimate, never raises).
|
|
70
|
+
* - Unreadable JSON → null + logger.warn (don't crash arbiter on garbage).
|
|
71
|
+
* - v0 (pre-envelope) snapshot → auto-migrated through config migrate(),
|
|
72
|
+
* returned without persisting (callers re-persist on next write).
|
|
73
|
+
* - v1 envelope with mismatched checksum → THROWS SnapshotChecksumError
|
|
74
|
+
* (#619 acceptance: hard error, no silent overwrite).
|
|
75
|
+
* - v1 envelope with valid checksum → unwrapped + run through config
|
|
76
|
+
* migrate() before return.
|
|
77
|
+
*/
|
|
78
|
+
export declare function loadSnapshot(dir: string): ArbiterConfig | null;
|
|
79
|
+
export declare function loadConfig(dir: string): ArbiterConfig | null;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{existsSync,readFileSync}from"node:fs";import{ensureDir}from"./fs.js";import{join}from"node:path";import{writeFile,assertWritten}from"./fs.js";import{acquireLock}from"./file-lock.js";import{getLogger}from"./logger.js";import{ConfigError}from"./errors.js";import{sanitizeCoercibleFields,validateConfig}from"../config/schema.js";import{migrate}from"../config/migrations/index.js";import{applyEnvOverrides}from"../config/env-overrides.js";import{rotateBackup}from"../state/backups.js";import{SnapshotChecksumError,canonicalJson,wrapSnapshot,unwrapSnapshot}from"../state/envelope.js";import{migrateState}from"../state/migrations/index.js";const CONFIG_FILE="arbiter.json",SNAPSHOT_FILE=".arbiter-generated.json";export async function saveConfig(dir,config){const lockDir=join(dir,".arbiter");ensureDir(lockDir);const lock=await acquireLock(join(lockDir,"kit.lock"));try{const path=join(dir,CONFIG_FILE),result=writeFile(path,JSON.stringify(config,null,2)+`
|
|
3
|
+
`,{skipPreserveCheck:!0});assertWritten(result,`arbiter config at ${path}`)}finally{await lock.release()}}export function saveConfigAndSnapshot(dir,config){const json=JSON.stringify(config,null,2)+`
|
|
4
|
+
`,path=join(dir,CONFIG_FILE),result=writeFile(path,json,{skipPreserveCheck:!0});assertWritten(result,`arbiter config at ${path}`),writeSnapshot(dir,config)}export function writeSnapshot(dir,config){const snapPath=join(dir,SNAPSHOT_FILE);rotateBackup(snapPath);const envelope=wrapSnapshot(config),result=writeFile(snapPath,canonicalJson(envelope)+`
|
|
5
|
+
`,{skipPreserveCheck:!0});assertWritten(result,`arbiter config snapshot at ${snapPath}`)}export function loadSnapshot(dir){const path=join(dir,SNAPSHOT_FILE);if(!existsSync(path))return null;let raw;try{raw=JSON.parse(readFileSync(path,"utf-8"))}catch(err){const msg=err instanceof Error?err.message:String(err);return getLogger().warn("config.snapshot_unreadable",{path,file:SNAPSHOT_FILE,err:msg},`${SNAPSHOT_FILE} at ${path} is unreadable (${msg}) \u2014 delete the file to regenerate on next 'arbiter update'`),null}let inner;try{const state=migrateState(raw);state.migrated||unwrapSnapshot(raw),inner=state.envelope.config}catch(err){if(err instanceof SnapshotChecksumError)throw err;const msg=err instanceof Error?err.message:String(err);return getLogger().warn("config.snapshot_envelope_failed",{path,file:SNAPSHOT_FILE,err:msg},`${SNAPSHOT_FILE} at ${path} failed envelope check (${msg}) \u2014 skipping snapshot`),null}try{return migrate(inner)}catch(err){const msg=err instanceof Error?err.message:String(err);return getLogger().warn("config.snapshot_migration_failed",{path,file:SNAPSHOT_FILE,err:msg},`${SNAPSHOT_FILE} at ${path} failed config migration (${msg}) \u2014 skipping snapshot`),null}}export function loadConfig(dir){const path=join(dir,CONFIG_FILE);if(!existsSync(path))return null;let raw;try{raw=JSON.parse(readFileSync(path,"utf-8"))}catch(err){const msg=err instanceof Error?err.message:String(err);throw new ConfigError("E_CONFIG_INVALID",`arbiter.json at ${path} has invalid JSON: ${msg}. Fix or delete and re-run.`,{hint:"Fix the JSON syntax or delete arbiter.json and run `arbiter init`."})}try{const migrated=migrate(raw),withEnv=applyEnvOverrides(migrated,process.env);let validation=validateConfig(withEnv);if(!validation.ok){const{draft,report}=sanitizeCoercibleFields(withEnv);report.length>0&&(validation=validateConfig(draft),validation.ok&&getLogger().warn("config.coerced_fields",{path,fields:report.map(r=>r.field).join(",")},`arbiter.json at ${path} had ${report.length} field(s) migrated to a safe default (${report.map(r=>`${r.field}: ${JSON.stringify(r.from)} \u2192 ${JSON.stringify(r.to)}`).join("; ")}). Run 'arbiter configure' to persist the cleaned-up values.`))}if(!validation.ok)throw new ConfigError("E_CONFIG_INVALID",`arbiter.json at ${path} failed validation: ${validation.errors.join("; ")}. Fix or delete and re-run.`,{hint:"Fix the configuration errors listed, or delete arbiter.json and run `arbiter init`."});return validation.config}catch(err){if(err instanceof ConfigError)throw err;const msg=err instanceof Error?err.message:String(err);throw new ConfigError("E_CONFIG_INVALID",`arbiter.json at ${path} failed migration: ${msg}. Fix or delete and re-run.`)}}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{confirm,isCancel}from"@clack/prompts";import{getBoolFlag}from"../config/env-registry.js";import{needsDowngradeWarn}from"./channel.js";export async function confirmChannelDowngrade(flagChannel,configChannel){if(!needsDowngradeWarn({flag:flagChannel,configChannel}))return;const isTTY=process.stdin.isTTY;if(getBoolFlag("ARBITER_ALLOW_CHANNEL_DOWNGRADE"))return;isTTY||(process.stderr.write(`[arbiter] error: --channel ${flagChannel??""} is less stable than the configured channel (${configChannel??"latest"}).
|
|
3
|
+
In non-TTY environments, set ARBITER_ALLOW_CHANNEL_DOWNGRADE=1 to allow this.
|
|
4
|
+
`),process.exit(1));const confirmed=await confirm({message:`--channel ${flagChannel??""} is less stable than your configured channel (${configChannel??"latest"}). Continue?`,initialValue:!1});(isCancel(confirmed)||!confirmed)&&(process.stderr.write(`[arbiter] Channel downgrade cancelled.
|
|
5
|
+
`),process.exit(1))}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
const FS_ERRNO_DOC_URL="https://arbiter.dev/reference/cli#filesystem-errors";export const ERROR_CATALOG=new Map([["E_CONFIG_NOT_FOUND",{code:"E_CONFIG_NOT_FOUND",summary:"No arbiter.json found",detail:"This error occurs when an arbiter command cannot find the project configuration file (arbiter.json) in the current directory.",recovery:"Run `arbiter init` to initialize AI governance in this directory.",docUrl:"https://arbiter.dev/reference/cli#init"}],["E_CONFIG_INVALID",{code:"E_CONFIG_INVALID",summary:"arbiter.json is invalid and could not be auto-migrated",detail:"A closed set of stale/renamed fields (e.g. an old contractType flavor or tools value) is coerced to a safe default automatically \u2014 that path only logs a WARN, it never throws. This error also covers a present invalid governanceLevel: because it defines what \u201Cgreen\u201D means, it fails closed like a JSON syntax error. Other fatal cases include arbiter.json not being an object or a field that directly controls gate/threshold strictness (features, thresholds, decomposition, frontend, automation, \u2026) having an unrecoverable shape.",recovery:"Fix the configuration errors listed above, or delete arbiter.json and re-run `arbiter init`. If a WARN about coerced fields appeared on a previous run, run `arbiter configure` to persist the cleaned-up values.",docUrl:"https://arbiter.dev/reference/cli#configure"}],["E_INVALID_TOOL",{code:"E_INVALID_TOOL",summary:"Unknown AI tool name",detail:"The --tools flag received a value that is not a recognized AI tool identifier.",recovery:"Valid tools: claude, codex. Example: `arbiter init --tools claude,codex`.",docUrl:"https://arbiter.dev/reference/cli#init"}],["E_INVALID_LEVEL",{code:"E_INVALID_LEVEL",summary:"Unknown governance level",detail:"The --level flag received a value that is not L1, L2, or L3.",recovery:"Use one of: L1 (fast/minimal), L2 (standard, default), L3 (audit-grade). Example: `arbiter init --level L2`.",docUrl:"https://arbiter.dev/reference/cli#governance-levels"}],["E_INVALID_ARCHETYPE",{code:"E_INVALID_ARCHETYPE",summary:"Invalid project archetype",detail:"The archetype value is not in the supported archetype list.",recovery:"Run `arbiter configure --help` for a list of valid archetypes.",docUrl:"https://arbiter.dev/reference/cli#configure"}],["E_INVALID_BOOL",{code:"E_INVALID_BOOL",summary:"Expected true or false",detail:"A configuration field that requires a boolean received something else.",recovery:"Use `true` or `false` (lowercase). Example: `arbiter configure --set features.debtGates=true`.",docUrl:"https://arbiter.dev/reference/cli#configure"}],["E_INVALID_NUMBER",{code:"E_INVALID_NUMBER",summary:"Expected a number",detail:"A configuration field that requires a number received a non-numeric value.",recovery:"Provide an integer or decimal. Example: `arbiter configure --set thresholds.lineCoverage=80`.",docUrl:"https://arbiter.dev/reference/cli#configure"}],["E_INVALID_FORMAT",{code:"E_INVALID_FORMAT",summary:"Invalid --set format",detail:"The --set argument must use the form `path=value`.",recovery:"Example: `arbiter configure --set tools=claude,codex`.",docUrl:"https://arbiter.dev/reference/cli#configure"}],["E_UNKNOWN_PATH",{code:"E_UNKNOWN_PATH",summary:"Unknown configuration path",detail:"The dotted path used in --set does not correspond to any known configuration key.",recovery:"Run `arbiter configure --help` to see valid paths.",docUrl:"https://arbiter.dev/reference/cli#configure"}],["E_TARGET_REQUIRED",{code:"E_TARGET_REQUIRED",summary:"--target is required for upgrade-level",detail:"The upgrade-level command needs to know which level to upgrade to.",recovery:"Specify `--target L2` or `--target L3`.",docUrl:"https://arbiter.dev/reference/cli#upgrade-level"}],["E_ALREADY_AT_LEVEL",{code:"E_ALREADY_AT_LEVEL",summary:"Already at the target governance level",detail:"The project is already configured at the requested level \u2014 nothing to upgrade.",recovery:"If you want to regenerate governance files, run `arbiter update` instead.",docUrl:"https://arbiter.dev/reference/cli#upgrade-level"}],["E_DOWNGRADE_NOT_SUPPORTED",{code:"E_DOWNGRADE_NOT_SUPPORTED",summary:"Downgrading governance level is not supported via upgrade-level",detail:"arbiter does not automatically downgrade governance because that removes enforced checks.",recovery:"Edit `governanceLevel` in arbiter.json manually, then run `arbiter update` to regenerate files.",docUrl:"https://arbiter.dev/reference/cli#upgrade-level"}],["E_GRACE_NOT_SUPPORTED",{code:"E_GRACE_NOT_SUPPORTED",summary:"Grace periods are only supported for L1 to L2 upgrades",detail:"The generated gate implements grace-period warning mode only when a project moves from L1 to L2 (ADR-028).",recovery:"To move to a higher level, run `arbiter configure --set governanceLevel=<L3|L4>` followed by `arbiter update`.",docUrl:"https://arbiter.dev/reference/cli#upgrade-level"}],["E_NO_GRACE_PERIOD",{code:"E_NO_GRACE_PERIOD",summary:"No active grace period to extend",detail:"The `--extend` flag requires an existing, non-expired grace period.",recovery:"Run `arbiter upgrade-level --target L2` first to start a new grace period.",docUrl:"https://arbiter.dev/reference/cli#upgrade-level"}],["E_GRACE_LOG_MALFORMED",{code:"E_GRACE_LOG_MALFORMED",summary:"grace-log.json is malformed",detail:"The grace period log file exists but cannot be parsed as expected JSON.",recovery:"Delete the grace-log.json file noted in the error message and re-run.",docUrl:"https://arbiter.dev/reference/cli#upgrade-level"}],["E_PLUGIN_FAILED",{code:"E_PLUGIN_FAILED",summary:"One or more plugins failed during init",detail:"A third-party arbiter plugin threw an error during the init run.",recovery:"Remove the failing plugin from the `plugins` array in arbiter.json, then re-run `arbiter update`.",docUrl:"https://arbiter.dev/reference/cli#plugin"}],["E_PLUGIN_UNRESOLVABLE",{code:"E_PLUGIN_UNRESOLVABLE",summary:"`plugin add` could not load the given package or path",detail:"The package/path passed to `arbiter plugin add` did not resolve or failed plugin-loader validation. arbiter.json was not modified.",recovery:"Verify the package name or path is correct and, for an npm package, that it installed successfully.",docUrl:"https://arbiter.dev/reference/cli#plugin"}],["E_JSON_REQUIRES_YES",{code:"E_JSON_REQUIRES_YES",summary:"--json requires --yes",detail:"The interactive wizard reads stdin, which is incompatible with machine-readable output.",recovery:"Add `--yes` to skip the wizard: `arbiter init --yes --json`.",docUrl:"https://arbiter.dev/reference/cli#init"}],["E_TASK_NOT_FOUND",{code:"E_TASK_NOT_FOUND",summary:"Task not found",detail:"The requested task ID does not exist in the current task store.",recovery:"Run `arbiter task list` to see available task IDs.",docUrl:"https://arbiter.dev/reference/cli#task"}],["E_INVALID_PHASE",{code:"E_INVALID_PHASE",summary:"Invalid task phase",detail:"The --to flag received a phase name that does not exist in the phase sequence.",recovery:"Run `arbiter task advance --help` to see valid phase names.",docUrl:"https://arbiter.dev/reference/cli#task"}],["E_GH_NOT_INSTALLED",{code:"E_GH_NOT_INSTALLED",summary:"GitHub CLI (gh) binary not found",detail:"arbiter tried to run a GitHub API operation but the `gh` CLI binary is not installed or not on PATH.",recovery:"Install the GitHub CLI: https://cli.github.com \u2014 then run `gh auth login` to authenticate.",docUrl:"https://arbiter.dev/reference/cli#github-setup"}],["E_GH_RECOVERABLE",{code:"E_GH_RECOVERABLE",summary:"One or more GitHub API calls failed (recoverable)",detail:"A GitHub API call failed with a non-fatal error (e.g. label already exists, branch-protection requires admin access that you do not have). The command completed partial provisioning; see the list of failures above.",recovery:"Retry after granting the required permissions, or run `arbiter update --github` again once access is available.",docUrl:"https://arbiter.dev/reference/cli#github-setup"}],["E_GH_FATAL",{code:"E_GH_FATAL",summary:"GitHub API call failed with a fatal error",detail:"A GitHub API call failed with an unrecoverable error (e.g. authentication token missing or revoked, network timeout). The command was halted.",recovery:"Run `gh auth login` to re-authenticate, then retry. Check your network connectivity.",docUrl:"https://arbiter.dev/reference/cli#github-setup"}],["E_PLUGIN_FATAL",{code:"E_PLUGIN_FATAL",summary:"A plugin failed during command execution",detail:"A plugin raised a fatal error during generator execution (e.g. missing dependency, load failure, or runtime exception). The command was halted.",recovery:"Remove the failing plugin from the `plugins` array in arbiter.json, then re-run `arbiter update`.",docUrl:"https://arbiter.dev/reference/cli#plugins"}],["ENOSPC",{code:"ENOSPC",summary:"Disk full",detail:"A write failed because the filesystem ran out of space.",recovery:"Free up disk space (check with `df -h`) and retry.",docUrl:FS_ERRNO_DOC_URL}],["EACCES",{code:"EACCES",summary:"Permission denied",detail:"An fs operation failed because the process lacks permission for the target path.",recovery:"Check file ownership and directory permissions, then retry.",docUrl:FS_ERRNO_DOC_URL}],["EROFS",{code:"EROFS",summary:"Read-only filesystem",detail:"A write failed because the target filesystem is mounted read-only.",recovery:"Check mount options (e.g. `mount | grep <device>`) and remount read-write.",docUrl:FS_ERRNO_DOC_URL}],["EDQUOT",{code:"EDQUOT",summary:"Disk quota exceeded",detail:"A write failed because the user or project disk quota has been exceeded.",recovery:"Free up space or ask an administrator to raise your disk quota.",docUrl:FS_ERRNO_DOC_URL}],["EPERM",{code:"EPERM",summary:"Operation not permitted",detail:"A write failed for a reason other than plain permission bits \u2014 e.g. an immutable file attribute, SELinux/AppArmor policy, or ACL.",recovery:"Check the immutable bit (`lsattr`), SELinux/AppArmor context, and ACLs.",docUrl:FS_ERRNO_DOC_URL}],["ENOTDIR",{code:"ENOTDIR",summary:"Not a directory",detail:"An fs operation failed because a component of the target path is a file, not a directory.",recovery:"Check the target path \u2014 an intermediate segment exists as a file.",docUrl:FS_ERRNO_DOC_URL}],["EISDIR",{code:"EISDIR",summary:"Is a directory",detail:"An fs operation failed because the target path is a directory, not a file.",recovery:"Point the command at a file path, not a directory.",docUrl:FS_ERRNO_DOC_URL}],["ENOENT",{code:"ENOENT",summary:"No such file or directory",detail:"An fs operation failed because the target path (or its parent directory) does not exist.",recovery:"Create the parent directory first, e.g. `mkdir -p <dir>`, then retry.",docUrl:FS_ERRNO_DOC_URL}],["EBUSY",{code:"EBUSY",summary:"Resource busy or locked",detail:"An fs operation failed because the target file is open or locked by another process.",recovery:"Close the file in the other process and retry.",docUrl:FS_ERRNO_DOC_URL}],["EMFILE",{code:"EMFILE",summary:"Too many open files",detail:"An fs operation failed because the process hit its open-file-descriptor limit.",recovery:"Close other open files or raise the limit with `ulimit -n`, then retry.",docUrl:FS_ERRNO_DOC_URL}],["E_GATE_MUTEX_UNSUPPORTED",{code:"E_GATE_MUTEX_UNSUPPORTED",summary:"flock(1) unavailable \u2014 gate mutex unsupported on this platform",detail:"`arbiter gate-exec` delegates the per-repo gate mutex to util-linux flock(1) because the kernel-backed lock survives killing the Arbiter Node PID alone and releases after the gate-exec supervisor is SIGKILL/OOM-killed and its process group is torn down. On platforms without flock (macOS base system, Windows) the mutex cannot be provided safely, and a lockfile emulation would reintroduce the SIGKILL hole \u2014 so gate-exec fails closed (ADR-103).",recovery:"Run the wave serially (`--max-parallel 1` \u2014 no mutex needed), or install flock (util-linux) and retry."}]]);
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{t}from"../i18n/index.js";export class UserFacingError extends Error{constructor(message){super(message),this.name="UserFacingError"}}export class ArbiterError extends UserFacingError{code;hint;docUrl;constructor(code,message,opts){super(message),this.name="ArbiterError",this.code=code,this.hint=opts?.hint,this.docUrl=opts?.docUrl}static fromKey(code,key,params,opts){return new ArbiterError(code,t(key,params),opts)}}export class RecoverableError extends ArbiterError{kind="recoverable";constructor(code,message,opts){super(code,message,opts),this.name="RecoverableError"}}export class FatalError extends ArbiterError{kind="fatal";recoverableContext;constructor(code,message,opts){super(code,message,opts),this.name="FatalError",this.recoverableContext=opts?.recoverableContext}}export class ConfigError extends ArbiterError{kind="config";constructor(code,message,opts){super(code,message,opts),this.name="ConfigError"}}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{existsSync,statSync}from"node:fs";import{appendFileTranslated,ensureDir,renameTranslated}from"./fs.js";import{dirname,join,resolve}from"node:path";const LOG_FILENAME="cmd-log.jsonl",DEFAULT_MAX_BYTES=10*1024*1024,INFORMATIONAL_COMMANDS=new Set(["","-V","--version","-h","--help","help"]);export function evidenceLogTarget(cwd,cmd){if(INFORMATIONAL_COMMANDS.has(cmd))return null;let dir=resolve(cwd);for(;;){if(existsSync(join(dir,"arbiter.json")))return dir;const parent=dirname(dir);if(parent===dir)return null;dir=parent}}let rotationCounter=0;export function appendEvidenceLine(entry,opts={}){if(!opts.noEvidence)try{const root=opts.dir??process.cwd(),evidenceDir=join(root,".evidence"),logPath=join(evidenceDir,LOG_FILENAME),maxBytes=opts.maxBytes??DEFAULT_MAX_BYTES;ensureDir(evidenceDir);let shouldRotate=!1;try{statSync(logPath).size>=maxBytes&&(shouldRotate=!0)}catch{}if(shouldRotate){const backupName=`${LOG_FILENAME}.${process.pid}.${Date.now()}.${++rotationCounter}`;try{renameTranslated(logPath,join(evidenceDir,backupName))}catch{}}appendFileTranslated(logPath,JSON.stringify(entry)+`
|
|
3
|
+
`)}catch{}}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{readFileSync,lstatSync,existsSync}from"node:fs";import{createExclusiveTranslated,renameTranslated,unlinkTranslated}from"./fs.js";import{resolve}from"node:path";import os from"node:os";import{randomBytes}from"node:crypto";import{ArbiterError}from"./errors.js";const DEFAULT_STALE_MS=3600*1e3,CLEANUP_SIGNALS=["SIGINT","SIGTERM","SIGHUP"],heldLocks=new Map;function makeLockHandle(key,lockPath){let released=!1;return{path:lockPath,pid:process.pid,release:()=>{if(released)return Promise.resolve();released=!0;const held=heldLocks.get(key);if(!held)return Promise.resolve();if(held.count-=1,held.count<=0){process.removeListener("exit",held.deleteLock);for(const sig of CLEANUP_SIGNALS)process.removeListener(sig,held.signalCleanup);heldLocks.delete(key),held.deleteLock()}return Promise.resolve()}}}function readBootId(){try{return readFileSync("/proc/sys/kernel/random/boot_id","utf-8").trim()}catch{return"unknown"}}function currentBootId(){return readBootId()}function sanitize(s){return s.replaceAll("\x1B"," ").replace(/[\r\n]/g," ").slice(0,200)}export function readLockInfo(path){try{const raw=readFileSync(path,"utf-8"),parsed=JSON.parse(raw);return typeof parsed.pid!="number"||typeof parsed.hostname!="string"||typeof parsed.bootId!="string"||typeof parsed.startedAt!="string"||typeof parsed.cmd!="string"||typeof parsed.nonce!="string"?null:parsed}catch{return null}}function isSymlink(path){try{return lstatSync(path).isSymbolicLink()}catch{return!1}}function isPidAlive(pid){try{return process.kill(pid,0),!0}catch(e){const code=e.code;return code==="ESRCH"?!1:code==="EPERM"?"unknown-user":!1}}export function isLockStale(info,staleAgeMs){if(info.hostname!==os.hostname())return!1;if(info.bootId!==currentBootId())return!0;const alive=isPidAlive(info.pid);return alive===!0?!1:alive===!1?!0:Date.now()-new Date(info.startedAt).getTime()>staleAgeMs}function writeLockExclusive(path,info){const content=JSON.stringify(info);try{createExclusiveTranslated(path,content)}catch(e){if(e.code==="EEXIST"){const err=ArbiterError.fromKey("E_LOCK_CONFLICT","errors.E_LOCK_CONFLICT",{path});throw err.name="LockConflictError",err}throw e}}function buildLockInfo(){return{pid:process.pid,hostname:os.hostname(),bootId:currentBootId(),startedAt:new Date().toISOString(),cmd:process.argv.slice(1).join(" ").slice(0,200),nonce:randomBytes(8).toString("hex")}}function tryTakeover(lockPath,info,staleAgeMs){if(info.hostname!==os.hostname()||!isLockStale(info,staleAgeMs))return!1;const staleMarker=`${lockPath}.stale-${process.pid}-${randomBytes(4).toString("hex")}`;try{renameTranslated(lockPath,staleMarker)}catch{return!1}try{unlinkTranslated(staleMarker)}catch{}return!0}export async function acquireLock(lockPath,opts={}){const staleAgeMs=opts.staleAgeMs??DEFAULT_STALE_MS,key=resolve(lockPath),alreadyHeld=heldLocks.get(key);if(alreadyHeld)return alreadyHeld.count+=1,makeLockHandle(key,lockPath);if(isSymlink(lockPath))throw ArbiterError.fromKey("E_LOCK_SYMLINK","errors.E_LOCK_SYMLINK",{path:lockPath});const info=buildLockInfo(),attempt=async retries=>{try{writeLockExclusive(lockPath,info)}catch(e){if(!(e.code==="EEXIST"||e instanceof Error&&e.name==="LockConflictError")||retries===0)throw e;const existing=readLockInfo(lockPath);if(!existing){await attempt(retries-1);return}if(existing.hostname!==os.hostname())throw ArbiterError.fromKey("E_LOCK_FOREIGN_HOST","errors.E_LOCK_FOREIGN_HOST",{host:sanitize(existing.hostname)});if(tryTakeover(lockPath,existing,staleAgeMs)){await attempt(retries-1);return}const age=Math.round((Date.now()-new Date(existing.startedAt).getTime())/1e3);throw ArbiterError.fromKey("E_LOCK_BUSY","errors.E_LOCK_BUSY",{pid:existing.pid,host:sanitize(existing.hostname),cmd:sanitize(existing.cmd),age})}};await attempt(3);const ourNonce=info.nonce;function deleteLock(){try{readLockInfo(lockPath)?.nonce===ourNonce&&unlinkTranslated(lockPath)}catch{}}const signalCleanup=()=>{try{deleteLock()}catch{}process.exit(130)};process.once("exit",deleteLock);for(const sig of CLEANUP_SIGNALS)process.once(sig,signalCleanup);return heldLocks.set(key,{count:1,nonce:ourNonce,deleteLock,signalCleanup}),makeLockHandle(key,lockPath)}export function inspectLock(lockPath){return existsSync(lockPath)?Promise.resolve(readLockInfo(lockPath)):Promise.resolve(null)}export function forceReleaseLock(lockPath,expectedPid,_rootDir,opts={}){const cwd=_rootDir??process.cwd(),resolved=resolve(lockPath);if(!resolved.startsWith(cwd+"/")&&resolved!==cwd)return Promise.reject(ArbiterError.fromKey("E_LOCK_PATH_ESCAPE","errors.E_LOCK_PATH_ESCAPE",{path:resolved}));if(isSymlink(lockPath))return Promise.reject(ArbiterError.fromKey("E_LOCK_SYMLINK_UNLINK","errors.E_LOCK_SYMLINK_UNLINK",{path:lockPath}));const info=readLockInfo(lockPath);return!info&&!opts.allowUnreadable?Promise.reject(ArbiterError.fromKey("E_LOCK_UNREADABLE","errors.E_LOCK_UNREADABLE",{path:lockPath})):info&&info.pid!==expectedPid?Promise.reject(ArbiterError.fromKey("E_LOCK_PID_CHANGED","errors.E_LOCK_PID_CHANGED",{expected:expectedPid,found:info.pid})):(unlinkTranslated(lockPath),Promise.resolve())}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{existsSync}from"node:fs";import{ensureDir,writeFileTranslated}from"./fs.js";import{join}from"node:path";import{homedir}from"node:os";const MARKER_REL=".arbiter/first-run-seen",BANNER=`
|
|
3
|
+
\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
|
|
4
|
+
\u2502 arbiter collects ZERO telemetry. \u2502
|
|
5
|
+
\u2502 No analytics. No tracking. No network calls home. \u2502
|
|
6
|
+
\u2502 See PRIVACY.md in the generated project files. \u2502
|
|
7
|
+
\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
|
|
8
|
+
`;export function showTelemetryBannerIfFirstRun(homeOverrideOrQuiet,quiet){let home,isQuiet;typeof homeOverrideOrQuiet=="string"?(home=homeOverrideOrQuiet,isQuiet=quiet??!1):(home=homedir(),isQuiet=homeOverrideOrQuiet??!1);const markerPath=join(home,MARKER_REL);existsSync(markerPath)||(ensureDir(join(home,".arbiter")),writeFileTranslated(markerPath,new Date().toISOString()),isQuiet||process.stderr.write(BANNER))}
|
package/dist/utils/fs.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{constants as fsConstants,existsSync,mkdirSync,writeFileSync,copyFileSync,renameSync,unlinkSync,readFileSync,appendFileSync,chmodSync,rmSync,symlinkSync,mkdtempSync,cpSync,openSync,closeSync,fstatSync}from"node:fs";import{dirname,isAbsolute,join,resolve}from"node:path";import{randomBytes,createHash}from"node:crypto";import{ArbiterError}from"./errors.js";import{getLogger}from"./logger.js";import{t}from"../i18n/index.js";import{manifestKey}from"../state/manifest-key.js";const inFlightTmpPaths=new Set;let handlersRegistered=!1;const FS_ERROR_KEYS={ENOSPC:"errors.E_FS_ENOSPC",EACCES:"errors.E_FS_EACCES",EROFS:"errors.E_FS_EROFS",EDQUOT:"errors.E_FS_EDQUOT",EPERM:"errors.E_FS_EPERM",ENOTDIR:"errors.E_FS_ENOTDIR",EISDIR:"errors.E_FS_EISDIR",ENOENT:"errors.E_FS_ENOENT",EBUSY:"errors.E_FS_EBUSY",EMFILE:"errors.E_FS_EMFILE"};export function toFsError(err,path){const e=err,key=FS_ERROR_KEYS[e.code??""];return key?ArbiterError.fromKey(e.code??"",key,{path:e.path??path}):e}function atomicWrite(filePath,content){const tmpPath=`${filePath}.arbiter-tmp-${randomBytes(4).toString("hex")}`;inFlightTmpPaths.add(tmpPath);try{writeFileSync(tmpPath,content,"utf-8"),renameSync(tmpPath,filePath)}catch(err){try{unlinkSync(tmpPath)}catch{}throw toFsError(err,filePath)}finally{inFlightTmpPaths.delete(tmpPath)}}function doCleanup(){for(const p of inFlightTmpPaths){try{unlinkSync(p)}catch(err){const code=err.code;code!=="ENOENT"&&process.stderr.write(`[arbiter] warning: could not remove in-flight tmp file ${p} (${code??"unknown"}) \u2014 remove manually
|
|
3
|
+
`)}inFlightTmpPaths.delete(p)}}export function cleanupInFlightTmpFiles(){doCleanup()}export function registerCleanupHandlers(){if(!handlersRegistered){handlersRegistered=!0;for(const signal of["SIGTERM","SIGINT"])process.once(signal,()=>{doCleanup(),process.listenerCount(signal)===0&&process.kill(process.pid,signal)})}}export function _registerTmpPath(p){inFlightTmpPaths.add(p)}export function _translateFsError(code,path){const key=FS_ERROR_KEYS[code];return key?t(key,{path}):null}let generationSession=null;function sha256(content){return createHash("sha256").update(content).digest("hex")}export function beginGenerationSession(opts){if(generationSession!==null)throw new Error("beginGenerationSession: a generation session is already active. This means a prior session was not ended (missing endGenerationSession in a finally block) or a nested/concurrent generation was attempted \u2014 both unsupported, since writeFile reads a single module-level session.");generationSession={targetDir:opts.targetDir,prevHashes:new Map(Object.entries(opts.prevHashes)),newHashes:new Map,...opts.onWithheld?{onWithheld:opts.onWithheld}:{},...opts.adoptPredicate?{adoptPredicate:opts.adoptPredicate}:{},...opts.selectPredicate?{selectPredicate:opts.selectPredicate}:{},...opts.onAdopt?{onAdopt:opts.onAdopt}:{}}}export function endGenerationSession(){const session=generationSession;return generationSession=null,session?Object.fromEntries(session.newHashes):{}}function recordGeneratedHash(filePath,content){const session=generationSession;if(!session)return;const key=manifestKey(session.targetDir,filePath);if(key===null){getLogger().warn("fs.manifest_key_skipped",{path:filePath},`generated-manifest: skipping non-relative path (cannot key portably): ${filePath}`);return}session.newHashes.set(key,sha256(content))}function resolveExclusion(session,filePath){if(!session.selectPredicate)return null;const key=manifestKey(session.targetDir,filePath);if(key===null)return null;const verdict=session.selectPredicate(key);return verdict==="emit"?null:{action:"skipped",baselineMatches:!1,withheld:!1,adopted:!1,excluded:verdict}}function hasRecordedBaseline(filePath){const session=generationSession;if(!session)return!1;const key=manifestKey(session.targetDir,filePath);return key!==null&&session.prevHashes.get(key)!==void 0}function resolveSessionSkip(session,filePath,content,disk,opts){const{backup,skipIfExists}=opts,key=manifestKey(session.targetDir,filePath),prev=key===null?void 0:session.prevHashes.get(key),rewrite={action:backup?"backed-up-and-replaced":"replaced",baselineMatches:!0,withheld:!1,adopted:!1};if(prev!==void 0&&sha256(disk)===prev||!skipIfExists&&prev===void 0)return rewrite;const withheldKey=key??filePath;return session.adoptPredicate?.(withheldKey,prev!==void 0)?(session.onAdopt?.(withheldKey,disk,content),{action:backup?"backed-up-and-replaced":"replaced",baselineMatches:!0,withheld:!0,adopted:!0}):((session.onWithheld??defaultWithheldWarn)(withheldKey),{action:"skipped",baselineMatches:!1,withheld:!0,adopted:!1})}function resolveWriteAction(filePath,content,skipIfExists,backup,skipPreserveCheck){const session=generationSession;if(session){const excluded=resolveExclusion(session,filePath);if(excluded)return excluded}if(!existsSync(filePath))return{action:"created",baselineMatches:!0,withheld:!1,adopted:!1,restored:hasRecordedBaseline(filePath)};let disk;try{disk=readFileSync(filePath,"utf-8")}catch{disk=null}if(disk!==null&&disk===content)return{action:"skipped",baselineMatches:!0,withheld:!1,adopted:!1};if(hasPreserveMarker(disk,skipPreserveCheck)){if(session){const key=manifestKey(session.targetDir,filePath);(session.onWithheld??defaultWithheldWarn)(key??filePath)}return{action:"skipped",baselineMatches:!1,withheld:!0,adopted:!1}}return session&&disk!==null?resolveSessionSkip(session,filePath,content,disk,{backup,skipIfExists}):skipIfExists?{action:"skipped",baselineMatches:!1,withheld:!1,adopted:!1}:{action:backup?"backed-up-and-replaced":"replaced",baselineMatches:!0,withheld:!1,adopted:!1}}export const PRESERVE_MARKER="arbiter:preserve";function hasPreserveMarker(disk,skip){return!skip&&disk!==null&&disk.includes(PRESERVE_MARKER)}function defaultWithheldWarn(key){getLogger().warn("fs.fix_withheld",{path:key},`user-modified, template fix NOT applied: ${key} (review with \`arbiter diff --withheld\`; adopt every withheld fix with \`arbiter update --adopt\`)`)}function normalizeWriteFileOpts(opts){return{skipIfExists:opts.skipIfExists??!1,backup:opts.backup??!1,dryRun:opts.dryRun??!1,skipPreserveCheck:opts.skipPreserveCheck??!1}}export function writeFile(filePath,content,opts={}){const{skipIfExists,backup,dryRun,skipPreserveCheck}=normalizeWriteFileOpts(opts),{action,baselineMatches,withheld,adopted,restored,excluded}=resolveWriteAction(filePath,content,skipIfExists,backup,skipPreserveCheck);return!dryRun&&action!=="skipped"&&(action==="backed-up-and-replaced"&©FileSync(filePath,`${filePath}.arbiter-backup`),mkdirSync(dirname(filePath),{recursive:!0}),atomicWrite(filePath,content)),!dryRun&&baselineMatches&&recordGeneratedHash(filePath,content),excluded!==void 0?{path:filePath,action,excluded}:withheld&&adopted?{path:filePath,action,withheld:!0,adopted:!0}:restored===!0?{path:filePath,action,restored:!0}:withheld?{path:filePath,action,withheld:!0}:{path:filePath,action}}export function assertWritten(result,description){if(result.withheld===!0&&result.adopted!==!0)throw new Error(`refusing to report success: ${description} was not written to ${result.path} \u2014 the write was withheld (the existing on-disk content could not be safely overwritten, e.g. it carries the \`${PRESERVE_MARKER}\` marker). Delete the stale file, or its marker, and retry.`)}export function writeFileTranslated(path,data){try{writeFileSync(path,data)}catch(err){throw toFsError(err,path)}}function containedParts(relativePath){if(isAbsolute(relativePath))throw new Error(`contained path must be relative: ${relativePath}`);const parts=relativePath.split(/[\\/]+/);if(parts.some(part=>part===""||part==="."||part===".."))throw new Error(`contained path has invalid components: ${relativePath}`);return parts}function openContainedDirectory(rootDir,directoryParts){if(process.platform==="win32")throw new Error("descriptor-relative filesystem operations are unsupported on Windows");const flags=fsConstants.O_RDONLY|fsConstants.O_DIRECTORY|fsConstants.O_NOFOLLOW;let fd=-1;try{fd=openSync("/",flags);const rootParts=resolve(rootDir).split("/").filter(Boolean);for(const part of[...rootParts,...directoryParts]){const child=`${process.platform==="linux"?"/proc/self/fd":"/dev/fd"}/${fd}/${part}`;try{mkdirSync(child)}catch(err){if(err.code!=="EEXIST")throw toFsError(err,child)}const next=openSync(child,flags);closeSync(fd),fd=next}return fd}catch(err){throw fd!==-1&&closeSync(fd),toFsError(err,rootDir)}}function writeFileContained(rootDir,relativePath,data){const parts=containedParts(relativePath),fileName=parts.pop(),fullPath=join(rootDir,relativePath);let dirFd=-1,tempFd=-1,tempPath=null;try{dirFd=openContainedDirectory(rootDir,parts);const dirPath=`${process.platform==="linux"?"/proc/self/fd":"/dev/fd"}/${dirFd}`;tempPath=join(dirPath,`.arbiter-tmp-${randomBytes(4).toString("hex")}`),tempFd=openSync(tempPath,fsConstants.O_WRONLY|fsConstants.O_CREAT|fsConstants.O_EXCL|fsConstants.O_NOFOLLOW,384),writeFileSync(tempFd,data),closeSync(tempFd),tempFd=-1,renameSync(tempPath,join(dirPath,fileName)),tempPath=null}catch(err){if(tempFd!==-1&&closeSync(tempFd),tempPath!==null)try{unlinkSync(tempPath)}catch{}throw toFsError(err,fullPath)}finally{dirFd!==-1&&closeSync(dirFd)}}function readFileContained(rootDir,relativePath){const parts=containedParts(relativePath),fileName=parts.pop(),fullPath=join(rootDir,relativePath);let dirFd=-1,fileFd=-1;try{if(dirFd=openContainedDirectory(rootDir,parts),fileFd=openSync(`${process.platform==="linux"?"/proc/self/fd":"/dev/fd"}/${dirFd}/${fileName}`,fsConstants.O_RDONLY|fsConstants.O_NONBLOCK|fsConstants.O_NOFOLLOW),!fstatSync(fileFd).isFile())throw new Error(`not a regular file: ${fullPath}`);return readFileSync(fileFd,"utf8")}catch(err){throw toFsError(err,fullPath)}finally{fileFd!==-1&&closeSync(fileFd),dirFd!==-1&&closeSync(dirFd)}}export{writeFileContained,readFileContained};export function openAppendDescriptorTranslated(path){try{return openSync(path,"a")}catch(err){throw toFsError(err,path)}}export function closeDescriptorTranslated(fd,path){try{closeSync(fd)}catch(err){throw toFsError(err,path)}}export function ensureDir(path){try{mkdirSync(path,{recursive:!0})}catch(err){throw toFsError(err,path)}}export function appendFileTranslated(path,data){try{appendFileSync(path,data)}catch(err){throw toFsError(err,path)}}export function copyFileTranslated(src,dest){try{copyFileSync(src,dest)}catch(err){throw toFsError(err,dest)}}export function renameTranslated(from,to){try{renameSync(from,to)}catch(err){throw toFsError(err,to)}}export function chmodTranslated(path,mode){try{chmodSync(path,mode)}catch(err){throw toFsError(err,path)}}export function unlinkTranslated(path){try{unlinkSync(path)}catch(err){throw toFsError(err,path)}}export function rmTranslated(path,opts){try{rmSync(path,opts)}catch(err){throw toFsError(err,path)}}export function symlinkTranslated(target,path,type){try{symlinkSync(target,path,type)}catch(err){throw toFsError(err,path)}}export function mkdtempTranslated(prefix){try{return mkdtempSync(prefix)}catch(err){throw toFsError(err,prefix)}}export function createExclusiveTranslated(path,content){try{writeFileSync(path,content,{flag:"wx"})}catch(err){throw err.code==="EEXIST"?err:toFsError(err,path)}}export function copyTreeTranslated(src,dest,opts={}){try{cpSync(src,dest,opts)}catch(err){throw toFsError(err,dest)}}export function readFileTranslated(path,encoding){try{return encoding?readFileSync(path,encoding):readFileSync(path)}catch(err){throw toFsError(err,path)}}export function mergeSettingsJson(existing,incoming){const result={...existing},dropped=[];for(const[key,incomingVal]of Object.entries(incoming)){const existingVal=existing[key];key==="hooks"&&isHooksObject(incomingVal)&&isHooksObject(existingVal)?result[key]=mergeHooks(existingVal,incomingVal):key==="permissions"&&isPermissions(incomingVal)&&isPermissions(existingVal)?result[key]=mergePermissions(existingVal,incomingVal):existingVal===void 0?result[key]=incomingVal:dropped.push(key)}return dropped.length>0&&getLogger().warn("fs.settings_merge_preserved",{dropped_keys:dropped.join(",")},`settings.json merge preserved your existing values for these top-level keys; arbiter's new defaults were NOT applied: ${dropped.join(", ")}. Remove these keys from .claude/settings.json and re-run to pick up the new defaults.`),result}function isHooksObject(val){return typeof val=="object"&&val!==null&&!Array.isArray(val)}function isPermissions(val){return typeof val=="object"&&val!==null&&!Array.isArray(val)}const ARBITER_HOOK_BASENAMES=new Set(["stop-dangerous","enforce-read-only","pre-edit-ssot-guard","check-no-orphan-todo","check-no-placeholders","lib","post-commit-check","check-no-unused-exports","pre-edit-plan-anchor","pre-compact","post-edit-dispatch","debug-state-on-failure","skill-forced-eval","guard-task-completion","guard-done-evidence","check-circular-deps","check-no-pii","hooks"]);function extractHookBasename(command){return command.match(/\.claude\/hooks\/([^./\s]+)\.\w+/)?.[1]??null}function isDispatcherCommand(command){return/\.claude\/hooks\/hooks\.mjs\b/.test(command)}function mergeHooks(existing,incoming){const result={...existing};for(const[event,incomingEntries]of Object.entries(incoming)){const merged=[...existing[event]??[]];for(const incomingEntry of incomingEntries){const existingEntry=merged.find(e=>e.matcher===incomingEntry.matcher);existingEntry?mergeHookEntry(existingEntry,incomingEntry,event):merged.push(incomingEntry)}result[event]=merged}return result}function mergeHookEntry(existingEntry,incomingEntry,event){const hasDispatcherIncoming=incomingEntry.hooks.some(h=>isDispatcherCommand(h.command));for(const hook of incomingEntry.hooks){if(hasDispatcherIncoming&&isDispatcherCommand(hook.command)){const existingDispatcher=existingEntry.hooks.find(h=>isDispatcherCommand(h.command));if(existingEntry.hooks=existingEntry.hooks.filter(h=>{const basename=extractHookBasename(h.command);return isDispatcherCommand(h.command)||basename===null||!ARBITER_HOOK_BASENAMES.has(basename)}),existingDispatcher&&existingDispatcher.command!==hook.command){getLogger().warn("fs.hook_command_preserved",{hook_event:event,matcher:existingEntry.matcher,existing_command:existingDispatcher.command,incoming_command:hook.command},`settings.json merge preserved your existing dispatcher command for matcher ${existingEntry.matcher}; arbiter's dispatcher command was NOT applied.`);continue}}else{const incomingBasename=extractHookBasename(hook.command);incomingBasename&&(existingEntry.hooks=existingEntry.hooks.filter(h=>extractHookBasename(h.command)!==incomingBasename))}new Set(existingEntry.hooks.map(h=>h.command)).has(hook.command)||existingEntry.hooks.push(hook)}}function mergePermissions(existing,incoming){const unionArrays=(a=[],b=[])=>[...new Set([...a,...b])];return{allow:unionArrays(existing.allow,incoming.allow),deny:unionArrays(existing.deny,incoming.deny)}}export function resolvedPath(targetDir,...parts){return join(targetDir,...parts)}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{existsSync}from"node:fs";import{join}from"node:path";import{writeFile,readFileTranslated,assertWritten}from"./fs.js";import{runCli,CliError}from"./run-cli.js";function parseIssueNumber(stdout){const issueStr=stdout.split(`
|
|
3
|
+
`).find(l=>/\/issues\/\d+$/.test(l.trim()))?.trim().match(/\/issues\/(\d+)$/)?.[1];return issueStr!==void 0?Number.parseInt(issueStr,10):null}export function createGhIssue(dir,input){const labelArgs=input.labels.flatMap(label=>["--label",label]);let stdout;try{stdout=runCli("gh",["issue","create","--title",input.title,"--body",input.body,...labelArgs],{cwd:dir,timeoutMs:3e4}).stdout}catch(err){if(err instanceof CliError)return err.notFound?{ok:!1,reason:"gh not installed"}:err.timedOut?{ok:!1,reason:"gh timed out"}:{ok:!1,reason:`gh exit ${err.exitCode}: ${err.stderr.slice(-200)}`};throw err}const issueNumber=parseIssueNumber(stdout);return issueNumber===null?{ok:!1,reason:`gh returned non-integer issue number: ${JSON.stringify(stdout.trim())}`}:{ok:!0,issueNumber}}export function appendTechDebtIssue(evidenceDir,issueNumber){const tdPath=join(evidenceDir,"tech-debt.json");let issues=[];if(existsSync(tdPath)){const raw=readFileTranslated(tdPath,"utf-8");try{const parsed=JSON.parse(raw);issues=parsed!==null&&typeof parsed=="object"&&!Array.isArray(parsed)&&Array.isArray(parsed.issues)?parsed.issues.filter(v=>typeof v=="number"&&Number.isInteger(v)):[]}catch{issues=[]}}issues.push(issueNumber);const result=writeFile(tdPath,JSON.stringify({issues},null,2)+`
|
|
4
|
+
`,{skipPreserveCheck:!0});assertWritten(result,`tech-debt evidence at ${tdPath}`)}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{existsSync,readFileSync}from"node:fs";import{join}from"node:path";import{writeFile}from"./fs.js";import{getLogger}from"./logger.js";const MARKER_BEGIN="// >>> arbiter:java-tooling (managed \u2014 `arbiter update` re-adds missing lines)",MARKER_END="// <<< arbiter:java-tooling";function escapeRegExp(s){return s.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function readFileOrNull(path){try{return readFileSync(path,"utf-8")}catch{return null}}export function findRootBuildFile(targetDir){for(const name of["build.gradle.kts","build.gradle"]){const p=join(targetDir,name);if(existsSync(p))return p}return null}function findPluginsBlock(content){const m=/(?:^|\n)[ \t]*plugins\s*\{/.exec(content);if(!m)return null;const open=m.index+m[0].length-1,close=matchBrace(content,open);return close<0?null:{open,close}}function matchBrace(content,openIdx){let depth=0;for(let i=openIdx;i<content.length;i++)if(content[i]==="{")depth++;else if(content[i]==="}"&&--depth===0)return i;return-1}function hasPlugin(content,pluginsBlock,id){return content.includes(`"${id}"`)||content.includes(`'${id}'`)?!0:new RegExp(`(?:^|[^\\w.])${escapeRegExp(id)}(?:[^\\w.]|$)`).test(pluginsBlock)}function pluginLine(dsl,spec,indent){if(dsl==="kts"){const base2=`${indent}id("${spec.id}")`;return spec.version?`${base2} version "${spec.version}"`:base2}const base=`${indent}id '${spec.id}'`;return spec.version?`${base} version '${spec.version}'`:base}function pluginsBlockInsertionOffset(content){const lines=content.split(`
|
|
3
|
+
`);let offset=0,inBlockComment=!1;for(const line of lines){const trimmed=line.trim(),lineSpan=line.length+1;if(inBlockComment){offset+=lineSpan,trimmed.includes("*/")&&(inBlockComment=!1);continue}if(trimmed===""||trimmed.startsWith("//")||trimmed.startsWith("import ")){offset+=lineSpan;continue}if(trimmed.startsWith("/*")){offset+=lineSpan,trimmed.includes("*/")||(inBlockComment=!0);continue}if(/^buildscript\s*\{/.test(trimmed)){const open=content.indexOf("{",offset),close=matchBrace(content,open);if(close<0)return offset;const nl=content.indexOf(`
|
|
4
|
+
`,close);return nl<0?content.length:nl+1}return offset}return offset}function ensurePlugins(content,dsl,plugins){if(plugins.length===0)return content;const block=findPluginsBlock(content),blockSrc=block?content.slice(block.open,block.close+1):"",missing=plugins.filter(p=>!hasPlugin(content,blockSrc,p.id));if(missing.length===0)return content;if(block){const inner=content.slice(block.open+1,block.close),indent=/\n([ \t]+)\S/.exec(inner)?.[1]??" ",lines2=missing.map(p=>pluginLine(dsl,p,indent)).join(`
|
|
5
|
+
`),remainder=content.slice(block.open+1),separator=remainder.startsWith(`
|
|
6
|
+
`)?"":`
|
|
7
|
+
`;return content.slice(0,block.open+1)+`
|
|
8
|
+
`+lines2+separator+remainder}const newBlock=`plugins {
|
|
9
|
+
${missing.map(p=>pluginLine(dsl,p," ")).join(`
|
|
10
|
+
`)}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
`,at=pluginsBlockInsertionOffset(content);return content.slice(0,at)+newBlock+content.slice(at)}function dependencySnippet(dsl,dep){const conf=dep.configuration??"testImplementation";return dsl==="kts"?`dependencies { "${conf}"("${dep.coordinate}") }`:`dependencies { ${conf} '${dep.coordinate}' }`}function dependencyAsSnippet(dep){const groupArtifact=dep.coordinate.split(":").slice(0,2).join(":");return{signature:new RegExp(escapeRegExp(groupArtifact)),kts:dependencySnippet("kts",dep),groovy:dependencySnippet("groovy",dep)}}function ensureSnippets(content,dsl,snippets){const missing=snippets.filter(s=>!s.signature.test(content));if(missing.length===0)return content;const body=missing.map(s=>dsl==="kts"?s.kts:s.groovy).join(`
|
|
14
|
+
|
|
15
|
+
`),beginIdx=content.indexOf(MARKER_BEGIN),endIdx=beginIdx>=0?content.indexOf(MARKER_END,beginIdx):-1;return beginIdx>=0&&endIdx>=0?content.slice(0,endIdx)+body+`
|
|
16
|
+
`+content.slice(endIdx):`${content.replace(/\n*$/,"")}
|
|
17
|
+
|
|
18
|
+
${MARKER_BEGIN}
|
|
19
|
+
${body}
|
|
20
|
+
${MARKER_END}
|
|
21
|
+
`}export function safeApplyFromSnippet(targetDir,relPath,opts={}){const scriptPath=join(targetDir,relPath);if(!existsSync(scriptPath))return null;let script;try{script=readFileSync(scriptPath,"utf-8")}catch{return null}if(/(?:^|\n)[ \t]*plugins\s*\{/.test(script))return getLogger().warn("gradle.apply_from_withheld",{path:scriptPath},`gradle wiring: NOT applying ${relPath} \u2014 it contains a plugins {} block, which Gradle forbids in applied scripts. Remove the block (or re-generate via arbiter update) and re-run.`),null;if(/(?:^|\n)[ \t]*import[ \t]+(?:com\.github\.spotbugs|com\.diffplug)\b/.test(script))return getLogger().warn("gradle.apply_from_withheld",{path:scriptPath},`gradle wiring: NOT applying ${relPath} \u2014 it still imports a plugin-provided class, which Gradle cannot resolve inside an applied script (classloader isolation). Delete the file (its config now lives inline in the root build via arbiter update) or remove the import.`),null;if(opts.rootBuildSignatures?.length){const rootBuildFile=findRootBuildFile(targetDir),rootContent=rootBuildFile?readFileOrNull(rootBuildFile):null;if(rootContent!==null&&opts.rootBuildSignatures.some(sig=>sig.test(rootContent)))return getLogger().warn("gradle.apply_from_withheld",{path:scriptPath},`gradle wiring: NOT applying ${relPath} \u2014 the root build already configures this tooling inline (brownfield). Remove the inline block first if you want arbiter to manage it via ${relPath} instead, then re-run arbiter update.`),null}return{signature:new RegExp(`apply[^\\n]*["']${escapeRegExp(relPath)}["']`),kts:`apply(from = "${relPath}")`,groovy:`apply from: '${relPath}'`}}export function injectGradleWiring(targetDir,dryRun,req){if(dryRun)return{changed:!1,buildFile:findRootBuildFile(targetDir)};const buildFile=findRootBuildFile(targetDir);if(!buildFile)return getLogger().warn("gradle.no_build_file",{targetDir},"gradle wiring: no build.gradle(.kts) found at project root \u2014 tooling left unwired"),{changed:!1,buildFile:null};let content;try{content=readFileSync(buildFile,"utf-8")}catch(err){return getLogger().warn("gradle.read_failed",{path:buildFile,err:String(err)},"gradle wiring: failed to read root build script"),{changed:!1,buildFile}}const original=content,dsl=buildFile.endsWith(".kts")?"kts":"groovy";content=ensurePlugins(content,dsl,req.plugins??[]);const snippets=[...req.snippets??[],...(req.dependencies??[]).map(dependencyAsSnippet)];return content=ensureSnippets(content,dsl,snippets),content===original?{changed:!1,buildFile}:(writeFile(buildFile,content,{dryRun}),{changed:!0,buildFile})}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export function jsonOutput(command,status,data,errors,opts){const envelope={command,version:"1",status,data,...errors!==void 0&&errors.length>0?{errors}:{},...opts?.warnings!==void 0&&opts.warnings.length>0?{warnings:opts.warnings}:{},...opts?.errorClass!==void 0?{errorClass:opts.errorClass}:{},...opts?.code!==void 0?{code:opts.code}:{}};process.stdout.write(JSON.stringify(envelope)+`
|
|
2
|
+
`)}export function statusToExitCode(status){return status==="error"?2:status==="warning"?1:0}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
const VALID_LEVELS=new Set(["error","warn","info","debug","trace"]),VALID_FORMATS=new Set(["text","json"]),DEFAULT_LEVEL="info",DEFAULT_FORMAT="text";function pickLevel(flag,env){for(const candidate of[flag,env]){if(candidate===void 0)continue;const lowered=candidate.toLowerCase();if(VALID_LEVELS.has(lowered))return lowered;process.stderr.write(`[warn] ignoring invalid log level: ${candidate}
|
|
3
|
+
`)}return DEFAULT_LEVEL}function pickFormat(flag,env){for(const candidate of[flag,env]){if(candidate===void 0)continue;const lowered=candidate.toLowerCase();if(VALID_FORMATS.has(lowered))return lowered;process.stderr.write(`[warn] ignoring invalid log format: ${candidate}
|
|
4
|
+
`)}return DEFAULT_FORMAT}export function resolveLoggerConfig(inputs){return{level:pickLevel(inputs.flagLevel,inputs.envLevel),format:pickFormat(inputs.flagFormat,inputs.envFormat)}}export function resolveFromProcess(argv,env){const inputs={},flagLevel=extractFlagValue(argv,"--log-level");flagLevel!==void 0&&(inputs.flagLevel=flagLevel);const flagFormat=extractFlagValue(argv,"--log-format");return flagFormat!==void 0&&(inputs.flagFormat=flagFormat),env.ARBITER_LOG_LEVEL!==void 0&&(inputs.envLevel=env.ARBITER_LOG_LEVEL),env.ARBITER_LOG_FORMAT!==void 0&&(inputs.envFormat=env.ARBITER_LOG_FORMAT),resolveLoggerConfig(inputs)}function extractFlagValue(argv,flag){for(let i=0;i<argv.length;i++){const arg=argv[i];if(arg===flag)return argv[i+1];if(arg!==void 0&&arg.startsWith(`${flag}=`))return arg.slice(flag.length+1)}}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{AsyncLocalStorage}from"node:async_hooks";const LEVEL_RANK={error:0,warn:1,info:2,debug:3,trace:4},runContext=new AsyncLocalStorage;export class Logger{opts;constructor(opts){this.opts={level:opts.level,format:opts.format,runId:opts.runId,stream:opts.stream??process.stderr}}error(event,attrs,msg){this.emit("error",event,attrs,msg)}warn(event,attrs,msg){this.emit("warn",event,attrs,msg)}info(event,attrs,msg){this.emit("info",event,attrs,msg)}debug(event,attrs,msg){this.emit("debug",event,attrs,msg)}trace(event,attrs,msg){this.emit("trace",event,attrs,msg)}getRunId(){return runContext.getStore()?.runId??this.opts.runId}emit(level,event,attrs,msg){if(LEVEL_RANK[level]>LEVEL_RANK[this.opts.level])return;const runId=this.getRunId(),line=this.opts.format==="json"?formatJson(level,event,runId,attrs,msg):formatText(level,event,runId,attrs,msg);this.opts.stream.write(line+`
|
|
3
|
+
`)}}function formatJson(level,event,runId,attrs,msg){const record={ts:new Date().toISOString(),level,event};if(runId!==void 0&&(record.runId=runId),msg!==void 0&&(record.msg=msg),attrs)for(const[k,v]of Object.entries(attrs))v!==void 0&&!(k in record)&&(record[k]=v);return JSON.stringify(record)}function formatText(level,event,runId,attrs,msg){const parts=[`[${level}]`,event];if(msg!==void 0&&parts.push(msg),runId!==void 0&&parts.push(`runId=${runId}`),attrs)for(const[k,v]of Object.entries(attrs))v!==void 0&&parts.push(`${k}=${String(v)}`);return parts.join(" ")}let rootLogger=new Logger({level:"info",format:"text"});export function setRootLogger(opts){return rootLogger=new Logger(opts),rootLogger}export function getLogger(){return rootLogger}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{createRequire}from"node:module";const require=createRequire(import.meta.url);function loadMatrix(){return require("../compatibility/cross-language-matrix.json")}export function checkMaturity(language,feature){const featureMap=loadMatrix()[feature];if(!featureMap)return{maturity:"unavailable",tool:"N/A",reason:`No matrix entry for feature "${feature}"`};const entry=featureMap[language];return entry?{maturity:entry.maturity,tool:entry.tool,reason:entry.reason}:{maturity:"unavailable",tool:"N/A",reason:`No matrix entry for language "${language}" + feature "${feature}"`}}export function hasMatrixCell(language,feature){return loadMatrix()[feature]?.[language]!==void 0}export function isL3Allowed(language,feature,acceptBetaTools,resolve=checkMaturity){const{maturity,tool,reason}=resolve(language,feature);switch(maturity){case"proven":return{allowed:!0};case"beta":return acceptBetaTools?{allowed:!0}:{allowed:!1,errorMessage:`${tool} is marked "beta" for ${language} in the cross-language matrix. ${reason}. Use L2, disable ${feature} testing, or pass --accept-beta-tools to override.`};case"unsafe":return{allowed:!1,errorMessage:`${tool} is marked "unsafe" for ${language} in the cross-language matrix. ${reason}. Use L2 or disable ${feature} testing. --accept-beta-tools does not override unsafe tools.`};case"unavailable":return{allowed:!1,errorMessage:`${feature} is marked "unavailable" for ${language} in the cross-language matrix. ${reason}.`};default:return{allowed:!1,errorMessage:`Unexpected maturity "${String(maturity)}" for ${feature} on ${language} in the cross-language matrix (expected proven, beta, unsafe, or unavailable). Fix the matrix entry or extend MaturityLevel.`}}}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{existsSync,readFileSync}from"node:fs";import{writeFile,resolvedPath}from"./fs.js";import{getLogger}from"./logger.js";function assertNonVolatileVersion(name,version){if(/^(file:|link:|portal:)/.test(version)||/\.(tgz|tar\.gz)$/.test(version))throw new Error(`injectDevDependency: refusing volatile install channel for "${name}": "${version}". file:/link:/portal: specifiers and local .tgz paths are machine- and time-specific and break \`npm install\` for the fleet (#1314). Pin a registry version (e.g. "5.0.6") instead.`)}export function mutatePackageJson(targetDir,dryRun,mutate){if(dryRun)return;const pkgPath=resolvedPath(targetDir,"package.json");if(!existsSync(pkgPath))return;let pkg;try{pkg=JSON.parse(readFileSync(pkgPath,"utf-8"))}catch(err){getLogger().warn("pkg.mutate_parse_failed",{path:pkgPath,err:String(err)},"mutatePackageJson: failed to parse package.json");return}mutate(pkg)&&writeFile(pkgPath,JSON.stringify(pkg,null,2)+`
|
|
3
|
+
`,{dryRun})}export function injectDevDependency(targetDir,name,version,dryRun){assertNonVolatileVersion(name,version),mutatePackageJson(targetDir,dryRun,pkg=>{const devDeps=pkg.devDependencies??{};return devDeps[name]?!1:(devDeps[name]=version,pkg.devDependencies=devDeps,!0)})}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
export function isWindows(){return process.platform==="win32"}export function isWSL2(){return typeof process.env.WSL_DISTRO_NAME=="string"||typeof process.env.WSLENV=="string"&&process.env.WSLENV.length>0||typeof process.env.WSL_INTEROP=="string"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{createRequire}from"node:module";import{pathToFileURL,fileURLToPath}from"node:url";import{existsSync,readFileSync}from"node:fs";import{dirname,join}from"node:path";import{Worker}from"node:worker_threads";import{validatePluginPackageJson}from"../integrations/plugin-schema.js";import{ArbiterError}from"./errors.js";const VALID_NAME_RE=/^[a-z0-9][a-z0-9-_]*$/,DEFAULT_TIMEOUT_MS=6e4,noop=()=>{};function validateTargetDir(targetDir){for(const ch of targetDir){const code=ch.charCodeAt(0);if(code<32||code===127)throw new Error("invalid targetDir: contains NUL, control character, or DEL")}if(process.platform!=="win32"&&targetDir.includes("\\"))throw new Error("invalid targetDir: contains backslash (use forward slashes on POSIX)")}function resolveWorkerPath(){const url=import.meta.url;if(url.endsWith(".ts")){const tsxLoader=fileURLToPath(new URL("../../node_modules/tsx/dist/esm/index.mjs",url));return{path:fileURLToPath(new URL("./plugin-worker.ts",url)),execArgv:["--import",tsxLoader]}}return{path:fileURLToPath(new URL("./plugin-worker.js",url)),execArgv:[]}}function invokeInWorker(entryPath,kind,payload,name,timeoutMs){const{path:workerPath,execArgv}=resolveWorkerPath();return new Promise((resolve,reject)=>{const worker=new Worker(workerPath,{workerData:{entryPath,kind,...payload},execArgv,resourceLimits:{maxOldGenerationSizeMb:256,maxYoungGenerationSizeMb:32},env:{}});let settled=!1;const settle=fn=>{if(!settled){settled=!0,clearTimeout(timer),process.off("SIGINT",onSignal),process.off("SIGTERM",onSignal);try{fn()}catch(e){reject(e instanceof Error?e:new Error(String(e)))}}},timer=setTimeout(()=>{worker.terminate().then(noop,noop),settle(()=>{reject(ArbiterError.fromKey("E_PLUGIN_TIMEOUT","cli.plugin.timeout",{name,ms:timeoutMs}))})},timeoutMs),onSignal=()=>{worker.terminate().then(noop,noop),settle(()=>{reject(ArbiterError.fromKey("E_PLUGIN_INTERRUPTED","cli.plugin.interrupted",{name}))})};process.once("SIGINT",onSignal),process.once("SIGTERM",onSignal),worker.once("message",msg=>{settle(()=>{msg.kind==="error"?reject(new Error(`Plugin "${name}" error: ${msg.message}`)):resolve(msg.value)})}),worker.once("error",err=>{settle(()=>{reject(err)})}),worker.once("exit",code=>{settle(()=>{reject(code!==0?ArbiterError.fromKey("E_PLUGIN_CRASHED","cli.plugin.crashed",{name,code}):new Error(`Plugin "${name}" exited without returning a result`))})})})}function readPluginManifest(entry){let dir=dirname(entry);for(let depth=0;depth<64;depth++){const candidate=join(dir,"package.json");if(existsSync(candidate))try{return JSON.parse(readFileSync(candidate,"utf-8"))}catch(err){throw new Error(`package.json at ${candidate} is not valid JSON: ${err instanceof Error?err.message:String(err)}`,{cause:err})}const parent=dirname(dir);if(parent===dir)break;dir=parent}throw new Error("no package.json manifest found for plugin")}function validatePluginManifest(entry,pkg){const result=validatePluginPackageJson(readPluginManifest(entry));if(!result.ok)throw new Error(`Plugin "${pkg}" has an invalid package.json manifest: ${result.errors.join("; ")}`)}async function loadRawMod(require,entry){try{const loaded=require(entry);if(loaded===null||typeof loaded!="object")throw new Error("Plugin must export a default object.");return loaded}catch(err){if(err.code!=="ERR_REQUIRE_ESM")throw err;return await import(pathToFileURL(entry).href)}}export async function loadPlugin(pkg,targetDir,opts){validateTargetDir(targetDir);const timeoutMs=opts?.invokeTimeoutMs??DEFAULT_TIMEOUT_MS,require=createRequire(join(targetDir,"__arbiter_anchor__.js"));let entry;try{entry=require.resolve(pkg)}catch(err){throw err instanceof Error&&err.code==="MODULE_NOT_FOUND"?new Error(`Plugin "${pkg}" not found in ${targetDir}/node_modules. Install it first: npm install ${pkg}`,{cause:err}):new Error(`Plugin "${pkg}" failed to resolve: ${err instanceof Error?err.message:String(err)}`,{cause:err})}validatePluginManifest(entry,pkg);const rawMod=await loadRawMod(require,entry),plugin=rawMod.default??rawMod.plugin??rawMod;validatePluginShape(plugin,pkg);const p=plugin,pluginName=p.name,templateRoot=p.templateRoot,hasDetect=typeof p.detect=="function",verifyPlanRules=Array.isArray(p.verifyPlanRules)?p.verifyPlanRules:void 0,invariants=Array.isArray(p.invariants)?p.invariants:void 0,proxy={name:pluginName,apiVersion:"1",templateRoot,...verifyPlanRules!==void 0?{verifyPlanRules}:{},...invariants!==void 0?{invariants}:{},generate(ctx){return invokeInWorker(entry,"generate",{config:ctx.config,targetDir:ctx.targetDir,templateRoot},pluginName,timeoutMs)}};return hasDetect&&(proxy.detect=config=>invokeInWorker(entry,"detect",{config,targetDir,templateRoot},pluginName,timeoutMs)),proxy}function validatePluginInvariants(pkg,raw){if(raw===void 0)return;if(!Array.isArray(raw))throw new Error(`Plugin "${pkg}" field invariants must be an array if present.`);const seen=new Set;for(const entry of raw){const id=entry.id;if(typeof id!="string"||!PROJ_INVARIANT_ID_RE.test(id))throw new Error(`Plugin "${pkg}" invariant id must match /^PROJ-\\d+$/ (the INV-NN namespace is reserved) \u2014 got ${String(id)}`);if(seen.has(id))throw new Error(`Plugin "${pkg}" declares duplicate invariant id ${id}`);seen.add(id)}}function validatePluginShape(plugin,pkg){if(!plugin||typeof plugin!="object")throw new Error(`Plugin "${pkg}" must export a default object.`);const p=plugin;if(typeof p.name!="string"||!VALID_NAME_RE.test(p.name))throw new Error(`Plugin "${pkg}" has invalid name "${String(p.name)}". Must match /^[a-z0-9][a-z0-9-_]*$/`);if(p.apiVersion!=="1")throw new Error(`Plugin "${pkg}" requires apiVersion "1", got "${String(p.apiVersion)}".`);if(typeof p.generate!="function")throw new Error(`Plugin "${pkg}" is missing required generate() function.`);if(typeof p.templateRoot!="string")throw new Error(`Plugin "${pkg}" must have a string templateRoot field.`);if("detect"in p&&typeof p.detect!="function")throw new Error(`Plugin "${pkg}" detect field must be a function if present.`);if("verifyPlanRules"in p&&!Array.isArray(p.verifyPlanRules))throw new Error(`Plugin "${pkg}" field verifyPlanRules must be an array if present.`);validatePluginInvariants(pkg,p.invariants)}const PROJ_INVARIANT_ID_RE=/^PROJ-\d+$/;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{workerData,parentPort}from"node:worker_threads";import{pathToFileURL}from"node:url";import{readFileSync}from"node:fs";import{join}from"node:path";import ejs from"ejs";const port=parentPort,LEVEL_ORDER=["L1","L2","L3","L4"];export function withPluginRenderDefaults(data){const withBasePackage=Object.prototype.hasOwnProperty.call(data,"basePackage")?data:{...data,basePackage:void 0},level=withBasePackage.governanceLevel,rank=typeof level=="string"?LEVEL_ORDER.indexOf(level):-1;return{...withBasePackage,isL2Plus:rank>=LEVEL_ORDER.indexOf("L2"),isL3Plus:rank>=LEVEL_ORDER.indexOf("L3"),isL4:level==="L4"}}async function run(){const{entryPath,kind,config,targetDir,templateRoot}=workerData,rawMod=await import(pathToFileURL(entryPath).href),plugin=rawMod.default??rawMod.plugin;if(kind==="detect"){const detectFn=plugin.detect,result=await detectFn(config);port.postMessage({kind:"result",value:result})}else{const ctx={config,targetDir,renderTemplate(relPath,data){const absPath=join(templateRoot,relPath);let src;try{src=readFileSync(absPath,"utf-8")}catch(err){const code=err instanceof Error&&"code"in err?String(err.code):"unknown";throw new Error(`failed to read plugin template "${relPath}" (${code}): ${absPath}`,{cause:err})}return ejs.render(src,withPluginRenderDefaults(data))}},generateFn=plugin.generate,result=await generateFn(ctx);port.postMessage({kind:"result",value:result})}}workerData&&run().catch(err=>{port.postMessage({kind:"error",message:err instanceof Error?err.message:String(err)})});
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{dirname,join}from"node:path";import{existsSync}from"node:fs";import{createRequire}from"node:module";import{runCli,CliError}from"./run-cli.js";import{getLogger}from"./logger.js";function resolveOwnPrettierBin(){try{const pkgPath=createRequire(import.meta.url).resolve("prettier/package.json"),bin=join(dirname(pkgPath),"bin/prettier.cjs");return existsSync(bin)?bin:null}catch{return null}}const ARBITER_DEFAULT_PRETTIER_ARGS=["--single-quote","--no-semi","--print-width","100"];function resolvePrettierInvocation(targetDir){const ownBin=resolveOwnPrettierBin();if(ownBin===null)return null;const prettierRc=join(targetDir,".prettierrc"),prettierRcJson=join(targetDir,".prettierrc.json"),configFile=existsSync(prettierRc)?prettierRc:existsSync(prettierRcJson)?prettierRcJson:null;return{bin:ownBin,configArgs:configFile?["--config",configFile]:ARBITER_DEFAULT_PRETTIER_ARGS}}export function formatContent(content,filePath,targetDir){const invocation=resolvePrettierInvocation(targetDir);if(invocation===null)return content;const{bin,configArgs}=invocation;try{const{stdout}=runCli("node",[bin,"--stdin-filepath",filePath,...configArgs],{cwd:targetDir,timeoutMs:3e4,input:content});return stdout}catch(err){return err instanceof CliError&&err.notFound||getLogger().warn("prettier_format.failed",{filePath,err:String(err)},"formatContent: prettier --stdin-filepath failed (best-effort, returning unformatted)"),content}}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{homedir}from"node:os";import{dirname,join,resolve}from"node:path";import{ensureDir,writeFileTranslated}from"./fs.js";function defaultProfileBaseDir(){return join(homedir(),".arbiter","profiles")}export function detectRuntime(globalsRef=globalThis){const g=globalsRef;return{hasBun:g.Bun!==void 0,hasDeno:g.Deno!==void 0}}export class ProfilerNotSupportedError extends Error{constructor(runtime){super(`--profile only supported on Node.js; detected: ${runtime}`),this.name="ProfilerNotSupportedError"}}export async function startProfiler(opts){const runtime=detectRuntime();if(runtime.hasBun)throw new ProfilerNotSupportedError("Bun");if(runtime.hasDeno)throw new ProfilerNotSupportedError("Deno");const{Session}=await import("node:inspector"),session=new Session;session.connect(),await postPromise(session,"Profiler.enable"),await postPromise(session,"Profiler.start");const baseDir=opts.baseDir??defaultProfileBaseDir(),outPath=resolve(baseDir,`${opts.runId}.cpuprofile`);return{async stop(){const result=await postPromise(session,"Profiler.stop");return session.disconnect(),ensureDir(dirname(outPath)),writeFileTranslated(outPath,JSON.stringify(result.profile)),outPath}}}function postPromise(session,method){return new Promise((resolveP,rejectP)=>{session.post.bind(session)(method,(err,params)=>{if(err){rejectP(err);return}resolveP(params)})})}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import ejs from"ejs";import{join,dirname}from"node:path";import{fileURLToPath}from"node:url";import{levelAtLeast,LEVEL_ORDER}from"../config/levels.js";import{resolveMaxParallelWorktrees}from"../config/collaboration-mode-defaults.js";import{readFileTranslated}from"./fs.js";const __dirname=dirname(fileURLToPath(import.meta.url)),TEMPLATES_DIR=join(__dirname,"..","templates"),SOURCE_EXTENSIONS={typescript:[".ts",".tsx",".mts",".cts",".mjs",".cjs",".js",".jsx"],multi:[".ts",".tsx",".mts",".cts",".mjs",".cjs",".js",".jsx",".java",".kt",".kts"],java:[".java"],kotlin:[".kt",".kts",".java"],rust:[".rs"],go:[".go"],python:[".py"]};function isGovernanceLevel(value){return typeof value=="string"&&LEVEL_ORDER.includes(value)}export function withBasePackageDefault(data){return Object.prototype.hasOwnProperty.call(data,"basePackage")?data:{...data,basePackage:void 0}}export function withLevelBooleans(data){const level=data.governanceLevel,harnessDefault=Object.prototype.hasOwnProperty.call(data,"enableEvidenceHarness")?{}:{enableEvidenceHarness:!1};return{...data,...harnessDefault,isL2Plus:isGovernanceLevel(level)&&levelAtLeast(level,"L2"),isL3Plus:isGovernanceLevel(level)&&levelAtLeast(level,"L3"),isL4:isGovernanceLevel(level)&&level==="L4"}}const ARCHETYPE_TO_BUCKET={"backend-web-db":"service",cli:"cli",embedded:"cli","data-pipeline":"batch"};export function resolveServiceBucket(archetype){return(typeof archetype=="string"?ARCHETYPE_TO_BUCKET[archetype]:void 0)??"lib"}export function withServiceBucket(data){const archetype=data.archetype,bucket=resolveServiceBucket(archetype);return{...data,serviceBucket:bucket,isService:bucket==="service",isCli:bucket==="cli",isBatch:bucket==="batch"}}function withRenderDefaults(data){const language=data.language;return withServiceBucket(withLevelBooleans(withBasePackageDefault({...data,maxParallelWorktrees:resolveMaxParallelWorktrees(data),sourceExtensions:typeof language=="string"?[...SOURCE_EXTENSIONS[language]??[]]:[]})))}export function renderTemplate(templatePath,data){const fullPath=join(TEMPLATES_DIR,templatePath),source=readFileTranslated(fullPath,"utf-8");return ejs.render(source,withRenderDefaults(data),{filename:fullPath})}export function renderFromAbsPath(absPath,data){const source=readFileTranslated(absPath,"utf-8");return ejs.render(source,withRenderDefaults(data),{filename:absPath})}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{existsSync,readdirSync,readFileSync,statSync}from"node:fs";import{ensureDir,rmTranslated,writeFileTranslated}from"./fs.js";import{homedir}from"node:os";import{join,resolve}from"node:path";import{walkDir}from"./walk-dir.js";const REDACTED="***REDACTED***",REDACTABLE_WORDS=new Set(["TOKEN","SECRET","KEY","PASSWORD","PASS","AUTH","CREDENTIAL","CREDENTIALS","PRIVATE","API","DSN"]),REDACTION_PREFIX_RE=/^(GH_|GITHUB_|NPM_)/i,COMPOUND_SECRET_RE=/TOKEN|SECRET|PASSWORD|PASSWD|APIKEY|PRIVATEKEY|CREDENTIAL/,URL_USERINFO_RE=/:\/\/[^/@\s]+:[^/@\s]+@/;function defaultReplayBaseDir(){return join(homedir(),".arbiter","logs")}export function shouldRedactKey(key){if(REDACTION_PREFIX_RE.test(key))return!0;const upper=key.toUpperCase();return COMPOUND_SECRET_RE.test(upper)?!0:upper.split("_").some(seg=>REDACTABLE_WORDS.has(seg))}export function redactEnv(env){const out={};for(const[k,v]of Object.entries(env))v!==void 0&&(out[k]=shouldRedactKey(k)||URL_USERINFO_RE.test(v)?REDACTED:v);return out}const SENSITIVE_FLAGS=["--gh-token","--api-key","--token","--password","--secret"];function redactArgv(argv){return argv.map((arg,i)=>{const prev=argv[i-1]??"";if(SENSITIVE_FLAGS.some(f=>prev===f))return"[REDACTED]";const matched=SENSITIVE_FLAGS.find(f=>arg.startsWith(f+"="));return matched!==void 0?`${matched}=[REDACTED]`:arg})}function snapshotArbiterState(cwd){const snap={},configPath=join(cwd,"arbiter.json");if(existsSync(configPath))try{snap.arbiterJson=JSON.parse(readFileSync(configPath,"utf-8"))}catch{snap.arbiterJson={_error:"unreadable"}}const arbiterDir=join(cwd,".arbiter");return existsSync(arbiterDir)&&(snap.arbiterDir=walkDir(arbiterDir,{base:arbiterDir}).sort()),snap}export function startReplay(inputs){const baseDir=inputs.baseDir??defaultReplayBaseDir(),dir=resolve(baseDir,inputs.runId);ensureDir(dir);const output=[];return writeFileTranslated(join(dir,"command.txt"),redactArgv(inputs.argv).join(" ")+`
|
|
3
|
+
`),writeFileTranslated(join(dir,"env.json"),JSON.stringify(redactEnv(inputs.env),null,2)),writeFileTranslated(join(dir,"state-before.json"),JSON.stringify(snapshotArbiterState(inputs.cwd),null,2)),{runId:inputs.runId,dir,output,append(chunk){output.push(chunk)},close(exitCode){writeFileTranslated(join(dir,"output.log"),output.join(""));const after={exitCode,ts:new Date().toISOString(),...snapshotArbiterState(inputs.cwd)};writeFileTranslated(join(dir,"state-after.json"),JSON.stringify(after,null,2))}}}export function rotateReplayLogs(opts){const baseDir=opts.baseDir??defaultReplayBaseDir();if(!existsSync(baseDir))return[];const entries=readdirSync(baseDir,{withFileTypes:!0}).filter(e=>e.isDirectory()).map(e=>{const full=join(baseDir,e.name),stat=statSync(full);return{name:e.name,full,mtime:stat.mtimeMs}}).sort((a,b)=>b.mtime-a.mtime);if(entries.length<=opts.capN)return[];const evicted=[];for(const entry of entries.slice(opts.capN))try{rmTranslated(entry.full,{recursive:!0,force:!0}),evicted.push(entry.name)}catch{}return evicted}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{spawn,spawnSync}from"node:child_process";import{readdirSync,readlinkSync}from"node:fs";import{getLogger}from"./logger.js";export class CliError extends Error{cmd;args;exitCode;stdout;stderr;timedOut;notFound;outputTruncated;constructor(details,message){super(message??formatMessage(details)),this.name="CliError",this.cmd=details.cmd,this.args=details.args,this.exitCode=details.exitCode,this.stdout=details.stdout,this.stderr=details.stderr,this.timedOut=details.timedOut,this.notFound=details.notFound,this.outputTruncated=details.outputTruncated??!1}}const DEFAULT_TIMEOUT_MS=6e4,DEFAULT_MAX_BUFFER_BYTES=64*1024*1024;function classifyAttempt(obs){const{cmd,args,stdout,stderr,errorCode}=obs;if(errorCode==="ENOENT")return{ok:!1,fatal:!0,error:new CliError({cmd,args,exitCode:-1,stdout,stderr,timedOut:!1,notFound:!0},`Command not found: ${cmd}`)};if(errorCode==="ENOBUFS"){const limitMb=Math.round(obs.maxBufferBytes/1048576);return{ok:!1,fatal:!0,error:new CliError({cmd,args,exitCode:-1,stdout,stderr,timedOut:!1,notFound:!1,outputTruncated:!0},`Command output exceeded buffer limit (${limitMb} MB): ${`${cmd} ${args.join(" ")}`.trim()}`)}}const sigtermLikelyTimeout=obs.signal==="SIGTERM"&&obs.durationMs>=obs.timeoutMs-1e3;return errorCode==="ETIMEDOUT"||sigtermLikelyTimeout?{ok:!1,fatal:!1,error:new CliError({cmd,args,exitCode:-1,stdout,stderr,timedOut:!0,notFound:!1})}:errorCode!==void 0?{ok:!1,fatal:!0,error:new CliError({cmd,args,exitCode:-1,stdout,stderr,timedOut:!1,notFound:!1},`Command failed to start (${errorCode}): ${cmd}`)}:obs.status!==0?{ok:!1,fatal:!1,error:new CliError({cmd,args,exitCode:obs.status??-1,stdout,stderr,timedOut:!1,notFound:!1})}:{ok:!0,result:{stdout,stderr,exitCode:0,durationMs:obs.durationMs}}}function runOnce(cmd,args,opts){const start=Date.now(),result=spawnSync(cmd,[...args],{cwd:opts.cwd,env:opts.env,timeout:opts.timeoutMs,input:opts.input,maxBuffer:opts.maxBufferBytes,encoding:"utf-8",shell:!1});return classifyAttempt({cmd,args,stdout:result.stdout,stderr:result.stderr,errorCode:result.error?.code,status:result.status,signal:result.signal,durationMs:Date.now()-start,timeoutMs:opts.timeoutMs,maxBufferBytes:opts.maxBufferBytes})}function normalizeOpts(opts){return{runOpts:{cwd:opts.cwd,env:opts.env,input:opts.input,timeoutMs:opts.timeoutMs??DEFAULT_TIMEOUT_MS,maxBufferBytes:opts.maxBufferBytes??DEFAULT_MAX_BUFFER_BYTES},retries:opts.retries??0,retryDelayMs:opts.retryDelayMs??500}}function settleAttempt(cmd,outcome,attemptErrors){if(outcome.ok)return logRetrySuccess(cmd,attemptErrors),outcome.result;if(outcome.fatal)throw outcome.error;return attemptErrors.push(outcome.error),null}export function runCli(cmd,args,opts={}){const{runOpts,retries,retryDelayMs}=normalizeOpts(opts),attemptErrors=[];for(let attempt=0;attempt<=retries;attempt++){const result=settleAttempt(cmd,runOnce(cmd,args,runOpts),attemptErrors);if(result)return result;attempt<retries&&sleepSync(retryDelayMs)}throw finalRetryError(cmd,args,attemptErrors)}function logRetrySuccess(cmd,attemptErrors){attemptErrors.length!==0&&getLogger().warn("run_cli.retry_succeeded",{cmd,attempts:attemptErrors.length,first_error:attemptErrors[0]?.message??"(none)"},`runCli: ${cmd} succeeded after ${attemptErrors.length} failed attempt(s)`)}function finalRetryError(cmd,args,attemptErrors){const last=attemptErrors[attemptErrors.length-1];return last?(attemptErrors.length>1&&getLogger().warn("run_cli.retries_exhausted",{cmd,attempts:attemptErrors.length,final_error:last.message},`runCli: ${cmd} failed after ${attemptErrors.length} attempt(s)`),last):new CliError({cmd,args,exitCode:-1,stdout:"",stderr:"",timedOut:!1,notFound:!1},`Command failed before first attempt: ${cmd}`)}async function killAndWaitForProcessGroup(pid){let warned=!1;for(;;){try{process.kill(-pid,"SIGKILL"),process.kill(-pid,0)}catch(err){if(err.code==="ESRCH")return;warned||(process.stderr.write(`gate-exec: cannot kill process group ${pid}; retaining the mutex and retrying
|
|
3
|
+
`),warned=!0),await new Promise(resolve=>setTimeout(resolve,100));continue}await new Promise(resolve=>setTimeout(resolve,10))}}function procFileHolders(path){let entries;try{entries=readdirSync("/proc")}catch{return null}const holders=[];for(const entry of entries){if(!/^\d+$/.test(entry))continue;const pid=Number(entry);let fds;try{fds=readdirSync(`/proc/${pid}/fd`)}catch{continue}for(const fd of fds)try{if(readlinkSync(`/proc/${pid}/fd/${fd}`)!==path)continue;holders.push(pid);break}catch{continue}}return holders}async function killAndWaitForTrackedDescendants(path){let warned=!1;for(;;){const holders=procFileHolders(path);if(holders===null){process.stderr.write(`gate-exec: warning: /proc unavailable; escaped descendant cleanup is limited to the supervisor process group
|
|
4
|
+
`);return}if(holders.length===0)return;let killFailed=!1;for(const pid of holders)try{process.kill(pid,"SIGKILL")}catch(err){if(err.code==="ESRCH")continue;warned||(process.stderr.write(`gate-exec: cannot kill tracked descendant ${pid}; retaining the mutex and retrying
|
|
5
|
+
`),warned=!0),killFailed=!0}await new Promise(resolve=>setTimeout(resolve,killFailed?100:10))}}export function runInteractive(cmd,args,opts={}){const stdio=opts.extraFds?["inherit","inherit","inherit",...opts.extraFds]:"inherit";return new Promise((resolve,reject)=>{const child=spawn(cmd,[...args],{cwd:opts.cwd,env:opts.env,stdio,shell:!1,detached:opts.detached});let parentSignal,teardownPromise;const teardown=()=>(teardownPromise!==void 0||(teardownPromise=(async()=>{child.pid!==void 0&&await killAndWaitForProcessGroup(child.pid),opts.trackedDescendantFdPath!==void 0&&await killAndWaitForTrackedDescendants(opts.trackedDescendantFdPath)})()),teardownPromise),parentSignalHandlers=new Map,removeParentSignalHandlers=()=>{for(const[signal,handler]of parentSignalHandlers)process.removeListener(signal,handler)};if(opts.teardownOnParentSignal)for(const signal of["SIGHUP","SIGINT","SIGTERM"]){const handler=()=>{parentSignal??=signal,teardown().catch(reject)};parentSignalHandlers.set(signal,handler),process.once(signal,handler)}child.once("error",err=>{if(removeParentSignalHandlers(),err.code!=="ENOENT"){resolve({exitCode:1});return}reject(new CliError({cmd,args,exitCode:-1,stdout:"",stderr:"",timedOut:!1,notFound:!0},`Command not found: ${cmd}`))}),child.once("close",(code,signal)=>{(async()=>{(parentSignal!==void 0||signal&&opts.teardownProcessGroupOnSignal)&&await teardown(),removeParentSignalHandlers();const signalExitCode=parentSignal?128+{SIGHUP:1,SIGINT:2,SIGTERM:15}[parentSignal]:void 0;resolve({exitCode:signalExitCode??code??1})})().catch(reject)})})}export function runCliJson(cmd,args,opts={}){const result=runCli(cmd,args,opts);try{return JSON.parse(result.stdout)}catch(err){throw new CliError({cmd,args,exitCode:result.exitCode,stdout:result.stdout,stderr:result.stderr,timedOut:!1,notFound:!1},`Invalid JSON output from ${cmd}: ${err.message}`)}}function formatMessage(details){const cmdStr=`${details.cmd} ${details.args.join(" ")}`.trim();if(details.timedOut)return`Command timed out: ${cmdStr}`;const preview=details.stderr.trim().slice(0,500);return`Command failed (exit ${details.exitCode}): ${cmdStr}${preview?`
|
|
6
|
+
${preview}`:""}`}function sleepSync(ms){if(ms<=0)return;const buf=new Int32Array(new SharedArrayBuffer(4));Atomics.wait(buf,0,0,ms)}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{existsSync}from"node:fs";import{join}from"node:path";import{randomBytes}from"node:crypto";export function mintId(prefix,hexBytes=2){const now=new Date,date=now.toISOString().slice(0,10).replace(/-/g,""),time=now.toISOString().slice(11,19).replace(/:/g,"");return`${prefix}-${date}-${time}-${randomBytes(hexBytes).toString("hex")}`}export function mintUniqueId(prefix,targetDir,hexBytes=2){for(let attempt=0;attempt<3;attempt++){const id=mintId(prefix,hexBytes),dir=join(targetDir,".arbiter","plan","runs",id);if(!existsSync(dir))return id}return mintId(prefix,hexBytes)}export function getRunId(){return process.env.ARBITER_RUN_ID||(process.env.ARBITER_RUN_ID=mintId("arb",4)),process.env.ARBITER_RUN_ID}export function formatRunIdFooter(){return`
|
|
3
|
+
Run ID: ${getRunId()}`}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{readFileSync}from"node:fs";import{join}from"node:path";import{getLogger}from"./logger.js";export function readFileSafe(path){try{return readFileSync(path,"utf-8")}catch(err){return isEnoent(err)||getLogger().warn("safe_read.read_failed",{path,err:String(err)},`could not read ${path}`),""}}export function readPackageJsonSafe(dir){const path=join(dir,"package.json");try{return JSON.parse(readFileSync(path,"utf-8"))}catch(err){return isEnoent(err)?{}:(getLogger().warn("safe_read.parse_failed",{path,err:String(err)},`could not parse ${path}`),{})}}function isEnoent(err){return typeof err=="object"&&err!==null&&err.code==="ENOENT"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
export function colorEnabled(stream=process.stdout,env=process.env){return!!stream.isTTY&&env.NO_COLOR===void 0&&env.CI===void 0&&env.TERM!=="dumb"}export function asciiOnly(explicit,env=process.env){if(explicit)return!0;const lang=env.LC_ALL||env.LC_CTYPE||env.LANG||"";return lang==="C"||lang==="POSIX"}const SGR={reset:"\x1B[0m",green:"\x1B[32m",yellow:"\x1B[33m",red:"\x1B[31m",dim:"\x1B[2m",cyan:"\x1B[36m"};export function paint(text,color,on){return!on||!Object.hasOwn(SGR,color)?text:`${SGR[color]}${text}${SGR.reset}`}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{readdirSync}from"node:fs";import{join}from"node:path";import{toFsError}from"./fs.js";export function walkDir(root,options={}){const{skipDirs,filter,base,errorMode="swallow"}=options,out=[],visit=dir=>{let entries;try{entries=readdirSync(dir,{withFileTypes:!0})}catch(err){if(errorMode==="fs-soft"){const code=err.code;if(code==="ENOENT"||code==="EACCES")return;throw toFsError(err,dir)}return}for(const entry of entries){const full=join(dir,entry.name);entry.isDirectory()?(!skipDirs||!skipDirs.has(entry.name))&&visit(full):entry.isFile()&&(!filter||filter(entry.name,full))&&out.push(base!==void 0?full.slice(base.length+1):full)}};return visit(root),out}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const driveByScopeRule={id:"VB-INV-NO-DRIVEBY",ssotPointer:{path:"TESTING_POLICY.md",anchor:"43"},applicability(){return!0},evaluate(plan){const violations=[],{paths,boundaries}=plan.scope;if(paths!==void 0&&paths.length>0){const normalizedPaths=paths.map(p=>p.endsWith("/")?p:`${p}/`);for(const f of plan.files)normalizedPaths.some(p=>f.path.startsWith(p)||f.path===p.slice(0,-1))||violations.push({rule_id:"VB-INV-NO-DRIVEBY",severity:"ERROR",message:`File "${f.path}" is outside declared scope paths [${paths.join(", ")}]`,ssot_pointer:{path:"TESTING_POLICY.md",anchor:"43"},evidence:{paths:[f.path]}});return violations}if(boundaries!==void 0&&boundaries.length>0){for(const f of plan.files){const segment=f.path.split("/")[0]??"";boundaries.includes(segment)||violations.push({rule_id:"VB-INV-NO-DRIVEBY",severity:"ERROR",message:`File "${f.path}" top-level segment "${segment}" is outside declared boundaries [${boundaries.join(", ")}]`,ssot_pointer:{path:"TESTING_POLICY.md",anchor:"43"},evidence:{paths:[f.path]}})}return violations}const segments=new Set(plan.files.map(f=>f.path.split("/")[0]??""));return segments.size>2?violations.push({rule_id:"VB-INV-NO-DRIVEBY",severity:"ERROR",message:`Plan spans ${segments.size} top-level path segments \u2014 likely drive-by scope (>2)`,ssot_pointer:{path:"TESTING_POLICY.md",anchor:"43"},evidence:{paths:plan.files.map(f=>f.path)}}):segments.size===2&&violations.push({rule_id:"VB-INV-NO-DRIVEBY",severity:"WARN",message:"Plan spans 2 top-level path segments \u2014 review for scope creep",ssot_pointer:{path:"TESTING_POLICY.md",anchor:"43"},evidence:{paths:plan.files.map(f=>f.path)}}),violations}};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const TASK_REF_RE=/#\d+|T-[A-Z]+-\d+/;export const orphanTodosRule={id:"VB-INV-NO-ORPHAN",ssotPointer:{path:"GLOBAL_INVARIANTS.md",anchor:"inv-21"},applicability(plan){return plan.files.some(f=>(f.changes?.adds_todos?.length??0)>0)},evaluate(plan){const violations=[];for(const f of plan.files)for(const todo of f.changes?.adds_todos??[])TASK_REF_RE.test(todo)||violations.push({rule_id:"VB-INV-NO-ORPHAN",severity:"ERROR",message:`TODO lacks task reference: "${todo}"`,ssot_pointer:{path:"GLOBAL_INVARIANTS.md",anchor:"inv-21"},evidence:{paths:[f.path],match:todo}});return violations}};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{driveByScopeRule}from"./drive-by-scope.js";import{orphanTodosRule}from"./orphan-todos.js";import{skipPatternsRule}from"./skip-patterns.js";import{uiLanguageRule}from"./ui-language.js";const BUILT_IN_RULES=[uiLanguageRule,skipPatternsRule,orphanTodosRule,driveByScopeRule];export function buildRegistry(extraRules=[]){const allRules=[...BUILT_IN_RULES,...extraRules],seen=new Set;for(const rule of allRules){if(seen.has(rule.id))return{rules:[],error:`Duplicate rule ID "${rule.id}" \u2014 extra rules may not shadow built-ins`};seen.add(rule.id)}return{rules:allRules}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const SKIP_MATCHERS=["@Disabled",".skip(","@Ignore","xit".concat("("),"xdescribe".concat("("),"it.skip"];export const skipPatternsRule={id:"VB-INV-NO-SKIP",ssotPointer:{path:"GLOBAL_INVARIANTS.md",anchor:"inv-12"},applicability(plan){return plan.files.some(f=>f.changes?.adds_tests===!0||f.changes?.modifies_tests===!0||(f.changes?.skip_patterns?.length??0)>0)},evaluate(plan){const violations=[];for(const f of plan.files)for(const pattern of f.changes?.skip_patterns??[])SKIP_MATCHERS.some(m=>pattern.includes(m))&&violations.push({rule_id:"VB-INV-NO-SKIP",severity:"ERROR",message:`Skip pattern detected: "${pattern}"`,ssot_pointer:{path:"GLOBAL_INVARIANTS.md",anchor:"inv-12"},evidence:{paths:[f.path],match:pattern}});return violations}};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { PlanJsonV1, Violation } from '../../types/plan.js';
|
|
2
|
+
interface RuleContext {
|
|
3
|
+
targetDir: string;
|
|
4
|
+
}
|
|
5
|
+
export type RuleViolation = Pick<Violation, 'rule_id' | 'severity' | 'message' | 'ssot_pointer' | 'evidence'>;
|
|
6
|
+
export interface VerifyPlanRule {
|
|
7
|
+
id: string;
|
|
8
|
+
ssotPointer: {
|
|
9
|
+
path: string;
|
|
10
|
+
anchor: string;
|
|
11
|
+
};
|
|
12
|
+
applicability(plan: PlanJsonV1): boolean;
|
|
13
|
+
evaluate(plan: PlanJsonV1, ctx: RuleContext): RuleViolation[];
|
|
14
|
+
}
|
|
15
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const FRONTEND_PATH_RE=/^frontend\/src\/.+\.(vue|ts|tsx)$/,TEST_PATH_RE=/\.(spec|test)\./,ITALIAN_STOPWORDS_RE=/\b(che|non|anche|per|con|come|nel|della|degli)\b/i;function isUiApplicable(path,addsUiStrings,uiStringsLen){return addsUiStrings===!0||uiStringsLen>0||FRONTEND_PATH_RE.test(path)&&!TEST_PATH_RE.test(path)}function checkUiStrings(filePath,strings){const violations=[];for(const s of strings)ITALIAN_STOPWORDS_RE.test(s)&&violations.push({rule_id:"VB-INV-EN-UI",severity:"ERROR",message:`UI string contains Italian text: "${s}"`,ssot_pointer:{path:"GLOBAL_INVARIANTS.md",anchor:"inv-25"},evidence:{paths:[filePath],match:s}});return violations}export const uiLanguageRule={id:"VB-INV-EN-UI",ssotPointer:{path:"GLOBAL_INVARIANTS.md",anchor:"inv-25"},applicability(plan){return plan.files.some(f=>isUiApplicable(f.path,f.changes?.adds_ui_strings,f.changes?.ui_strings?.length??0))},evaluate(plan){const violations=[];for(const f of plan.files){if(!isUiApplicable(f.path,f.changes?.adds_ui_strings,f.changes?.ui_strings?.length??0))continue;const strings=f.changes?.ui_strings??[];strings.length>0?violations.push(...checkUiStrings(f.path,strings)):f.changes?.adds_ui_strings===!0&&plan.invariants?.ui_language!=="EN"&&violations.push({rule_id:"VB-INV-EN-UI",severity:"WARN",message:'File adds UI strings but invariants.ui_language:"EN" assertion is missing',ssot_pointer:{path:"GLOBAL_INVARIANTS.md",anchor:"inv-25"},evidence:{paths:[f.path]}})}return violations}};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import{join}from"node:path";import{mintUniqueId}from"../utils/run-id.js";import{buildRegistry}from"./rules/registry.js";import{ensureDir,writeFileTranslated}from"../utils/fs.js";function makeErrorReview(taskId,runId,reviewer,ruleId,message){return{timestamp:new Date().toISOString(),task_id:taskId,run_id:runId,status:"ERROR",reviewer,verification:{ok:!1,violations:[{rule_id:ruleId,severity:"ERROR",message,ssot_pointer:null,evidence:{paths:[]}}],notes:[]},blocking:!0,blocking_reason:message}}function determineStatus(violations,failOnWarn,notes){const hasErrors=violations.some(v=>v.severity==="ERROR"),hasWarns=violations.some(v=>v.severity==="WARN");return hasErrors||hasWarns&&failOnWarn?"REJECTED":(hasWarns&¬es.push("Plan approved with warnings \u2014 review before merge"),"APPROVED")}function persist(runDir,pointerDir,planSource,review){const reviewJson=JSON.stringify(review,null,2);try{writeFileTranslated(join(runDir,"PLAN.json"),planSource),writeFileTranslated(join(runDir,"REVIEW.json"),reviewJson),writeFileTranslated(join(pointerDir,"PLAN.json"),planSource),writeFileTranslated(join(pointerDir,"REVIEW.json"),reviewJson)}catch(err){throw process.stderr.write(`[arbiter] persist failed: ${err instanceof Error?err.message:String(err)}
|
|
3
|
+
`),err}}export function runVerify(opts){const{plan,planSource,targetDir,extraRules=[]}=opts,reviewer=opts.reviewer??plan.review_bridge.reviewer,failOnWarn=opts.failOnWarn??plan.review_bridge.fail_on_warn,runId=mintUniqueId("bridge",targetDir),runDir=join(targetDir,".arbiter","plan","runs",runId),pointerDir=join(targetDir,".arbiter","plan");ensureDir(runDir),ensureDir(pointerDir);const registryResult=buildRegistry(extraRules);if(registryResult.error!==void 0){const review2=makeErrorReview(plan.task_id,runId,reviewer,"REGISTRY",registryResult.error);return persist(runDir,pointerDir,planSource,review2),{runId,status:"ERROR",review:review2,runDir,pointerDir}}const ctx={targetDir},violations=[],notes=[];for(const rule of registryResult.rules){let applies;try{applies=rule.applicability(plan)}catch(err){const message=err instanceof Error?err.message:String(err),review2=makeErrorReview(plan.task_id,runId,reviewer,"RUNTIME",`Rule "${rule.id}" applicability threw: ${message}`);return persist(runDir,pointerDir,planSource,review2),{runId,status:"ERROR",review:review2,runDir,pointerDir}}if(applies)try{const ruleViolations=rule.evaluate(plan,ctx);for(const v of ruleViolations)violations.push({rule_id:v.rule_id,severity:v.severity,message:v.message,ssot_pointer:v.ssot_pointer,evidence:v.evidence})}catch(err){const message=err instanceof Error?err.message:String(err),review2=makeErrorReview(plan.task_id,runId,reviewer,"RUNTIME",`Rule "${rule.id}" threw: ${message}`);return persist(runDir,pointerDir,planSource,review2),{runId,status:"ERROR",review:review2,runDir,pointerDir}}}const status=determineStatus(violations,failOnWarn,notes),errCount=violations.filter(v=>v.severity==="ERROR").length,blockingReason=status==="REJECTED"?errCount>0?`${errCount} ERROR violation(s)`:"WARN violations with fail_on_warn:true":null,review={timestamp:new Date().toISOString(),task_id:plan.task_id,run_id:runId,status,reviewer,verification:{ok:status==="APPROVED",violations,notes},blocking:status==="REJECTED",blocking_reason:blockingReason};return persist(runDir,pointerDir,planSource,review),{runId,status,review,runDir,pointerDir}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function defaultContractType(archetype,hasPublicApi){if(!hasPublicApi)return"none";switch(archetype){case"backend-web-db":return"rest-owned";case"frontend-spa":return"rest-public";case"data-pipeline":return"message-queue";default:return"none"}}export function shouldAskContractType(answers){return answers.hasPublicApi===!0}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{levelAtLeast}from"../config/levels.js";const SELECTABLE_PRESETS=["industrial-grade","solo-homelab"];export function resolvePresetOption(raw){if(raw!==void 0)return SELECTABLE_PRESETS.find(p=>p===raw)}export function applyPreset(preset,config){if(preset!=="none"){if(config.preset=preset,preset==="solo-homelab"){applySoloHomelab(config);return}config.enableIso27001Mapping=!0,config.enableNis2Mapping=!0,config.enableGdprMapping=!0,config.enableRiskRegister=!0,config.enableEvidenceHarness=!0,config.enableOperationsHandbook=!0,config.enableMcpFallback=!0,config.auth={provider:"none",tenantIsolation:!0,...config.auth},config.observability={provider:"none",metrics:!0,logs:!0,traces:!1,alerts:!0,...config.observability}}}const SOLO_HOMELAB_MAX_GOVERNANCE="L2";function applySoloHomelab(config){config.enableIso27001Mapping=!1,config.enableNis2Mapping=!1,config.enableGdprMapping=!1,config.enableRiskRegister=!1,config.industryOverlay="none",config.enableMutationTesting=!1,config.enableEvidenceHarness=!1,config.enableOperationsHandbook=!1,levelAtLeast(config.governanceLevel,"L3")&&(config.governanceLevel=SOLO_HOMELAB_MAX_GOVERNANCE)}
|