devrites 3.0.6 → 3.1.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 +18 -0
- package/README.md +67 -45
- package/SECURITY.md +31 -29
- package/docs/adr/0001-go-engine-as-control-plane.md +1 -1
- package/docs/adr/0002-dual-host-harness.md +1 -1
- package/docs/adr/0004-state-schema-phases-sections.md +1 -2
- package/docs/adr/0006-clock-seam-and-engine-ci-gates.md +3 -4
- package/docs/adr/0009-prebuild-decision-coverage-and-readiness.md +67 -0
- package/docs/adr/0010-agent-first-fresh-context-orchestration.md +71 -0
- package/docs/adr/0011-define-plan-transition-rights.md +28 -0
- package/docs/adr/README.md +3 -0
- package/docs/agents/triage-labels.md +7 -7
- package/docs/architecture.md +157 -140
- package/docs/cli.md +41 -12
- package/docs/command-map.md +49 -35
- package/docs/engine/agent-contract.md +92 -15
- package/docs/engine/commands.md +242 -72
- package/docs/engine/state-schema.md +52 -23
- package/docs/engine/workspace-schema.md +94 -15
- package/docs/extensions.md +1 -1
- package/docs/flow.md +80 -50
- package/docs/harness-compliance.md +29 -5
- package/docs/orchestration.md +107 -79
- package/docs/quick-reference.md +7 -3
- package/docs/release.md +4 -3
- package/docs/skills.md +64 -38
- package/docs/usage.md +57 -38
- package/engine/cmd/releasepack/main.go +219 -0
- package/engine/cmd/releasepack/main_test.go +170 -0
- package/engine/commands.go +170 -23
- package/engine/git_guard.go +187 -0
- package/engine/git_guard_test.go +283 -0
- package/engine/hookpolicy.go +53 -55
- package/engine/hookpolicy_test.go +91 -1
- package/engine/hooks.go +296 -75
- package/engine/hooks_events_test.go +276 -6
- package/engine/hooks_workspace.go +640 -159
- package/engine/internal/devritespaths/paths.go +65 -10
- package/engine/internal/devritespaths/paths_test.go +110 -0
- package/engine/internal/doctor/doctor.go +153 -23
- package/engine/internal/doctor/doctor_test.go +74 -0
- package/engine/internal/forge/forge.go +940 -0
- package/engine/internal/forge/forge_test.go +576 -0
- package/engine/internal/forge/git.go +245 -0
- package/engine/internal/forge/liveness_unix.go +48 -0
- package/engine/internal/forge/liveness_windows.go +67 -0
- package/engine/internal/forge/manifest.go +402 -0
- package/engine/internal/gate/gate.go +90 -71
- package/engine/internal/gate/gate_test.go +71 -2
- package/engine/internal/harness/compliance.go +18 -24
- package/engine/internal/harness/harness.go +37 -44
- package/engine/internal/harness/harness_test.go +21 -5
- package/engine/internal/install/install.go +575 -58
- package/engine/internal/install/install_test.go +490 -9
- package/engine/internal/iohooks/iohooks.go +350 -59
- package/engine/internal/iohooks/iohooks_test.go +421 -1
- package/engine/internal/lib/buildreadiness.go +35 -18
- package/engine/internal/lib/clarifyreturn.go +91 -0
- package/engine/internal/lib/clarifyreturn_test.go +123 -0
- package/engine/internal/lib/context.go +54 -15
- package/engine/internal/lib/cursor_compat_test.go +8 -0
- package/engine/internal/lib/extensions.go +2 -3
- package/engine/internal/lib/gitauthority.go +601 -0
- package/engine/internal/lib/gitauthority_test.go +346 -0
- package/engine/internal/lib/jsonout.go +25 -11
- package/engine/internal/lib/jsonout_test.go +16 -0
- package/engine/internal/lib/lanes.go +14 -8
- package/engine/internal/lib/observability_test.go +358 -0
- package/engine/internal/lib/packageexistence.go +151 -35
- package/engine/internal/lib/packageexistence_test.go +163 -0
- package/engine/internal/lib/progress.go +11 -9
- package/engine/internal/lib/provenance.go +462 -0
- package/engine/internal/lib/provenance_test.go +154 -0
- package/engine/internal/lib/readiness_contract.json +111 -0
- package/engine/internal/lib/readinessartifact.go +510 -0
- package/engine/internal/lib/readinessartifact_test.go +321 -0
- package/engine/internal/lib/reconcile.go +712 -88
- package/engine/internal/lib/reconcile_test.go +335 -16
- package/engine/internal/lib/recoveryattempts.go +298 -0
- package/engine/internal/lib/recoveryattempts_test.go +215 -0
- package/engine/internal/lib/resolve.go +23 -19
- package/engine/internal/lib/runbook_context_test.go +20 -0
- package/engine/internal/lib/session.go +701 -9
- package/engine/internal/lib/session_test.go +80 -13
- package/engine/internal/lib/testintegrity.go +33 -37
- package/engine/internal/lib/testintegrity_test.go +63 -1
- package/engine/internal/migrate/migrate.go +81 -1
- package/engine/internal/migrate/migrate_test.go +22 -1
- package/engine/internal/reason/reason.go +180 -0
- package/engine/internal/reason/reason_test.go +35 -0
- package/engine/internal/rootfacts/facts.go +466 -0
- package/engine/internal/rootfacts/facts_test.go +306 -0
- package/engine/internal/safepath/safepath.go +55 -0
- package/engine/internal/safepath/safepath_test.go +69 -0
- package/engine/internal/safepath/safepath_windows_test.go +26 -0
- package/engine/internal/state/clarify_transition.go +165 -0
- package/engine/internal/state/clarify_transition_test.go +130 -0
- package/engine/internal/state/cmd/workflowmanifest/main.go +12 -8
- package/engine/internal/state/cursor.go +58 -0
- package/engine/internal/state/cursor_test.go +42 -1
- package/engine/internal/state/feature.go +35 -48
- package/engine/internal/state/schema.go +90 -37
- package/engine/internal/state/snapshot.go +18 -2
- package/engine/internal/state/state_test.go +187 -7
- package/engine/internal/state/status.go +48 -11
- package/engine/internal/state/workflow_manifest.json +231 -50
- package/engine/internal/toolpolicy/classifier.go +533 -0
- package/engine/internal/toolpolicy/classifier_test.go +424 -0
- package/engine/internal/toolpolicy/git.go +616 -0
- package/engine/internal/toolpolicy/scanner.go +382 -0
- package/engine/main.go +160 -77
- package/engine/observability_cli_test.go +52 -0
- package/engine/root_routing_test.go +277 -0
- package/engine/testdata/golden/TestParityBuildReadiness/arg=approved.golden +1 -1
- package/engine/testdata/golden/TestParityBuildReadiness/arg=emptystatus.golden +1 -1
- package/engine/testdata/golden/TestParityBuildReadiness/arg=noclarify.golden +1 -0
- package/engine/testdata/golden/TestParityBuildReadiness/arg=novet.golden +1 -0
- package/engine/testdata/golden/TestParityBuildReadiness/arg=stalevet.golden +1 -0
- package/engine/testdata/golden/TestParityBuildReadiness/arg=trailhash.golden +1 -1
- package/engine/testdata/golden/TestParityBuildReadiness/arg=trailpipe.golden +1 -1
- package/engine/testdata/golden/TestParityBuildReadiness/arg=vetnotready.golden +1 -0
- package/engine/testdata/golden/TestParityProgress/arg=allbuilt.golden +1 -1
- package/engine/testdata/golden/TestParityProgress/arg=done.golden +1 -1
- package/engine/testdata/golden/TestParityProgress/arg=mid.golden +1 -1
- package/engine/testdata/golden/TestParityProgress/arg=nophase.golden +1 -1
- package/engine/testdata/golden/TestParityProgress/arg=noslice.golden +1 -1
- package/engine/testdata/golden/TestParityProgress/arg=plan.golden +1 -1
- package/engine/testdata/golden/TestParityProgress/arg=seal.golden +1 -1
- package/engine/testdata/golden/TestParityReconcile/check-clean.golden +1 -1
- package/engine/testdata/golden/TestParityReconcile/inline-fallback.golden +1 -0
- package/engine/testdata/golden/TestParityReconcile/snapshot-no-allowlist.golden +1 -0
- package/engine/testdata/golden/TestParityWrightScope/devrites-edit-denied.golden +2 -0
- package/engine/testdata/golden/TestParityWrightScope/out-of-scope-enforce-denies.golden +1 -1
- package/engine/tests/adr_0011_define_plan_test.go +30 -0
- package/engine/tests/budget_test.go +19 -8
- package/engine/tests/concurrency_cli_test.go +7 -1
- package/engine/tests/doctor_cli_test.go +148 -2
- package/engine/tests/forge_cli_test.go +463 -0
- package/engine/tests/gate_test.go +63 -8
- package/engine/tests/hook_test.go +260 -8
- package/engine/tests/hooks_io_test.go +94 -3
- package/engine/tests/json_contract_test.go +127 -2
- package/engine/tests/migrate_cli_test.go +2 -2
- package/engine/tests/parity_buildreadiness_test.go +141 -10
- package/engine/tests/parity_learnings_test.go +16 -17
- package/engine/tests/parity_reconcile_test.go +20 -17
- package/engine/tests/parity_resolve_test.go +12 -11
- package/engine/tests/parity_test.go +21 -17
- package/pack/.claude/agents/devrites-code-reviewer.md +62 -48
- package/pack/.claude/agents/devrites-devex-reviewer.md +69 -53
- package/pack/.claude/agents/devrites-doubt-reviewer.md +29 -22
- package/pack/.claude/agents/devrites-evidence-scout.md +69 -0
- package/pack/.claude/agents/devrites-forge-judge.md +74 -61
- package/pack/.claude/agents/devrites-frontend-reviewer.md +48 -39
- package/pack/.claude/agents/devrites-performance-reviewer.md +49 -40
- package/pack/.claude/agents/devrites-plan-drafter.md +71 -0
- package/pack/.claude/agents/devrites-plan-reviewer.md +80 -47
- package/pack/.claude/agents/devrites-proof-runner.md +74 -0
- package/pack/.claude/agents/devrites-retrospector.md +48 -45
- package/pack/.claude/agents/devrites-security-auditor.md +46 -36
- package/pack/.claude/agents/devrites-simplifier-reviewer.md +50 -46
- package/pack/.claude/agents/devrites-slice-wright.md +153 -165
- package/pack/.claude/agents/devrites-spec-reviewer.md +34 -32
- package/pack/.claude/agents/devrites-strategy-reviewer.md +63 -34
- package/pack/.claude/agents/devrites-test-analyst.md +40 -30
- package/pack/.claude/settings.json +2 -1
- package/pack/.claude/skills/devrites-audit/SKILL.md +40 -61
- package/pack/.claude/skills/devrites-debug-recovery/SKILL.md +24 -13
- package/pack/.claude/skills/devrites-debug-recovery/reference/build-the-loop.md +24 -21
- package/pack/.claude/skills/devrites-debug-recovery/reference/cleanup-and-classify.md +34 -6
- package/pack/.claude/skills/devrites-debug-recovery/reference/instrumentation.md +2 -2
- package/pack/.claude/skills/devrites-doubt/SKILL.md +25 -17
- package/pack/.claude/skills/devrites-interview/SKILL.md +53 -52
- package/pack/.claude/skills/devrites-lib/SKILL.md +11 -9
- package/pack/.claude/skills/devrites-lib/reference/intent-map.md +3 -2
- package/pack/.claude/skills/devrites-lib/reference/parallel-dispatch.md +82 -137
- package/pack/.claude/skills/devrites-lib/reference/reply-contract.md +8 -1
- package/pack/.claude/skills/devrites-lib/reference/standards/README.md +38 -55
- package/pack/.claude/skills/devrites-lib/reference/standards/afk-hitl.md +53 -27
- package/pack/.claude/skills/devrites-lib/reference/standards/agents.md +198 -190
- package/pack/.claude/skills/devrites-lib/reference/standards/anti-patterns.md +5 -15
- package/pack/.claude/skills/devrites-lib/reference/standards/ci-cd.md +27 -58
- package/pack/.claude/skills/devrites-lib/reference/standards/code-review.md +16 -52
- package/pack/.claude/skills/devrites-lib/reference/standards/coding-style.md +2 -10
- package/pack/.claude/skills/devrites-lib/reference/standards/context-hygiene.md +18 -61
- package/pack/.claude/skills/devrites-lib/reference/standards/core.md +23 -18
- package/pack/.claude/skills/devrites-lib/reference/standards/deprecation.md +17 -57
- package/pack/.claude/skills/devrites-lib/reference/standards/development-workflow.md +4 -33
- package/pack/.claude/skills/devrites-lib/reference/standards/git-workflow.md +4 -20
- package/pack/.claude/skills/devrites-lib/reference/standards/hooks.md +3 -14
- package/pack/.claude/skills/devrites-lib/reference/standards/patterns.md +9 -25
- package/pack/.claude/skills/devrites-lib/reference/standards/performance.md +0 -9
- package/pack/.claude/skills/devrites-lib/reference/standards/principles.md +1 -3
- package/pack/.claude/skills/devrites-lib/reference/standards/security.md +17 -7
- package/pack/.claude/skills/devrites-lib/reference/standards/testing.md +1 -27
- package/pack/.claude/skills/devrites-lib/reference/standards/tooling.md +46 -50
- package/pack/.claude/skills/devrites-lib/reference/workspace-artifact-schema.md +23 -10
- package/pack/.claude/skills/devrites-source-driven/SKILL.md +23 -22
- package/pack/.claude/skills/rite/SKILL.md +8 -6
- package/pack/.claude/skills/rite/reference/menu.md +8 -6
- package/pack/.claude/skills/rite-adopt/SKILL.md +33 -37
- package/pack/.claude/skills/rite-autocomplete/SKILL.md +33 -28
- package/pack/.claude/skills/rite-autocomplete/reference/loop.md +21 -21
- package/pack/.claude/skills/rite-autocomplete/reference/stop-conditions.md +15 -6
- package/pack/.claude/skills/rite-build/SKILL.md +35 -30
- package/pack/.claude/skills/rite-build/reference/afk-discipline.md +38 -38
- package/pack/.claude/skills/rite-build/reference/evidence-standard.md +10 -0
- package/pack/.claude/skills/rite-build/reference/forge.md +186 -156
- package/pack/.claude/skills/rite-build/reference/one-slice-cycle.md +4 -3
- package/pack/.claude/skills/rite-build/reference/phase-contract.md +96 -175
- package/pack/.claude/skills/rite-build/reference/wright-dispatch.md +129 -134
- package/pack/.claude/skills/rite-clarify/SKILL.md +89 -0
- package/pack/.claude/skills/rite-clarify/reference/decision-coverage.md +59 -0
- package/pack/.claude/skills/rite-converge/SKILL.md +35 -25
- package/pack/.claude/skills/rite-define/SKILL.md +57 -32
- package/pack/.claude/skills/rite-define/reference/gates.md +16 -15
- package/pack/.claude/skills/rite-define/reference/plan-template.md +16 -8
- package/pack/.claude/skills/rite-frame/reference/failure-modes.md +22 -24
- package/pack/.claude/skills/rite-plan/SKILL.md +44 -16
- package/pack/.claude/skills/rite-plan/reference/task-breakdown.md +4 -0
- package/pack/.claude/skills/rite-polish/SKILL.md +27 -23
- package/pack/.claude/skills/rite-prototype/SKILL.md +25 -26
- package/pack/.claude/skills/rite-prove/SKILL.md +27 -17
- package/pack/.claude/skills/rite-resolve/SKILL.md +12 -11
- package/pack/.claude/skills/rite-resolve/reference/answer-protocol.md +3 -0
- package/pack/.claude/skills/rite-review/SKILL.md +37 -28
- package/pack/.claude/skills/rite-seal/reference/phase-contract.md +27 -92
- package/pack/.claude/skills/rite-seal/reference/risk-and-rollback.md +13 -0
- package/pack/.claude/skills/rite-ship/reference/design-memory.md +31 -36
- package/pack/.claude/skills/rite-spec/SKILL.md +84 -135
- package/pack/.claude/skills/rite-spec/reference/investigation.md +37 -33
- package/pack/.claude/skills/rite-spec/reference/question-protocol.md +6 -2
- package/pack/.claude/skills/rite-spec/reference/spec-checklists.md +25 -24
- package/pack/.claude/skills/rite-spec/reference/spec-template.md +9 -2
- package/pack/.claude/skills/rite-spec/reference/state-workspace.md +13 -3
- package/pack/.claude/skills/rite-temper/SKILL.md +62 -47
- package/pack/.claude/skills/rite-temper/reference/review-dimensions.md +24 -22
- package/pack/.claude/skills/rite-vet/SKILL.md +110 -113
- package/pack/.claude/skills/rite-vet/reference/artifacts.md +42 -9
- package/pack/.claude/skills/rite-vet/reference/review-axes.md +39 -37
- package/pack/generated/claude/agents/devrites-code-reviewer.md +62 -48
- package/pack/generated/claude/agents/devrites-devex-reviewer.md +69 -53
- package/pack/generated/claude/agents/devrites-doubt-reviewer.md +29 -22
- package/pack/generated/claude/agents/devrites-evidence-scout.md +69 -0
- package/pack/generated/claude/agents/devrites-forge-judge.md +74 -61
- package/pack/generated/claude/agents/devrites-frontend-reviewer.md +48 -39
- package/pack/generated/claude/agents/devrites-performance-reviewer.md +49 -40
- package/pack/generated/claude/agents/devrites-plan-drafter.md +71 -0
- package/pack/generated/claude/agents/devrites-plan-reviewer.md +80 -47
- package/pack/generated/claude/agents/devrites-proof-runner.md +74 -0
- package/pack/generated/claude/agents/devrites-retrospector.md +48 -45
- package/pack/generated/claude/agents/devrites-security-auditor.md +46 -36
- package/pack/generated/claude/agents/devrites-simplifier-reviewer.md +50 -46
- package/pack/generated/claude/agents/devrites-slice-wright.md +153 -165
- package/pack/generated/claude/agents/devrites-spec-reviewer.md +34 -32
- package/pack/generated/claude/agents/devrites-strategy-reviewer.md +63 -34
- package/pack/generated/claude/agents/devrites-test-analyst.md +40 -30
- package/pack/generated/claude/settings.json +2 -1
- package/pack/generated/claude/skills/devrites-audit/SKILL.md +40 -61
- package/pack/generated/claude/skills/devrites-debug-recovery/SKILL.md +24 -13
- package/pack/generated/claude/skills/devrites-debug-recovery/reference/build-the-loop.md +24 -21
- package/pack/generated/claude/skills/devrites-debug-recovery/reference/cleanup-and-classify.md +34 -6
- package/pack/generated/claude/skills/devrites-debug-recovery/reference/instrumentation.md +2 -2
- package/pack/generated/claude/skills/devrites-doubt/SKILL.md +25 -17
- package/pack/generated/claude/skills/devrites-interview/SKILL.md +53 -52
- package/pack/generated/claude/skills/devrites-lib/SKILL.md +11 -9
- package/pack/generated/claude/skills/devrites-lib/reference/intent-map.md +3 -2
- package/pack/generated/claude/skills/devrites-lib/reference/parallel-dispatch.md +82 -137
- package/pack/generated/claude/skills/devrites-lib/reference/reply-contract.md +8 -1
- package/pack/generated/claude/skills/devrites-lib/reference/standards/README.md +38 -55
- package/pack/generated/claude/skills/devrites-lib/reference/standards/afk-hitl.md +53 -27
- package/pack/generated/claude/skills/devrites-lib/reference/standards/agents.md +198 -190
- package/pack/generated/claude/skills/devrites-lib/reference/standards/anti-patterns.md +5 -15
- package/pack/generated/claude/skills/devrites-lib/reference/standards/ci-cd.md +27 -58
- package/pack/generated/claude/skills/devrites-lib/reference/standards/code-review.md +16 -52
- package/pack/generated/claude/skills/devrites-lib/reference/standards/coding-style.md +2 -10
- package/pack/generated/claude/skills/devrites-lib/reference/standards/context-hygiene.md +18 -61
- package/pack/generated/claude/skills/devrites-lib/reference/standards/core.md +23 -18
- package/pack/generated/claude/skills/devrites-lib/reference/standards/deprecation.md +17 -57
- package/pack/generated/claude/skills/devrites-lib/reference/standards/development-workflow.md +4 -33
- package/pack/generated/claude/skills/devrites-lib/reference/standards/git-workflow.md +4 -20
- package/pack/generated/claude/skills/devrites-lib/reference/standards/hooks.md +3 -14
- package/pack/generated/claude/skills/devrites-lib/reference/standards/patterns.md +9 -25
- package/pack/generated/claude/skills/devrites-lib/reference/standards/performance.md +0 -9
- package/pack/generated/claude/skills/devrites-lib/reference/standards/principles.md +1 -3
- package/pack/generated/claude/skills/devrites-lib/reference/standards/security.md +17 -7
- package/pack/generated/claude/skills/devrites-lib/reference/standards/testing.md +1 -27
- package/pack/generated/claude/skills/devrites-lib/reference/standards/tooling.md +46 -50
- package/pack/generated/claude/skills/devrites-lib/reference/workspace-artifact-schema.md +23 -10
- package/pack/generated/claude/skills/devrites-source-driven/SKILL.md +23 -22
- package/pack/generated/claude/skills/rite/SKILL.md +8 -6
- package/pack/generated/claude/skills/rite/reference/menu.md +8 -6
- package/pack/generated/claude/skills/rite-adopt/SKILL.md +33 -37
- package/pack/generated/claude/skills/rite-autocomplete/SKILL.md +33 -28
- package/pack/generated/claude/skills/rite-autocomplete/reference/loop.md +21 -21
- package/pack/generated/claude/skills/rite-autocomplete/reference/stop-conditions.md +15 -6
- package/pack/generated/claude/skills/rite-build/SKILL.md +35 -30
- package/pack/generated/claude/skills/rite-build/reference/afk-discipline.md +38 -38
- package/pack/generated/claude/skills/rite-build/reference/evidence-standard.md +10 -0
- package/pack/generated/claude/skills/rite-build/reference/forge.md +186 -156
- package/pack/generated/claude/skills/rite-build/reference/one-slice-cycle.md +4 -3
- package/pack/generated/claude/skills/rite-build/reference/phase-contract.md +96 -175
- package/pack/generated/claude/skills/rite-build/reference/wright-dispatch.md +129 -134
- package/pack/generated/claude/skills/rite-clarify/SKILL.md +89 -0
- package/pack/generated/claude/skills/rite-clarify/reference/decision-coverage.md +59 -0
- package/pack/generated/claude/skills/rite-converge/SKILL.md +35 -25
- package/pack/generated/claude/skills/rite-define/SKILL.md +57 -32
- package/pack/generated/claude/skills/rite-define/reference/gates.md +16 -15
- package/pack/generated/claude/skills/rite-define/reference/plan-template.md +16 -8
- package/pack/generated/claude/skills/rite-frame/reference/failure-modes.md +22 -24
- package/pack/generated/claude/skills/rite-plan/SKILL.md +44 -16
- package/pack/generated/claude/skills/rite-plan/reference/task-breakdown.md +4 -0
- package/pack/generated/claude/skills/rite-polish/SKILL.md +27 -23
- package/pack/generated/claude/skills/rite-prototype/SKILL.md +25 -26
- package/pack/generated/claude/skills/rite-prove/SKILL.md +27 -17
- package/pack/generated/claude/skills/rite-resolve/SKILL.md +12 -11
- package/pack/generated/claude/skills/rite-resolve/reference/answer-protocol.md +3 -0
- package/pack/generated/claude/skills/rite-review/SKILL.md +37 -28
- package/pack/generated/claude/skills/rite-seal/reference/phase-contract.md +27 -92
- package/pack/generated/claude/skills/rite-seal/reference/risk-and-rollback.md +13 -0
- package/pack/generated/claude/skills/rite-ship/reference/design-memory.md +31 -36
- package/pack/generated/claude/skills/rite-spec/SKILL.md +84 -135
- package/pack/generated/claude/skills/rite-spec/reference/investigation.md +37 -33
- package/pack/generated/claude/skills/rite-spec/reference/question-protocol.md +6 -2
- package/pack/generated/claude/skills/rite-spec/reference/spec-checklists.md +25 -24
- package/pack/generated/claude/skills/rite-spec/reference/spec-template.md +9 -2
- package/pack/generated/claude/skills/rite-spec/reference/state-workspace.md +13 -3
- package/pack/generated/claude/skills/rite-temper/SKILL.md +62 -47
- package/pack/generated/claude/skills/rite-temper/reference/review-dimensions.md +24 -22
- package/pack/generated/claude/skills/rite-vet/SKILL.md +110 -113
- package/pack/generated/claude/skills/rite-vet/reference/artifacts.md +42 -9
- package/pack/generated/claude/skills/rite-vet/reference/review-axes.md +39 -37
- package/pack/generated/codex/AGENTS.md +5 -2
- package/pack/generated/codex/agents/devrites-code-reviewer.toml +69 -47
- package/pack/generated/codex/agents/devrites-devex-reviewer.toml +75 -51
- package/pack/generated/codex/agents/devrites-doubt-reviewer.toml +35 -20
- package/pack/generated/codex/agents/devrites-evidence-scout.toml +75 -0
- package/pack/generated/codex/agents/devrites-forge-judge.toml +80 -59
- package/pack/generated/codex/agents/devrites-frontend-reviewer.toml +54 -37
- package/pack/generated/codex/agents/devrites-performance-reviewer.toml +55 -38
- package/pack/generated/codex/agents/devrites-plan-drafter.toml +77 -0
- package/pack/generated/codex/agents/devrites-plan-reviewer.toml +82 -47
- package/pack/generated/codex/agents/devrites-proof-runner.toml +80 -0
- package/pack/generated/codex/agents/devrites-retrospector.toml +54 -43
- package/pack/generated/codex/agents/devrites-security-auditor.toml +53 -35
- package/pack/generated/codex/agents/devrites-simplifier-reviewer.toml +56 -44
- package/pack/generated/codex/agents/devrites-slice-wright.toml +159 -163
- package/pack/generated/codex/agents/devrites-spec-reviewer.toml +40 -30
- package/pack/generated/codex/agents/devrites-strategy-reviewer.toml +65 -34
- package/pack/generated/codex/agents/devrites-test-analyst.toml +46 -28
- package/pack/generated/codex/hooks.json +4 -14
- package/pack/generated/codex/skills/devrites-api-interface/SKILL.md +7 -3
- package/pack/generated/codex/skills/devrites-audit/SKILL.md +47 -64
- package/pack/generated/codex/skills/devrites-browser-proof/SKILL.md +7 -3
- package/pack/generated/codex/skills/devrites-debug-recovery/SKILL.md +31 -16
- package/pack/generated/codex/skills/devrites-debug-recovery/reference/build-the-loop.md +24 -21
- package/pack/generated/codex/skills/devrites-debug-recovery/reference/cleanup-and-classify.md +34 -6
- package/pack/generated/codex/skills/devrites-debug-recovery/reference/instrumentation.md +2 -2
- package/pack/generated/codex/skills/devrites-doubt/SKILL.md +32 -20
- package/pack/generated/codex/skills/devrites-frontend-craft/SKILL.md +7 -3
- package/pack/generated/codex/skills/devrites-interview/SKILL.md +60 -55
- package/pack/generated/codex/skills/devrites-lib/SKILL.md +18 -12
- package/pack/generated/codex/skills/devrites-lib/reference/intent-map.md +3 -2
- package/pack/generated/codex/skills/devrites-lib/reference/parallel-dispatch.md +82 -137
- package/pack/generated/codex/skills/devrites-lib/reference/reply-contract.md +8 -1
- package/pack/generated/codex/skills/devrites-lib/reference/standards/README.md +38 -55
- package/pack/generated/codex/skills/devrites-lib/reference/standards/afk-hitl.md +53 -27
- package/pack/generated/codex/skills/devrites-lib/reference/standards/agents.md +198 -190
- package/pack/generated/codex/skills/devrites-lib/reference/standards/anti-patterns.md +5 -15
- package/pack/generated/codex/skills/devrites-lib/reference/standards/ci-cd.md +27 -58
- package/pack/generated/codex/skills/devrites-lib/reference/standards/code-review.md +16 -52
- package/pack/generated/codex/skills/devrites-lib/reference/standards/coding-style.md +2 -10
- package/pack/generated/codex/skills/devrites-lib/reference/standards/context-hygiene.md +18 -61
- package/pack/generated/codex/skills/devrites-lib/reference/standards/core.md +23 -18
- package/pack/generated/codex/skills/devrites-lib/reference/standards/deprecation.md +17 -57
- package/pack/generated/codex/skills/devrites-lib/reference/standards/development-workflow.md +4 -33
- package/pack/generated/codex/skills/devrites-lib/reference/standards/git-workflow.md +4 -20
- package/pack/generated/codex/skills/devrites-lib/reference/standards/hooks.md +3 -14
- package/pack/generated/codex/skills/devrites-lib/reference/standards/patterns.md +9 -25
- package/pack/generated/codex/skills/devrites-lib/reference/standards/performance.md +0 -9
- package/pack/generated/codex/skills/devrites-lib/reference/standards/principles.md +1 -3
- package/pack/generated/codex/skills/devrites-lib/reference/standards/security.md +17 -7
- package/pack/generated/codex/skills/devrites-lib/reference/standards/testing.md +1 -27
- package/pack/generated/codex/skills/devrites-lib/reference/standards/tooling.md +46 -50
- package/pack/generated/codex/skills/devrites-lib/reference/workspace-artifact-schema.md +23 -10
- package/pack/generated/codex/skills/devrites-prose-craft/SKILL.md +7 -3
- package/pack/generated/codex/skills/devrites-refresh-indexes/SKILL.md +7 -3
- package/pack/generated/codex/skills/devrites-source-driven/SKILL.md +29 -24
- package/pack/generated/codex/skills/devrites-ux-shape/SKILL.md +7 -3
- package/pack/generated/codex/skills/rite/SKILL.md +19 -13
- package/pack/generated/codex/skills/rite/reference/menu.md +8 -6
- package/pack/generated/codex/skills/rite-adopt/SKILL.md +40 -40
- package/pack/generated/codex/skills/rite-autocomplete/SKILL.md +40 -31
- package/pack/generated/codex/skills/rite-autocomplete/reference/loop.md +21 -21
- package/pack/generated/codex/skills/rite-autocomplete/reference/stop-conditions.md +15 -6
- package/pack/generated/codex/skills/rite-build/SKILL.md +42 -33
- package/pack/generated/codex/skills/rite-build/reference/afk-discipline.md +38 -38
- package/pack/generated/codex/skills/rite-build/reference/evidence-standard.md +10 -0
- package/pack/generated/codex/skills/rite-build/reference/forge.md +186 -156
- package/pack/generated/codex/skills/rite-build/reference/one-slice-cycle.md +4 -3
- package/pack/generated/codex/skills/rite-build/reference/phase-contract.md +96 -175
- package/pack/generated/codex/skills/rite-build/reference/wright-dispatch.md +129 -134
- package/pack/generated/codex/skills/rite-clarify/SKILL.md +105 -0
- package/pack/generated/codex/skills/rite-clarify/reference/decision-coverage.md +59 -0
- package/pack/generated/codex/skills/rite-converge/SKILL.md +42 -28
- package/pack/generated/codex/skills/rite-customize/SKILL.md +7 -3
- package/pack/generated/codex/skills/rite-define/SKILL.md +64 -35
- package/pack/generated/codex/skills/rite-define/reference/gates.md +16 -15
- package/pack/generated/codex/skills/rite-define/reference/plan-template.md +16 -8
- package/pack/generated/codex/skills/rite-doctor/SKILL.md +7 -3
- package/pack/generated/codex/skills/rite-dogfood/SKILL.md +7 -3
- package/pack/generated/codex/skills/rite-explain/SKILL.md +7 -3
- package/pack/generated/codex/skills/rite-frame/SKILL.md +7 -3
- package/pack/generated/codex/skills/rite-frame/reference/failure-modes.md +22 -24
- package/pack/generated/codex/skills/rite-handoff/SKILL.md +7 -3
- package/pack/generated/codex/skills/rite-learn/SKILL.md +7 -3
- package/pack/generated/codex/skills/rite-plan/SKILL.md +51 -19
- package/pack/generated/codex/skills/rite-plan/reference/task-breakdown.md +5 -1
- package/pack/generated/codex/skills/rite-polish/SKILL.md +34 -26
- package/pack/generated/codex/skills/rite-pov/SKILL.md +7 -3
- package/pack/generated/codex/skills/rite-pr-feedback/SKILL.md +7 -3
- package/pack/generated/codex/skills/rite-pressure-test/SKILL.md +7 -3
- package/pack/generated/codex/skills/rite-prototype/SKILL.md +32 -29
- package/pack/generated/codex/skills/rite-prove/SKILL.md +34 -20
- package/pack/generated/codex/skills/rite-quick/SKILL.md +7 -3
- package/pack/generated/codex/skills/rite-resolve/SKILL.md +19 -14
- package/pack/generated/codex/skills/rite-resolve/reference/answer-protocol.md +3 -0
- package/pack/generated/codex/skills/rite-review/SKILL.md +44 -31
- package/pack/generated/codex/skills/rite-seal/SKILL.md +7 -3
- package/pack/generated/codex/skills/rite-seal/reference/phase-contract.md +27 -92
- package/pack/generated/codex/skills/rite-seal/reference/risk-and-rollback.md +13 -0
- package/pack/generated/codex/skills/rite-ship/SKILL.md +7 -3
- package/pack/generated/codex/skills/rite-ship/reference/design-memory.md +31 -36
- package/pack/generated/codex/skills/rite-spec/SKILL.md +91 -138
- package/pack/generated/codex/skills/rite-spec/reference/investigation.md +37 -33
- package/pack/generated/codex/skills/rite-spec/reference/question-protocol.md +6 -2
- package/pack/generated/codex/skills/rite-spec/reference/spec-checklists.md +25 -24
- package/pack/generated/codex/skills/rite-spec/reference/spec-template.md +9 -2
- package/pack/generated/codex/skills/rite-spec/reference/state-workspace.md +13 -3
- package/pack/generated/codex/skills/rite-status/SKILL.md +7 -3
- package/pack/generated/codex/skills/rite-temper/SKILL.md +69 -50
- package/pack/generated/codex/skills/rite-temper/reference/review-dimensions.md +24 -22
- package/pack/generated/codex/skills/rite-vet/SKILL.md +117 -116
- package/pack/generated/codex/skills/rite-vet/reference/artifacts.md +42 -9
- package/pack/generated/codex/skills/rite-vet/reference/review-axes.md +40 -38
- package/pack/generated/codex/skills/rite-zoom-out/SKILL.md +7 -3
- package/package.json +1 -1
- package/scripts/build-release-tarball.sh +32 -15
- package/scripts/check-authority-drift.py +125 -0
- package/scripts/check-instruction-size-baseline.mjs +19 -11
- package/scripts/check-invocation-integrity.py +2 -0
- package/scripts/codex-generate.sh +69 -33
- package/scripts/grade-feature.sh +121 -40
- package/scripts/live-hosts/agent-result.schema.json +230 -0
- package/scripts/live-hosts/claude.sh +87 -0
- package/scripts/live-hosts/codex.sh +81 -0
- package/scripts/live-hosts/common.sh +113 -0
- package/scripts/live-hosts/fake-host.py +264 -0
- package/scripts/live-hosts/host-transport.py +287 -0
- package/scripts/release-check.sh +5 -1
- package/scripts/run-agent-contract-evals.py +1380 -0
- package/scripts/run-behavioral-evals.sh +24 -30
- package/scripts/run-evals.sh +1 -5
- package/scripts/run-live-behavioral-evals.py +1274 -144
- package/scripts/run-outcome-evals.sh +414 -88
- package/scripts/run-tests.mjs +30 -2
- package/scripts/skills-inventory.mjs +1 -1
- package/scripts/validate-workflow-security.py +39 -20
- package/scripts/validate-workspace-schema.py +362 -10
- package/scripts/validate.sh +21 -15
- package/engine/testdata/golden/TestParityWrightScope/devrites-edit-allowed.golden +0 -1
- /package/engine/testdata/golden/{TestParityReconcile/check-no-claimed.golden → TestParityBuildReadiness/arg=clarifyopen.golden} +0 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"os"
|
|
7
|
+
"path/filepath"
|
|
8
|
+
"strings"
|
|
9
|
+
"testing"
|
|
10
|
+
|
|
11
|
+
"github.com/devrites/devrites/internal/harness"
|
|
12
|
+
"github.com/devrites/devrites/internal/lib"
|
|
13
|
+
"github.com/devrites/devrites/internal/reason"
|
|
14
|
+
"github.com/devrites/devrites/internal/toolpolicy"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
func TestGitClassifierReasonIDsBelongToDiagnosticCatalog(t *testing.T) {
|
|
18
|
+
ids := []toolpolicy.ReasonID{
|
|
19
|
+
toolpolicy.ReasonInputTooLarge,
|
|
20
|
+
toolpolicy.ReasonAmbiguousSyntax,
|
|
21
|
+
toolpolicy.ReasonAmbiguousDynamic,
|
|
22
|
+
toolpolicy.ReasonAmbiguousAlias,
|
|
23
|
+
toolpolicy.ReasonAmbiguousGlobalOption,
|
|
24
|
+
toolpolicy.ReasonAmbiguousStdin,
|
|
25
|
+
toolpolicy.ReasonDestructiveReset,
|
|
26
|
+
toolpolicy.ReasonDestructiveClean,
|
|
27
|
+
toolpolicy.ReasonDestructiveCheckout,
|
|
28
|
+
toolpolicy.ReasonDestructiveRestore,
|
|
29
|
+
toolpolicy.ReasonDestructiveSwitch,
|
|
30
|
+
toolpolicy.ReasonDestructiveRemove,
|
|
31
|
+
toolpolicy.ReasonDestructiveBranch,
|
|
32
|
+
toolpolicy.ReasonDestructiveTag,
|
|
33
|
+
toolpolicy.ReasonDestructiveUpdateRef,
|
|
34
|
+
toolpolicy.ReasonDestructiveStash,
|
|
35
|
+
toolpolicy.ReasonDestructiveReflog,
|
|
36
|
+
toolpolicy.ReasonDestructivePrune,
|
|
37
|
+
toolpolicy.ReasonDestructiveHistory,
|
|
38
|
+
toolpolicy.ReasonDestructivePushForce,
|
|
39
|
+
toolpolicy.ReasonDestructivePushDelete,
|
|
40
|
+
toolpolicy.ReasonDestructiveWorktree,
|
|
41
|
+
toolpolicy.ReasonDestructiveRefDeletion,
|
|
42
|
+
}
|
|
43
|
+
for _, id := range ids {
|
|
44
|
+
if _, err := reason.Parse(string(id)); err != nil {
|
|
45
|
+
t.Errorf("%s: %v", id, err)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
func TestGitGuardOrdinaryGitPassesSilently(t *testing.T) {
|
|
51
|
+
for _, command := range []string{
|
|
52
|
+
"git status --short",
|
|
53
|
+
"git diff -- reset",
|
|
54
|
+
"git add file && git commit -m safe",
|
|
55
|
+
"printf '%s\\n' 'git reset --hard HEAD'",
|
|
56
|
+
} {
|
|
57
|
+
var stdout, stderr bytes.Buffer
|
|
58
|
+
if code := hookGitGuard(harness.Claude, gitGuardInput("Bash", command), &stdout, &stderr); code != 0 {
|
|
59
|
+
t.Fatalf("%q code=%d", command, code)
|
|
60
|
+
}
|
|
61
|
+
if stdout.Len() != 0 || stderr.Len() != 0 {
|
|
62
|
+
t.Fatalf("%q emitted stdout=%q stderr=%q", command, stdout.String(), stderr.String())
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
func TestGitGuardAmbiguousHighImpactAlwaysDenies(t *testing.T) {
|
|
68
|
+
for _, host := range []harness.Harness{harness.Claude, harness.Codex} {
|
|
69
|
+
t.Run(string(host), func(t *testing.T) {
|
|
70
|
+
var stdout, stderr bytes.Buffer
|
|
71
|
+
if code := hookGitGuard(host, gitGuardInput("Bash", "eval 'git reset --hard HEAD'"), &stdout, &stderr); code != 0 {
|
|
72
|
+
t.Fatalf("code=%d", code)
|
|
73
|
+
}
|
|
74
|
+
if !strings.Contains(stdout.String(), `"permissionDecision":"deny"`) ||
|
|
75
|
+
!strings.Contains(stdout.String(), string(reason.GitAmbiguousDynamic)) ||
|
|
76
|
+
!strings.Contains(stdout.String(), "direct, literal git command") {
|
|
77
|
+
t.Fatalf("stdout=%q", stdout.String())
|
|
78
|
+
}
|
|
79
|
+
if strings.Contains(stdout.String(), "reset --hard") || stderr.Len() != 0 {
|
|
80
|
+
t.Fatalf("guard leaked command or stderr: stdout=%q stderr=%q", stdout.String(), stderr.String())
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
func TestGitGuardOpensResolvesAndConsumesExactQuestionForBothHosts(t *testing.T) {
|
|
87
|
+
var hostReasons []string
|
|
88
|
+
for _, host := range []harness.Harness{harness.Claude, harness.Codex} {
|
|
89
|
+
t.Run(string(host), func(t *testing.T) {
|
|
90
|
+
root := makeHookWorkspace(t)
|
|
91
|
+
work := filepath.Join(root, "work", "demo")
|
|
92
|
+
if err := os.WriteFile(filepath.Join(work, "questions.md"), []byte("# Questions\n\nNone.\n"), 0o644); err != nil {
|
|
93
|
+
t.Fatal(err)
|
|
94
|
+
}
|
|
95
|
+
t.Setenv("DEVRITES_NOW", "2026-07-23T10:00:00Z")
|
|
96
|
+
const command = "git reset --hard SECRET-REF"
|
|
97
|
+
|
|
98
|
+
var denied, deniedErr bytes.Buffer
|
|
99
|
+
if code := hookGitGuard(host, gitGuardInput("Bash", command), &denied, &deniedErr); code != 0 {
|
|
100
|
+
t.Fatalf("pending code=%d", code)
|
|
101
|
+
}
|
|
102
|
+
if !strings.Contains(denied.String(), `"permissionDecision":"deny"`) ||
|
|
103
|
+
!strings.Contains(denied.String(), string(reason.GitAuthorityPending)) {
|
|
104
|
+
t.Fatalf("pending stdout=%q", denied.String())
|
|
105
|
+
}
|
|
106
|
+
questionData, err := os.ReadFile(filepath.Join(work, "questions.md"))
|
|
107
|
+
if err != nil {
|
|
108
|
+
t.Fatal(err)
|
|
109
|
+
}
|
|
110
|
+
if bytes.Contains(questionData, []byte("SECRET-REF")) || bytes.Contains(questionData, []byte("reset --hard")) {
|
|
111
|
+
t.Fatalf("question leaked command: %s", questionData)
|
|
112
|
+
}
|
|
113
|
+
qid := gitGuardQuestionID(t, questionData)
|
|
114
|
+
|
|
115
|
+
t.Setenv("DEVRITES_NOW", "2026-07-23T10:01:00Z")
|
|
116
|
+
var resolveOut, resolveErr bytes.Buffer
|
|
117
|
+
if code := lib.Resolve(root, []string{qid, lib.GitAuthorityAnswer}, &resolveOut, &resolveErr); code != 0 {
|
|
118
|
+
t.Fatalf("resolve code=%d stderr=%q", code, resolveErr.String())
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
var allowed, allowedErr bytes.Buffer
|
|
122
|
+
if code := hookGitGuard(host, gitGuardInput("Bash", command), &allowed, &allowedErr); code != 0 {
|
|
123
|
+
t.Fatalf("allow code=%d", code)
|
|
124
|
+
}
|
|
125
|
+
if !strings.Contains(allowed.String(), `"permissionDecision":"allow"`) ||
|
|
126
|
+
!strings.Contains(allowed.String(), string(reason.GitAuthorityGranted)) {
|
|
127
|
+
t.Fatalf("allow stdout=%q", allowed.String())
|
|
128
|
+
}
|
|
129
|
+
ledger, err := os.ReadFile(filepath.Join(work, lib.GitAuthorityLedgerFile))
|
|
130
|
+
if err != nil {
|
|
131
|
+
t.Fatal(err)
|
|
132
|
+
}
|
|
133
|
+
if bytes.Contains(ledger, []byte("SECRET-REF")) || bytes.Contains(ledger, []byte("reset --hard")) {
|
|
134
|
+
t.Fatalf("ledger leaked command: %s", ledger)
|
|
135
|
+
}
|
|
136
|
+
timeline, err := os.ReadFile(filepath.Join(root, "timeline.jsonl"))
|
|
137
|
+
if err != nil {
|
|
138
|
+
t.Fatal(err)
|
|
139
|
+
}
|
|
140
|
+
if bytes.Contains(timeline, []byte("SECRET-REF")) || bytes.Contains(timeline, []byte("reset --hard")) {
|
|
141
|
+
t.Fatalf("timeline leaked command: %s", timeline)
|
|
142
|
+
}
|
|
143
|
+
events := readV1HookEvents(t, root)
|
|
144
|
+
if len(events) != 2 {
|
|
145
|
+
t.Fatalf("events=%+v", events)
|
|
146
|
+
}
|
|
147
|
+
for _, event := range events {
|
|
148
|
+
if event.Boundary != lib.BoundaryGitPolicy || event.Event != "git-guard" ||
|
|
149
|
+
event.Host != lib.EventHost(host) || event.GuardStrength != lib.GuardEnforced {
|
|
150
|
+
t.Fatalf("event=%+v", event)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
hostReasons = append(hostReasons, string(events[0].ReasonID)+"/"+string(events[0].Outcome))
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
if len(hostReasons) != 2 || hostReasons[0] != hostReasons[1] {
|
|
157
|
+
t.Fatalf("host policy drift: %v", hostReasons)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
func TestGitGuardWrongDigestAndNoWorkspaceDeny(t *testing.T) {
|
|
162
|
+
root := makeHookWorkspace(t)
|
|
163
|
+
work := filepath.Join(root, "work", "demo")
|
|
164
|
+
if err := os.WriteFile(filepath.Join(work, "questions.md"), []byte("# Questions\n\nNone.\n"), 0o644); err != nil {
|
|
165
|
+
t.Fatal(err)
|
|
166
|
+
}
|
|
167
|
+
t.Setenv("DEVRITES_NOW", "2026-07-23T10:00:00Z")
|
|
168
|
+
|
|
169
|
+
var first bytes.Buffer
|
|
170
|
+
hookGitGuard(harness.Claude, gitGuardInput("Bash", "git reset --hard HEAD"), &first, &bytes.Buffer{})
|
|
171
|
+
qdata, err := os.ReadFile(filepath.Join(work, "questions.md"))
|
|
172
|
+
if err != nil {
|
|
173
|
+
t.Fatal(err)
|
|
174
|
+
}
|
|
175
|
+
qid := gitGuardQuestionID(t, qdata)
|
|
176
|
+
var resolveOut, resolveErr bytes.Buffer
|
|
177
|
+
if code := lib.Resolve(root, []string{qid, lib.GitAuthorityAnswer}, &resolveOut, &resolveErr); code != 0 {
|
|
178
|
+
t.Fatalf("resolve code=%d stderr=%q", code, resolveErr.String())
|
|
179
|
+
}
|
|
180
|
+
var mismatch bytes.Buffer
|
|
181
|
+
hookGitGuard(harness.Claude, gitGuardInput("Bash", "git clean -fd"), &mismatch, &bytes.Buffer{})
|
|
182
|
+
if !strings.Contains(mismatch.String(), `"permissionDecision":"deny"`) ||
|
|
183
|
+
strings.Contains(mismatch.String(), `"permissionDecision":"allow"`) {
|
|
184
|
+
t.Fatalf("wrong digest stdout=%q", mismatch.String())
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
t.Setenv("DEVRITES_ROOT", filepath.Join(t.TempDir(), ".devrites"))
|
|
188
|
+
if err := os.MkdirAll(os.Getenv("DEVRITES_ROOT"), 0o755); err != nil {
|
|
189
|
+
t.Fatal(err)
|
|
190
|
+
}
|
|
191
|
+
var absent bytes.Buffer
|
|
192
|
+
hookGitGuard(harness.Claude, gitGuardInput("Bash", "git reset --hard HEAD"), &absent, &bytes.Buffer{})
|
|
193
|
+
if !strings.Contains(absent.String(), string(reason.GitWorkspaceUnavailable)) ||
|
|
194
|
+
!strings.Contains(absent.String(), `"permissionDecision":"deny"`) {
|
|
195
|
+
t.Fatalf("no-workspace stdout=%q", absent.String())
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
func TestGitGuardEventFailureNeverChangesConsumedAllow(t *testing.T) {
|
|
200
|
+
root := makeHookWorkspace(t)
|
|
201
|
+
work := filepath.Join(root, "work", "demo")
|
|
202
|
+
if err := os.WriteFile(filepath.Join(work, "questions.md"), []byte("# Questions\n\nNone.\n"), 0o644); err != nil {
|
|
203
|
+
t.Fatal(err)
|
|
204
|
+
}
|
|
205
|
+
t.Setenv("DEVRITES_NOW", "2026-07-23T10:00:00Z")
|
|
206
|
+
const command = "git reset --hard HEAD"
|
|
207
|
+
var pending bytes.Buffer
|
|
208
|
+
hookGitGuard(harness.Claude, gitGuardInput("Bash", command), &pending, &bytes.Buffer{})
|
|
209
|
+
qdata, err := os.ReadFile(filepath.Join(work, "questions.md"))
|
|
210
|
+
if err != nil {
|
|
211
|
+
t.Fatal(err)
|
|
212
|
+
}
|
|
213
|
+
qid := gitGuardQuestionID(t, qdata)
|
|
214
|
+
var resolveOut, resolveErr bytes.Buffer
|
|
215
|
+
if code := lib.Resolve(root, []string{qid, lib.GitAuthorityAnswer}, &resolveOut, &resolveErr); code != 0 {
|
|
216
|
+
t.Fatalf("resolve code=%d stderr=%q", code, resolveErr.String())
|
|
217
|
+
}
|
|
218
|
+
timeline := filepath.Join(root, "timeline.jsonl")
|
|
219
|
+
if err := os.Remove(timeline); err != nil {
|
|
220
|
+
t.Fatal(err)
|
|
221
|
+
}
|
|
222
|
+
if err := os.Mkdir(timeline, 0o755); err != nil {
|
|
223
|
+
t.Fatal(err)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
var allowed bytes.Buffer
|
|
227
|
+
hookGitGuard(harness.Claude, gitGuardInput("Bash", command), &allowed, &bytes.Buffer{})
|
|
228
|
+
if !strings.Contains(allowed.String(), `"permissionDecision":"allow"`) {
|
|
229
|
+
t.Fatalf("telemetry failure changed allow: %q", allowed.String())
|
|
230
|
+
}
|
|
231
|
+
if _, err := os.Stat(filepath.Join(work, lib.GitAuthorityLedgerFile)); err != nil {
|
|
232
|
+
t.Fatalf("grant was not consumed before telemetry: %v", err)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
func TestGitGuardDispatchAndKillPath(t *testing.T) {
|
|
237
|
+
root := makeHookWorkspace(t)
|
|
238
|
+
if err := os.WriteFile(filepath.Join(root, "work", "demo", "questions.md"), []byte("# Questions\n\nNone.\n"), 0o644); err != nil {
|
|
239
|
+
t.Fatal(err)
|
|
240
|
+
}
|
|
241
|
+
payload, _ := json.Marshal(map[string]any{
|
|
242
|
+
"tool_name": "Bash",
|
|
243
|
+
"tool_input": map[string]string{"command": "git reset --hard HEAD"},
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
t.Setenv("DEVRITES_HOOK_PROFILE", "standard")
|
|
247
|
+
t.Setenv("DEVRITES_DISABLED_HOOKS", "git-guard")
|
|
248
|
+
var disabled bytes.Buffer
|
|
249
|
+
if code := run([]string{"hook", "git-guard", "--harness=claude"}, bytes.NewReader(payload), &disabled, &bytes.Buffer{}); code != 0 {
|
|
250
|
+
t.Fatalf("disabled code=%d", code)
|
|
251
|
+
}
|
|
252
|
+
if disabled.Len() != 0 {
|
|
253
|
+
t.Fatalf("disabled hook blocked: %q", disabled.String())
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
t.Setenv("DEVRITES_DISABLED_HOOKS", "")
|
|
257
|
+
var enabled bytes.Buffer
|
|
258
|
+
if code := run([]string{"hook", "git-guard", "--harness=claude"}, bytes.NewReader(payload), &enabled, &bytes.Buffer{}); code != 0 {
|
|
259
|
+
t.Fatalf("enabled code=%d", code)
|
|
260
|
+
}
|
|
261
|
+
if !strings.Contains(enabled.String(), `"permissionDecision":"deny"`) {
|
|
262
|
+
t.Fatalf("enabled hook did not deny: %q", enabled.String())
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
func gitGuardInput(tool, command string) *bytes.Reader {
|
|
267
|
+
data, _ := json.Marshal(map[string]any{
|
|
268
|
+
"tool_name": tool,
|
|
269
|
+
"tool_input": map[string]string{"command": command},
|
|
270
|
+
})
|
|
271
|
+
return bytes.NewReader(data)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
func gitGuardQuestionID(t *testing.T, data []byte) string {
|
|
275
|
+
t.Helper()
|
|
276
|
+
for _, line := range strings.Split(string(data), "\n") {
|
|
277
|
+
if strings.HasPrefix(line, "## q-") {
|
|
278
|
+
return strings.TrimPrefix(line, "## ")
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
t.Fatalf("question id missing:\n%s", data)
|
|
282
|
+
return ""
|
|
283
|
+
}
|
package/engine/hookpolicy.go
CHANGED
|
@@ -5,65 +5,65 @@ import (
|
|
|
5
5
|
"strings"
|
|
6
6
|
)
|
|
7
7
|
|
|
8
|
-
// Hook
|
|
9
|
-
//
|
|
10
|
-
// Every wired hook has a canonical id: its `hook <name>` CLI verb (allow,
|
|
11
|
-
// orient, stop-gate, …). That id is the addressable handle for two orthogonal
|
|
12
|
-
// runtime controls, both read from the environment so an operator tunes safety
|
|
13
|
-
// vs speed without editing any JSON:
|
|
8
|
+
// Hook names are also their canonical IDs. Two environment settings control
|
|
9
|
+
// whether a hook runs:
|
|
14
10
|
//
|
|
15
11
|
// DEVRITES_HOOK_PROFILE=minimal|standard|strict which hooks run at all
|
|
16
|
-
// DEVRITES_DISABLED_HOOKS="stop-gate,a1-guard"
|
|
12
|
+
// DEVRITES_DISABLED_HOOKS="stop-gate,a1-guard" hook IDs to disable
|
|
17
13
|
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
// exit 0` guard, so a disabled hook is indistinguishable from an absent binary.
|
|
14
|
+
// A hook runs when its tier is at or below the selected profile and its ID is
|
|
15
|
+
// not disabled. Disabled hooks return success without doing anything, just as
|
|
16
|
+
// they would if the binary were absent.
|
|
22
17
|
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
// one env var, not four.
|
|
18
|
+
// Enforcement is separate from activation. Main-thread guards observe by
|
|
19
|
+
// default until their DEVRITES_*=enforce variable is set. The strict profile
|
|
20
|
+
// enforces every legacy observe path. reviewer-readonly and wright-scope always
|
|
21
|
+
// enforce policy for declared DevRites leaf agents.
|
|
28
22
|
|
|
29
23
|
// hookTier is the minimum profile level at which a hook is active.
|
|
30
24
|
type hookTier int
|
|
31
25
|
|
|
32
26
|
const (
|
|
33
|
-
tierMinimal hookTier = iota // orientation
|
|
34
|
-
tierStandard //
|
|
35
|
-
tierStrict //
|
|
27
|
+
tierMinimal hookTier = iota // orientation only; never blocks or changes flow
|
|
28
|
+
tierStandard // default gates, guards, and caches
|
|
29
|
+
tierStrict // experimental or aggressive hooks
|
|
36
30
|
)
|
|
37
31
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
32
|
+
type hookDefinition struct {
|
|
33
|
+
tier hookTier
|
|
34
|
+
canBlock bool
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// hookRegistry records each hook's tier and whether it can block. The blocker
|
|
38
|
+
// audit derives its cases from canBlock, so a new blocker must cover reentry,
|
|
39
|
+
// malformed input, bounds, and the disabled path. Unknown hook names stay
|
|
40
|
+
// active instead of being silently disabled.
|
|
41
|
+
var hookRegistry = map[string]hookDefinition{
|
|
42
|
+
// Minimal hooks provide orientation and automatic approval without
|
|
43
|
+
// interrupting work.
|
|
44
|
+
"allow": {tier: tierMinimal},
|
|
45
|
+
"orient": {tier: tierMinimal},
|
|
46
|
+
"subagent-orient": {tier: tierMinimal},
|
|
47
|
+
"cursor": {tier: tierMinimal},
|
|
48
|
+
"statusline": {tier: tierMinimal},
|
|
49
49
|
|
|
50
|
-
//
|
|
51
|
-
"a1-guard": tierStandard,
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"source-cache-
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
50
|
+
// Standard hooks add the default gates, guards, sentinels, and caches.
|
|
51
|
+
"a1-guard": {tier: tierStandard, canBlock: true},
|
|
52
|
+
"git-guard": {tier: tierStandard, canBlock: true},
|
|
53
|
+
"stop-gate": {tier: tierStandard, canBlock: true},
|
|
54
|
+
"reviewer-readonly": {tier: tierStandard, canBlock: true},
|
|
55
|
+
"wright-scope": {tier: tierStandard, canBlock: true},
|
|
56
|
+
"redwatch": {tier: tierStandard},
|
|
57
|
+
"source-cache-pre": {tier: tierStandard},
|
|
58
|
+
"source-cache-post": {tier: tierStandard},
|
|
59
|
+
"refresh-indexes": {tier: tierStandard},
|
|
60
|
+
"event": {tier: tierStandard},
|
|
61
|
+
"auq": {tier: tierStandard},
|
|
62
|
+
"handoff-snapshot": {tier: tierStandard},
|
|
62
63
|
}
|
|
63
64
|
|
|
64
|
-
// hookProfileTier resolves DEVRITES_HOOK_PROFILE
|
|
65
|
-
// standard
|
|
66
|
-
// strips the working set below default).
|
|
65
|
+
// hookProfileTier resolves DEVRITES_HOOK_PROFILE. Empty or unrecognized input
|
|
66
|
+
// uses the standard tier so a typo cannot disable the default hooks.
|
|
67
67
|
func hookProfileTier() hookTier {
|
|
68
68
|
switch strings.ToLower(strings.TrimSpace(os.Getenv("DEVRITES_HOOK_PROFILE"))) {
|
|
69
69
|
case "minimal":
|
|
@@ -75,21 +75,20 @@ func hookProfileTier() hookTier {
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
// hookActive reports whether
|
|
79
|
-
//
|
|
78
|
+
// hookActive reports whether the current profile and disabled list allow a hook
|
|
79
|
+
// ID to run. cmdHook checks it before dispatch.
|
|
80
80
|
func hookActive(name string) bool {
|
|
81
81
|
if hookDisabled(name) {
|
|
82
82
|
return false
|
|
83
83
|
}
|
|
84
|
-
|
|
84
|
+
def, known := hookRegistry[name]
|
|
85
85
|
if !known {
|
|
86
|
-
return true //
|
|
86
|
+
return true // Profiles do not disable unknown IDs.
|
|
87
87
|
}
|
|
88
|
-
return tier <= hookProfileTier()
|
|
88
|
+
return def.tier <= hookProfileTier()
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
// hookDisabled reports whether
|
|
92
|
-
// comma-separated kill list.
|
|
91
|
+
// hookDisabled reports whether DEVRITES_DISABLED_HOOKS contains the hook ID.
|
|
93
92
|
func hookDisabled(name string) bool {
|
|
94
93
|
list := os.Getenv("DEVRITES_DISABLED_HOOKS")
|
|
95
94
|
if list == "" {
|
|
@@ -103,9 +102,8 @@ func hookDisabled(name string) bool {
|
|
|
103
102
|
return false
|
|
104
103
|
}
|
|
105
104
|
|
|
106
|
-
// hookEnforce reports whether
|
|
107
|
-
//
|
|
108
|
-
// the strict profile is active: strict enforces every guard at once.
|
|
105
|
+
// hookEnforce reports whether a guard that normally observes should block. Its
|
|
106
|
+
// own enforce variable or the strict profile enables blocking.
|
|
109
107
|
func hookEnforce(specificVar string) bool {
|
|
110
108
|
if os.Getenv(specificVar) == "enforce" {
|
|
111
109
|
return true
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
package main
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"strings"
|
|
6
|
+
"testing"
|
|
7
|
+
)
|
|
4
8
|
|
|
5
9
|
func TestHookActiveProfileTiers(t *testing.T) {
|
|
6
10
|
cases := []struct {
|
|
@@ -62,3 +66,89 @@ func TestHookEnforce(t *testing.T) {
|
|
|
62
66
|
t.Error("strict profile should enforce every guard")
|
|
63
67
|
}
|
|
64
68
|
}
|
|
69
|
+
|
|
70
|
+
func TestBlockingHookExitAudit(t *testing.T) {
|
|
71
|
+
type exitContract struct {
|
|
72
|
+
reentry string
|
|
73
|
+
malformed string
|
|
74
|
+
bound string
|
|
75
|
+
killPath string
|
|
76
|
+
}
|
|
77
|
+
audit := map[string]exitContract{
|
|
78
|
+
"stop-gate": {
|
|
79
|
+
reentry: "explicit stop_hook_active=true lets the re-entered Stop complete",
|
|
80
|
+
malformed: "empty, malformed, missing, null, or non-boolean stop_hook_active fails open",
|
|
81
|
+
bound: "one block before the host re-enters with stop_hook_active=true",
|
|
82
|
+
killPath: "DEVRITES_DISABLED_HOOKS=stop-gate or DEVRITES_HOOK_PROFILE=minimal",
|
|
83
|
+
},
|
|
84
|
+
"reviewer-readonly": {
|
|
85
|
+
reentry: "not applicable: each PreToolUse decision applies to one tool request",
|
|
86
|
+
malformed: "missing or malformed tool identity fails open",
|
|
87
|
+
bound: "one synchronous deny; no retry or subprocess",
|
|
88
|
+
killPath: "DEVRITES_DISABLED_HOOKS=reviewer-readonly or DEVRITES_HOOK_PROFILE=minimal",
|
|
89
|
+
},
|
|
90
|
+
"a1-guard": {
|
|
91
|
+
reentry: "not applicable: each PreToolUse decision applies to one tool request",
|
|
92
|
+
malformed: "missing or malformed tool identity fails open",
|
|
93
|
+
bound: "one synchronous deny; no retry or subprocess",
|
|
94
|
+
killPath: "DEVRITES_DISABLED_HOOKS=a1-guard or DEVRITES_HOOK_PROFILE=minimal",
|
|
95
|
+
},
|
|
96
|
+
"wright-scope": {
|
|
97
|
+
reentry: "not applicable: each PreToolUse decision applies to one tool request",
|
|
98
|
+
malformed: "missing or malformed tool identity fails open",
|
|
99
|
+
bound: "one synchronous deny; no retry or subprocess",
|
|
100
|
+
killPath: "DEVRITES_DISABLED_HOOKS=wright-scope or DEVRITES_HOOK_PROFILE=minimal",
|
|
101
|
+
},
|
|
102
|
+
"git-guard": {
|
|
103
|
+
reentry: "not applicable: each PreToolUse decision applies to one tool request",
|
|
104
|
+
malformed: "missing or malformed structured tool input fails open",
|
|
105
|
+
bound: "one synchronous deny or one consumed one-shot allow; no retry or subprocess",
|
|
106
|
+
killPath: "DEVRITES_DISABLED_HOOKS=git-guard or DEVRITES_HOOK_PROFILE=minimal",
|
|
107
|
+
},
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
for name, def := range hookRegistry {
|
|
111
|
+
if !def.canBlock {
|
|
112
|
+
continue
|
|
113
|
+
}
|
|
114
|
+
contract, ok := audit[name]
|
|
115
|
+
if !ok {
|
|
116
|
+
t.Errorf("production blocker %q has no exit audit row", name)
|
|
117
|
+
continue
|
|
118
|
+
}
|
|
119
|
+
if contract.reentry == "" || contract.malformed == "" || contract.bound == "" || contract.killPath == "" {
|
|
120
|
+
t.Errorf("blocker %q has an incomplete exit audit row: %#v", name, contract)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
for name := range audit {
|
|
124
|
+
def, ok := hookRegistry[name]
|
|
125
|
+
if !ok || !def.canBlock {
|
|
126
|
+
t.Errorf("exit audit row %q is not a production blocker", name)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
for name := range audit {
|
|
131
|
+
t.Run(name, func(t *testing.T) {
|
|
132
|
+
t.Setenv("DEVRITES_HOOK_PROFILE", "standard")
|
|
133
|
+
t.Setenv("DEVRITES_DISABLED_HOOKS", name)
|
|
134
|
+
if hookActive(name) {
|
|
135
|
+
t.Fatal("per-hook kill list did not disable blocker")
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
t.Setenv("DEVRITES_DISABLED_HOOKS", "")
|
|
139
|
+
t.Setenv("DEVRITES_HOOK_PROFILE", "minimal")
|
|
140
|
+
if hookActive(name) {
|
|
141
|
+
t.Fatal("minimal profile did not disable blocker")
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
t.Setenv("DEVRITES_HOOK_PROFILE", "standard")
|
|
145
|
+
var stdout, stderr bytes.Buffer
|
|
146
|
+
if code := run([]string{"hook", name, "--harness=claude"}, strings.NewReader("not-json"), &stdout, &stderr); code != exitOK {
|
|
147
|
+
t.Fatalf("malformed input exit = %d, want 0 (stderr %q)", code, stderr.String())
|
|
148
|
+
}
|
|
149
|
+
if stdout.Len() != 0 {
|
|
150
|
+
t.Fatalf("malformed input emitted a blocking decision: %q", stdout.String())
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
}
|