devrites 3.0.7 → 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 +12 -0
- package/README.md +67 -47
- 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 +486 -38
- package/engine/internal/install/install_test.go +383 -10
- 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
|
@@ -6,6 +6,7 @@ import (
|
|
|
6
6
|
"compress/gzip"
|
|
7
7
|
"context"
|
|
8
8
|
"crypto/sha256"
|
|
9
|
+
"encoding/hex"
|
|
9
10
|
"encoding/json"
|
|
10
11
|
"errors"
|
|
11
12
|
"flag"
|
|
@@ -26,6 +27,7 @@ import (
|
|
|
26
27
|
"github.com/devrites/devrites/internal/fsutil"
|
|
27
28
|
"github.com/devrites/devrites/internal/hostpack"
|
|
28
29
|
"github.com/devrites/devrites/internal/iohooks"
|
|
30
|
+
"github.com/devrites/devrites/internal/safepath"
|
|
29
31
|
"github.com/devrites/devrites/internal/version"
|
|
30
32
|
)
|
|
31
33
|
|
|
@@ -78,12 +80,22 @@ type runner struct {
|
|
|
78
80
|
source string
|
|
79
81
|
manifest []string
|
|
80
82
|
records map[string]string
|
|
81
|
-
prev map[string]
|
|
83
|
+
prev map[string]managedRecord
|
|
84
|
+
preflight map[string]pathSnapshot
|
|
82
85
|
stats stats
|
|
83
86
|
releaseBinaryTag string
|
|
84
87
|
preparedBinary string
|
|
85
88
|
}
|
|
86
89
|
|
|
90
|
+
type managedRecord struct {
|
|
91
|
+
Hash string
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
type pathSnapshot struct {
|
|
95
|
+
missing bool
|
|
96
|
+
hash string
|
|
97
|
+
}
|
|
98
|
+
|
|
87
99
|
func DefaultOptions(mode Mode) Options {
|
|
88
100
|
return Options{
|
|
89
101
|
Mode: mode,
|
|
@@ -213,7 +225,7 @@ func flagWasSet(flags *flag.FlagSet, name string) bool {
|
|
|
213
225
|
func usage(mode Mode) string {
|
|
214
226
|
switch mode {
|
|
215
227
|
case ModeUninstall:
|
|
216
|
-
return "usage: devrites-engine uninstall [--target DIR] [--dry-run] [--keep-binary]\n"
|
|
228
|
+
return "usage: devrites-engine uninstall [--target DIR] [--dry-run] [--force] [--keep-binary]\n"
|
|
217
229
|
case ModeUpdate:
|
|
218
230
|
return "usage: devrites-engine update [--target DIR] [--dry-run] [--force] [--check] [--to TAG] [install flags]\n"
|
|
219
231
|
default:
|
|
@@ -277,7 +289,16 @@ func newRunner(opts Options) (*runner, error) {
|
|
|
277
289
|
if payload != "" {
|
|
278
290
|
payload, _ = filepath.Abs(payload)
|
|
279
291
|
}
|
|
280
|
-
r := &runner{
|
|
292
|
+
r := &runner{
|
|
293
|
+
opts: opts,
|
|
294
|
+
target: target,
|
|
295
|
+
payload: payload,
|
|
296
|
+
payloadFS: os.DirFS(payload),
|
|
297
|
+
source: source,
|
|
298
|
+
prev: map[string]managedRecord{},
|
|
299
|
+
records: map[string]string{},
|
|
300
|
+
preflight: map[string]pathSnapshot{},
|
|
301
|
+
}
|
|
281
302
|
if opts.Mode != ModeUninstall {
|
|
282
303
|
if err := r.validatePayload(); err != nil {
|
|
283
304
|
return nil, fmt.Errorf("validate payload: %w", err)
|
|
@@ -343,6 +364,14 @@ func (r *runner) install() error {
|
|
|
343
364
|
fmt.Fprintln(r.opts.Stdout, " (dry run - no changes will be made)")
|
|
344
365
|
}
|
|
345
366
|
fmt.Fprintln(r.opts.Stdout)
|
|
367
|
+
if r.preparedBinary != "" {
|
|
368
|
+
if _, err := verifyEngineBinary(r.preparedBinary, r.binaryTag(), 30*time.Second); err != nil {
|
|
369
|
+
return fmt.Errorf("verify staged engine binary %s: %w", r.preparedBinary, err)
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
if err := r.preflightInstall(); err != nil {
|
|
373
|
+
return err
|
|
374
|
+
}
|
|
346
375
|
|
|
347
376
|
for _, tree := range hostpack.InstallTrees(r.opts.WithSkills, r.opts.WithAgents, r.opts.WithCodex) {
|
|
348
377
|
if err := r.installTree(tree.PayloadPrefix, tree.TargetPrefix); err != nil {
|
|
@@ -424,12 +453,233 @@ func (r *runner) installFile(src, rel string) error {
|
|
|
424
453
|
return r.installData(data, rel)
|
|
425
454
|
}
|
|
426
455
|
|
|
456
|
+
func (r *runner) preflightInstall() error {
|
|
457
|
+
desired, err := r.desiredInstallPaths()
|
|
458
|
+
if err != nil {
|
|
459
|
+
return fmt.Errorf("preflight install paths: %w", err)
|
|
460
|
+
}
|
|
461
|
+
var conflicts []string
|
|
462
|
+
for rel := range desired {
|
|
463
|
+
snapshot, err := r.rememberPath(rel)
|
|
464
|
+
if err != nil {
|
|
465
|
+
return err
|
|
466
|
+
}
|
|
467
|
+
record, managed := r.prev[rel]
|
|
468
|
+
if managed && !snapshot.missing {
|
|
469
|
+
if kind := managedConflict(record, snapshot); kind != "" && !r.opts.Force {
|
|
470
|
+
conflicts = append(conflicts, fmt.Sprintf("%s: %s", kind, rel))
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
for rel, record := range r.prev {
|
|
475
|
+
if desired[rel] || hostpack.PreserveOnPrune(rel) {
|
|
476
|
+
continue
|
|
477
|
+
}
|
|
478
|
+
snapshot, err := r.rememberPath(rel)
|
|
479
|
+
if err != nil {
|
|
480
|
+
return err
|
|
481
|
+
}
|
|
482
|
+
if !snapshot.missing {
|
|
483
|
+
if kind := managedConflict(record, snapshot); kind != "" && !r.opts.Force {
|
|
484
|
+
conflicts = append(conflicts, fmt.Sprintf("%s: %s", kind, rel))
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
if merge, ok := hostpack.ManagedMergeForMarker(rel); ok {
|
|
488
|
+
if _, err := r.rememberPath(merge.TargetRel); err != nil {
|
|
489
|
+
return err
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
for _, rel := range r.installMergeTargets() {
|
|
494
|
+
if _, err := r.rememberPath(rel); err != nil {
|
|
495
|
+
return err
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
if _, err := r.rememberPath(ManifestName); err != nil {
|
|
499
|
+
return err
|
|
500
|
+
}
|
|
501
|
+
return managedConflictError(conflicts)
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
func (r *runner) preflightUninstall(entries []string) error {
|
|
505
|
+
var conflicts []string
|
|
506
|
+
for _, rel := range entries {
|
|
507
|
+
if merge, ok := hostpack.ManagedMergeForMarker(rel); ok {
|
|
508
|
+
if _, err := r.rememberPath(merge.TargetRel); err != nil {
|
|
509
|
+
return err
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
if !hostpack.ShouldRemoveOnUninstall(rel, entries) {
|
|
513
|
+
continue
|
|
514
|
+
}
|
|
515
|
+
snapshot, err := r.rememberPath(rel)
|
|
516
|
+
if err != nil {
|
|
517
|
+
return err
|
|
518
|
+
}
|
|
519
|
+
if snapshot.missing {
|
|
520
|
+
continue
|
|
521
|
+
}
|
|
522
|
+
if kind := managedConflict(r.prev[rel], snapshot); kind != "" && !r.opts.Force {
|
|
523
|
+
conflicts = append(conflicts, fmt.Sprintf("%s: %s", kind, rel))
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
if _, err := r.rememberPath(ManifestName); err != nil {
|
|
527
|
+
return err
|
|
528
|
+
}
|
|
529
|
+
return managedConflictError(conflicts)
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
func (r *runner) desiredInstallPaths() (map[string]bool, error) {
|
|
533
|
+
out := map[string]bool{".devrites/README.md": true}
|
|
534
|
+
for _, tree := range hostpack.InstallTrees(r.opts.WithSkills, r.opts.WithAgents, r.opts.WithCodex) {
|
|
535
|
+
err := fs.WalkDir(r.payloadFS, tree.PayloadPrefix, func(path string, d fs.DirEntry, err error) error {
|
|
536
|
+
if err != nil {
|
|
537
|
+
return err
|
|
538
|
+
}
|
|
539
|
+
if d.IsDir() {
|
|
540
|
+
return nil
|
|
541
|
+
}
|
|
542
|
+
rel, err := filepath.Rel(tree.PayloadPrefix, path)
|
|
543
|
+
if err != nil {
|
|
544
|
+
return err
|
|
545
|
+
}
|
|
546
|
+
out[filepath.ToSlash(filepath.Join(tree.TargetPrefix, rel))] = true
|
|
547
|
+
return nil
|
|
548
|
+
})
|
|
549
|
+
if err != nil {
|
|
550
|
+
return nil, err
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
if r.opts.WithSkills && r.opts.AliasMode == "all" {
|
|
554
|
+
for _, alias := range hostpack.Aliases {
|
|
555
|
+
for _, rel := range hostpack.AliasTargets(alias, r.opts.WithCodex) {
|
|
556
|
+
out[rel] = true
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
if r.opts.WithSkills && r.opts.WithCodex {
|
|
561
|
+
out[hostpack.CodexAgentsMerge.MarkerRel] = true
|
|
562
|
+
out[hostpack.CodexHooksMerge.MarkerRel] = true
|
|
563
|
+
}
|
|
564
|
+
if r.opts.WithSkills {
|
|
565
|
+
out[hostpack.ClaudeSettingsMerge.MarkerRel] = true
|
|
566
|
+
}
|
|
567
|
+
return out, nil
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
func (r *runner) installMergeTargets() []string {
|
|
571
|
+
var out []string
|
|
572
|
+
if r.opts.WithSkills {
|
|
573
|
+
out = append(out, hostpack.ClaudeSettingsMerge.TargetRel)
|
|
574
|
+
if r.opts.WithCodex {
|
|
575
|
+
out = append(out, hostpack.CodexAgentsMerge.TargetRel, hostpack.CodexHooksMerge.TargetRel)
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
return out
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
func (r *runner) rememberPath(rel string) (pathSnapshot, error) {
|
|
582
|
+
rel = filepath.ToSlash(rel)
|
|
583
|
+
if snapshot, ok := r.preflight[rel]; ok {
|
|
584
|
+
return snapshot, nil
|
|
585
|
+
}
|
|
586
|
+
snapshot, err := inspectManagedPath(r.target, rel)
|
|
587
|
+
if err != nil {
|
|
588
|
+
return pathSnapshot{}, err
|
|
589
|
+
}
|
|
590
|
+
r.preflight[rel] = snapshot
|
|
591
|
+
return snapshot, nil
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
func (r *runner) recheckPath(rel string) error {
|
|
595
|
+
rel = filepath.ToSlash(rel)
|
|
596
|
+
before, ok := r.preflight[rel]
|
|
597
|
+
if !ok {
|
|
598
|
+
return fmt.Errorf("path %s was not preflighted", rel)
|
|
599
|
+
}
|
|
600
|
+
now, err := inspectManagedPath(r.target, rel)
|
|
601
|
+
if err != nil {
|
|
602
|
+
return err
|
|
603
|
+
}
|
|
604
|
+
if now != before {
|
|
605
|
+
return fmt.Errorf("refusing to change %s: file changed after preflight; retry the operation", rel)
|
|
606
|
+
}
|
|
607
|
+
return nil
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
func inspectManagedPath(target, rel string) (pathSnapshot, error) {
|
|
611
|
+
native := filepath.FromSlash(rel)
|
|
612
|
+
if !filepath.IsLocal(native) {
|
|
613
|
+
return pathSnapshot{}, fmt.Errorf("refusing unsafe managed path %q", rel)
|
|
614
|
+
}
|
|
615
|
+
dest := filepath.Join(target, native)
|
|
616
|
+
if !safepath.WithinResolved(dest, target) {
|
|
617
|
+
return pathSnapshot{}, fmt.Errorf("refusing managed path outside target: %s", rel)
|
|
618
|
+
}
|
|
619
|
+
walk := target
|
|
620
|
+
parts := strings.Split(native, string(filepath.Separator))
|
|
621
|
+
for _, part := range parts {
|
|
622
|
+
walk = filepath.Join(walk, part)
|
|
623
|
+
info, err := os.Lstat(walk)
|
|
624
|
+
if os.IsNotExist(err) {
|
|
625
|
+
return pathSnapshot{missing: true}, nil
|
|
626
|
+
}
|
|
627
|
+
if err != nil {
|
|
628
|
+
return pathSnapshot{}, fmt.Errorf("inspect managed path %s: %w", rel, err)
|
|
629
|
+
}
|
|
630
|
+
if info.Mode()&os.ModeSymlink != 0 {
|
|
631
|
+
return pathSnapshot{}, fmt.Errorf("refusing symlink or junction in managed path: %s", rel)
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
info, err := os.Lstat(dest)
|
|
635
|
+
if os.IsNotExist(err) {
|
|
636
|
+
return pathSnapshot{missing: true}, nil
|
|
637
|
+
}
|
|
638
|
+
if err != nil {
|
|
639
|
+
return pathSnapshot{}, fmt.Errorf("inspect managed path %s: %w", rel, err)
|
|
640
|
+
}
|
|
641
|
+
if !info.Mode().IsRegular() {
|
|
642
|
+
return pathSnapshot{}, fmt.Errorf("refusing non-regular managed path: %s", rel)
|
|
643
|
+
}
|
|
644
|
+
f, err := os.Open(dest)
|
|
645
|
+
if err != nil {
|
|
646
|
+
return pathSnapshot{}, fmt.Errorf("read managed path %s: %w", rel, err)
|
|
647
|
+
}
|
|
648
|
+
defer f.Close()
|
|
649
|
+
sum := sha256.New()
|
|
650
|
+
if _, err := io.Copy(sum, f); err != nil {
|
|
651
|
+
return pathSnapshot{}, fmt.Errorf("hash managed path %s: %w", rel, err)
|
|
652
|
+
}
|
|
653
|
+
return pathSnapshot{hash: "sha256:" + hex.EncodeToString(sum.Sum(nil))}, nil
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
func managedConflict(record managedRecord, snapshot pathSnapshot) string {
|
|
657
|
+
if record.Hash == "" {
|
|
658
|
+
return "legacy manifest entry (no hash)"
|
|
659
|
+
}
|
|
660
|
+
if record.Hash != snapshot.hash {
|
|
661
|
+
return "customized managed file"
|
|
662
|
+
}
|
|
663
|
+
return ""
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
func managedConflictError(conflicts []string) error {
|
|
667
|
+
if len(conflicts) == 0 {
|
|
668
|
+
return nil
|
|
669
|
+
}
|
|
670
|
+
sort.Strings(conflicts)
|
|
671
|
+
return fmt.Errorf("managed files differ from the install manifest:\n %s\nrerun with --force to replace or remove these files", strings.Join(conflicts, "\n "))
|
|
672
|
+
}
|
|
673
|
+
|
|
427
674
|
func (r *runner) installData(data []byte, rel string) error {
|
|
428
675
|
dest := filepath.Join(r.target, filepath.FromSlash(rel))
|
|
429
676
|
action := "install"
|
|
430
677
|
if exists(dest) {
|
|
678
|
+
record, managed := r.prev[rel]
|
|
431
679
|
switch {
|
|
432
|
-
case r.
|
|
680
|
+
case managed && managedConflict(record, r.preflight[filepath.ToSlash(rel)]) != "" && r.opts.Force:
|
|
681
|
+
action = "overwrite(force-customized)"
|
|
682
|
+
case managed:
|
|
433
683
|
action = "overwrite"
|
|
434
684
|
case r.opts.Force:
|
|
435
685
|
action = "overwrite(force)"
|
|
@@ -445,8 +695,13 @@ func (r *runner) installData(data []byte, rel string) error {
|
|
|
445
695
|
}
|
|
446
696
|
if r.opts.DryRun {
|
|
447
697
|
fmt.Fprintf(r.opts.Stdout, " [%s] %s\n", action, rel)
|
|
448
|
-
} else
|
|
449
|
-
|
|
698
|
+
} else {
|
|
699
|
+
if err := r.recheckPath(rel); err != nil {
|
|
700
|
+
return err
|
|
701
|
+
}
|
|
702
|
+
if err := fsutil.WriteFileAtomic(dest, data, 0o644); err != nil {
|
|
703
|
+
return fmt.Errorf("cannot write %s: %w", rel, err)
|
|
704
|
+
}
|
|
450
705
|
}
|
|
451
706
|
r.addManifest(rel)
|
|
452
707
|
r.addInstallRecord(rel, data)
|
|
@@ -492,6 +747,9 @@ func (r *runner) mergeMarkerFile(merge hostpack.MarkerMerge) error {
|
|
|
492
747
|
}
|
|
493
748
|
fmt.Fprintf(r.opts.Stdout, " [merge] %s (%s)\n", merge.TargetRel, verb)
|
|
494
749
|
} else {
|
|
750
|
+
if err := r.recheckPath(merge.TargetRel); err != nil {
|
|
751
|
+
return err
|
|
752
|
+
}
|
|
495
753
|
next := block
|
|
496
754
|
if current, err := os.ReadFile(dest); err == nil {
|
|
497
755
|
next = hostpack.MergeMarkerBlock(current, block, merge.Begin, merge.End)
|
|
@@ -513,6 +771,9 @@ func (r *runner) mergeJSONHooks(merge hostpack.JSONMerge) error {
|
|
|
513
771
|
fmt.Fprintf(r.opts.Stdout, " [merge] %s\n", merge.DryRunText)
|
|
514
772
|
return r.installMarker(merge.MarkerRel, merge.MarkerText)
|
|
515
773
|
}
|
|
774
|
+
if err := r.recheckPath(merge.TargetRel); err != nil {
|
|
775
|
+
return err
|
|
776
|
+
}
|
|
516
777
|
next := devrites
|
|
517
778
|
if current, err := readJSON(dest); err == nil {
|
|
518
779
|
if currentStatus, exists := current["statusLine"]; exists && !isDevritesHookCommand(currentStatus) {
|
|
@@ -546,7 +807,7 @@ func (r *runner) seedClaudeSettings() error {
|
|
|
546
807
|
if err != nil {
|
|
547
808
|
return fmt.Errorf("load Claude settings payload: %w", err)
|
|
548
809
|
}
|
|
549
|
-
managed := r.prev[merge.MarkerRel]
|
|
810
|
+
_, managed := r.prev[merge.MarkerRel]
|
|
550
811
|
if reflect.DeepEqual(current, devrites) {
|
|
551
812
|
if managed {
|
|
552
813
|
return r.installMarker(merge.MarkerRel, merge.MarkerText)
|
|
@@ -562,6 +823,9 @@ func (r *runner) seedClaudeSettings() error {
|
|
|
562
823
|
if err != nil {
|
|
563
824
|
return fmt.Errorf("read payload claude/settings.json: %w", err)
|
|
564
825
|
}
|
|
826
|
+
if err := r.recheckPath(rel); err != nil {
|
|
827
|
+
return err
|
|
828
|
+
}
|
|
565
829
|
return fsutil.WriteFileAtomic(dest, data, 0o644)
|
|
566
830
|
}
|
|
567
831
|
return r.mergeJSONHooks(merge)
|
|
@@ -574,6 +838,9 @@ func (r *runner) seedClaudeSettings() error {
|
|
|
574
838
|
if err != nil {
|
|
575
839
|
return fmt.Errorf("read payload claude/settings.json: %w", err)
|
|
576
840
|
}
|
|
841
|
+
if err := r.recheckPath(rel); err != nil {
|
|
842
|
+
return err
|
|
843
|
+
}
|
|
577
844
|
return fsutil.WriteFileAtomic(dest, data, 0o644)
|
|
578
845
|
}
|
|
579
846
|
|
|
@@ -627,10 +894,20 @@ func (r *runner) pruneDropped() error {
|
|
|
627
894
|
if !exists(dead) {
|
|
628
895
|
continue
|
|
629
896
|
}
|
|
897
|
+
action := "prune"
|
|
898
|
+
if managedConflict(r.prev[rel], r.preflight[rel]) != "" && r.opts.Force {
|
|
899
|
+
action = "prune(force-customized)"
|
|
900
|
+
}
|
|
630
901
|
if r.opts.DryRun {
|
|
631
|
-
fmt.Fprintf(r.opts.Stdout, " [
|
|
902
|
+
fmt.Fprintf(r.opts.Stdout, " [%s] %s (dropped from pack)\n", action, rel)
|
|
632
903
|
} else {
|
|
904
|
+
if err := r.recheckPath(rel); err != nil {
|
|
905
|
+
return err
|
|
906
|
+
}
|
|
633
907
|
if merge, ok := hostpack.ManagedMergeForMarker(rel); ok {
|
|
908
|
+
if err := r.recheckPath(merge.TargetRel); err != nil {
|
|
909
|
+
return err
|
|
910
|
+
}
|
|
634
911
|
if merge.TargetRel == hostpack.CodexHooksMerge.TargetRel || merge.TargetRel == hostpack.ClaudeSettingsMerge.TargetRel {
|
|
635
912
|
preserveEmpty := merge.TargetRel == hostpack.ClaudeSettingsMerge.TargetRel
|
|
636
913
|
_ = r.stripHooksPath(filepath.Join(r.target, filepath.FromSlash(merge.TargetRel)), preserveEmpty)
|
|
@@ -642,7 +919,7 @@ func (r *runner) pruneDropped() error {
|
|
|
642
919
|
return fmt.Errorf("remove %s: %w", rel, err)
|
|
643
920
|
}
|
|
644
921
|
pruneEmptyDirs(filepath.Dir(dead), r.target)
|
|
645
|
-
fmt.Fprintf(r.opts.Stdout, " [
|
|
922
|
+
fmt.Fprintf(r.opts.Stdout, " [%s] %s\n", action, rel)
|
|
646
923
|
}
|
|
647
924
|
r.stats.pruned++
|
|
648
925
|
}
|
|
@@ -666,6 +943,9 @@ func (r *runner) writeManifest() error {
|
|
|
666
943
|
for _, rel := range r.manifest {
|
|
667
944
|
b.WriteString(rel + "\n")
|
|
668
945
|
}
|
|
946
|
+
if err := r.recheckPath(ManifestName); err != nil {
|
|
947
|
+
return err
|
|
948
|
+
}
|
|
669
949
|
return fsutil.WriteFileAtomic(filepath.Join(r.target, ManifestName), []byte(b.String()), 0o644)
|
|
670
950
|
}
|
|
671
951
|
|
|
@@ -705,6 +985,9 @@ func (r *runner) uninstall() error {
|
|
|
705
985
|
fmt.Fprintln(r.opts.Stdout, " (dry run - no changes)")
|
|
706
986
|
}
|
|
707
987
|
fmt.Fprintln(r.opts.Stdout)
|
|
988
|
+
if err := r.preflightUninstall(entries); err != nil {
|
|
989
|
+
return err
|
|
990
|
+
}
|
|
708
991
|
|
|
709
992
|
for _, rel := range entries {
|
|
710
993
|
merge, ok := hostpack.ManagedMergeForMarker(rel)
|
|
@@ -715,6 +998,12 @@ func (r *runner) uninstall() error {
|
|
|
715
998
|
fmt.Fprintf(r.opts.Stdout, " [merge-remove] %s\n", merge.DryRun)
|
|
716
999
|
continue
|
|
717
1000
|
}
|
|
1001
|
+
if err := r.recheckPath(rel); err != nil {
|
|
1002
|
+
return err
|
|
1003
|
+
}
|
|
1004
|
+
if err := r.recheckPath(merge.TargetRel); err != nil {
|
|
1005
|
+
return err
|
|
1006
|
+
}
|
|
718
1007
|
if merge.TargetRel == hostpack.CodexHooksMerge.TargetRel || merge.TargetRel == hostpack.ClaudeSettingsMerge.TargetRel {
|
|
719
1008
|
preserveEmpty := merge.TargetRel == hostpack.ClaudeSettingsMerge.TargetRel
|
|
720
1009
|
if err := r.stripHooksPath(filepath.Join(r.target, filepath.FromSlash(merge.TargetRel)), preserveEmpty); err != nil {
|
|
@@ -734,10 +1023,19 @@ func (r *runner) uninstall() error {
|
|
|
734
1023
|
}
|
|
735
1024
|
dest := filepath.Join(r.target, filepath.FromSlash(rel))
|
|
736
1025
|
if exists(dest) {
|
|
1026
|
+
action := "remove"
|
|
1027
|
+
if managedConflict(r.prev[rel], r.preflight[rel]) != "" && r.opts.Force {
|
|
1028
|
+
action = "remove(force-customized)"
|
|
1029
|
+
}
|
|
737
1030
|
if r.opts.DryRun {
|
|
738
|
-
fmt.Fprintf(r.opts.Stdout, " [
|
|
739
|
-
} else
|
|
740
|
-
|
|
1031
|
+
fmt.Fprintf(r.opts.Stdout, " [%s] %s\n", action, rel)
|
|
1032
|
+
} else {
|
|
1033
|
+
if err := r.recheckPath(rel); err != nil {
|
|
1034
|
+
return err
|
|
1035
|
+
}
|
|
1036
|
+
if err := os.Remove(dest); err != nil && !os.IsNotExist(err) {
|
|
1037
|
+
return fmt.Errorf("remove %s: %w", rel, err)
|
|
1038
|
+
}
|
|
741
1039
|
}
|
|
742
1040
|
dirs = append(dirs, filepath.Dir(dest))
|
|
743
1041
|
r.stats.removed++
|
|
@@ -748,6 +1046,9 @@ func (r *runner) uninstall() error {
|
|
|
748
1046
|
if r.opts.DryRun {
|
|
749
1047
|
fmt.Fprintf(r.opts.Stdout, " [remove] %s\n", ManifestName)
|
|
750
1048
|
} else {
|
|
1049
|
+
if err := r.recheckPath(ManifestName); err != nil {
|
|
1050
|
+
return err
|
|
1051
|
+
}
|
|
751
1052
|
_ = os.Remove(mf)
|
|
752
1053
|
dirs = append(dirs, filepath.Dir(mf))
|
|
753
1054
|
for _, d := range dirs {
|
|
@@ -849,7 +1150,7 @@ func runUpdate(opts Options) error {
|
|
|
849
1150
|
next.Target = target
|
|
850
1151
|
next.PayloadDir = payload
|
|
851
1152
|
next.SourceDir = source
|
|
852
|
-
next.Force =
|
|
1153
|
+
next.Force = opts.Force
|
|
853
1154
|
if opts.DryRun {
|
|
854
1155
|
next.DryRun = true
|
|
855
1156
|
}
|
|
@@ -1302,10 +1603,7 @@ func (r *runner) installBinary() error {
|
|
|
1302
1603
|
fmt.Fprintln(r.opts.Stdout, " would install the global devrites-engine control-plane binary")
|
|
1303
1604
|
return nil
|
|
1304
1605
|
}
|
|
1305
|
-
tag :=
|
|
1306
|
-
if tag == "" {
|
|
1307
|
-
tag = "v" + strings.TrimPrefix(installedVersion(r.source), "v")
|
|
1308
|
-
}
|
|
1606
|
+
tag := r.binaryTag()
|
|
1309
1607
|
incoming := strings.TrimPrefix(tag, "v")
|
|
1310
1608
|
dest := binaryDest()
|
|
1311
1609
|
if exists(dest) {
|
|
@@ -1323,17 +1621,39 @@ func (r *runner) installBinary() error {
|
|
|
1323
1621
|
}
|
|
1324
1622
|
}
|
|
1325
1623
|
defer cleanup()
|
|
1624
|
+
if _, err := verifyEngineBinary(staged, tag, 30*time.Second); err != nil {
|
|
1625
|
+
return fmt.Errorf("verify staged engine binary %s: %w", staged, err)
|
|
1626
|
+
}
|
|
1326
1627
|
if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil {
|
|
1327
|
-
return
|
|
1628
|
+
return err
|
|
1328
1629
|
}
|
|
1329
1630
|
data, err := os.ReadFile(staged)
|
|
1330
1631
|
if err != nil {
|
|
1331
|
-
return
|
|
1632
|
+
return err
|
|
1633
|
+
}
|
|
1634
|
+
backup, oldMode, hadOld, err := backupBinary(dest)
|
|
1635
|
+
if err != nil {
|
|
1636
|
+
return err
|
|
1637
|
+
}
|
|
1638
|
+
if backup != "" {
|
|
1639
|
+
defer os.Remove(backup)
|
|
1332
1640
|
}
|
|
1333
1641
|
if err := fsutil.WriteFileAtomic(dest, data, 0o755); err != nil {
|
|
1334
|
-
|
|
1642
|
+
if restoreErr := restoreBinary(dest, backup, oldMode, hadOld); restoreErr != nil {
|
|
1643
|
+
return fmt.Errorf("install binary: %v; restore previous binary: %w", err, restoreErr)
|
|
1644
|
+
}
|
|
1645
|
+
return err
|
|
1335
1646
|
}
|
|
1336
1647
|
_ = os.Chmod(dest, 0o755)
|
|
1648
|
+
if _, err := verifyEngineBinary(dest, tag, 30*time.Second); err != nil {
|
|
1649
|
+
if restoreErr := restoreBinary(dest, backup, oldMode, hadOld); restoreErr != nil {
|
|
1650
|
+
return fmt.Errorf("verify installed engine binary: %v; restore previous binary: %w", err, restoreErr)
|
|
1651
|
+
}
|
|
1652
|
+
if hadOld {
|
|
1653
|
+
return fmt.Errorf("verify installed engine binary: %w (previous binary restored)", err)
|
|
1654
|
+
}
|
|
1655
|
+
return fmt.Errorf("verify installed engine binary: %w (bad binary removed)", err)
|
|
1656
|
+
}
|
|
1337
1657
|
fmt.Fprintf(r.opts.Stdout, " engine binary: installed %s\n", dest)
|
|
1338
1658
|
if !binaryReachableFromPATH(dest) {
|
|
1339
1659
|
fmt.Fprintf(r.opts.Stderr, "warning: engine binary: %s is not on PATH; installed hooks will fail open until devrites-engine is reachable.\n", dest)
|
|
@@ -1341,6 +1661,61 @@ func (r *runner) installBinary() error {
|
|
|
1341
1661
|
return nil
|
|
1342
1662
|
}
|
|
1343
1663
|
|
|
1664
|
+
func (r *runner) binaryTag() string {
|
|
1665
|
+
if r.releaseBinaryTag != "" {
|
|
1666
|
+
return r.releaseBinaryTag
|
|
1667
|
+
}
|
|
1668
|
+
if tag := os.Getenv("DEVRITES_REF"); tag != "" {
|
|
1669
|
+
return tag
|
|
1670
|
+
}
|
|
1671
|
+
return "v" + strings.TrimPrefix(installedVersion(r.source), "v")
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
func backupBinary(dest string) (string, fs.FileMode, bool, error) {
|
|
1675
|
+
info, err := os.Lstat(dest)
|
|
1676
|
+
if os.IsNotExist(err) {
|
|
1677
|
+
return "", 0, false, nil
|
|
1678
|
+
}
|
|
1679
|
+
if err != nil {
|
|
1680
|
+
return "", 0, false, fmt.Errorf("inspect existing engine binary: %w", err)
|
|
1681
|
+
}
|
|
1682
|
+
if !info.Mode().IsRegular() {
|
|
1683
|
+
return "", 0, false, fmt.Errorf("refusing to replace non-regular engine binary: %s", dest)
|
|
1684
|
+
}
|
|
1685
|
+
data, err := os.ReadFile(dest)
|
|
1686
|
+
if err != nil {
|
|
1687
|
+
return "", 0, false, fmt.Errorf("read existing engine binary: %w", err)
|
|
1688
|
+
}
|
|
1689
|
+
f, err := os.CreateTemp(filepath.Dir(dest), "."+filepath.Base(dest)+".backup-*")
|
|
1690
|
+
if err != nil {
|
|
1691
|
+
return "", 0, false, fmt.Errorf("create engine binary backup: %w", err)
|
|
1692
|
+
}
|
|
1693
|
+
backup := f.Name()
|
|
1694
|
+
if err := f.Close(); err != nil {
|
|
1695
|
+
_ = os.Remove(backup)
|
|
1696
|
+
return "", 0, false, fmt.Errorf("create engine binary backup: %w", err)
|
|
1697
|
+
}
|
|
1698
|
+
if err := fsutil.WriteFileAtomic(backup, data, info.Mode().Perm()); err != nil {
|
|
1699
|
+
_ = os.Remove(backup)
|
|
1700
|
+
return "", 0, false, fmt.Errorf("write engine binary backup: %w", err)
|
|
1701
|
+
}
|
|
1702
|
+
return backup, info.Mode().Perm(), true, nil
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
func restoreBinary(dest, backup string, mode fs.FileMode, hadOld bool) error {
|
|
1706
|
+
if !hadOld {
|
|
1707
|
+
if err := os.Remove(dest); err != nil && !os.IsNotExist(err) {
|
|
1708
|
+
return err
|
|
1709
|
+
}
|
|
1710
|
+
return nil
|
|
1711
|
+
}
|
|
1712
|
+
data, err := os.ReadFile(backup)
|
|
1713
|
+
if err != nil {
|
|
1714
|
+
return err
|
|
1715
|
+
}
|
|
1716
|
+
return fsutil.WriteFileAtomic(dest, data, mode)
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1344
1719
|
func (r *runner) binaryInstallFailure(err error) error {
|
|
1345
1720
|
if r.preparedBinary != "" {
|
|
1346
1721
|
return err
|
|
@@ -1364,7 +1739,7 @@ func (r *runner) acquireBinary(tag, baseURL string) (string, func(), error) {
|
|
|
1364
1739
|
return "", func() {}, fmt.Errorf("create temp dir: %w", err)
|
|
1365
1740
|
}
|
|
1366
1741
|
cleanup := func() { _ = os.RemoveAll(tmp) }
|
|
1367
|
-
staged := filepath.Join(tmp,
|
|
1742
|
+
staged := filepath.Join(tmp, engineBinaryName())
|
|
1368
1743
|
var releaseErr error
|
|
1369
1744
|
if r.releaseBinaryTag != "" && os.Getenv("DEVRITES_UPDATE_BUNDLE") == "" {
|
|
1370
1745
|
repo := os.Getenv("DEVRITES_REPO")
|
|
@@ -1416,6 +1791,9 @@ func downloadReleaseBinary(baseURL, repo, tag, dest string) (bool, error) {
|
|
|
1416
1791
|
if err := verifySHA256(dest, sumPath); err != nil {
|
|
1417
1792
|
return true, fmt.Errorf("could not verify release binary %s: %w", asset, err)
|
|
1418
1793
|
}
|
|
1794
|
+
if err := os.Chmod(dest, 0o755); err != nil && runtime.GOOS != "windows" {
|
|
1795
|
+
return true, fmt.Errorf("make release binary executable: %w", err)
|
|
1796
|
+
}
|
|
1419
1797
|
return true, nil
|
|
1420
1798
|
}
|
|
1421
1799
|
|
|
@@ -1442,24 +1820,31 @@ func (r *runner) removeBinary() error {
|
|
|
1442
1820
|
|
|
1443
1821
|
func binaryDest() string {
|
|
1444
1822
|
if dir := os.Getenv("DEVRITES_BIN_DIR"); dir != "" {
|
|
1445
|
-
return filepath.Join(dir,
|
|
1823
|
+
return filepath.Join(dir, engineBinaryName())
|
|
1446
1824
|
}
|
|
1447
1825
|
if home, err := os.UserHomeDir(); err == nil {
|
|
1448
|
-
return filepath.Join(home, ".local", "bin",
|
|
1826
|
+
return filepath.Join(home, ".local", "bin", engineBinaryName())
|
|
1449
1827
|
}
|
|
1450
|
-
return "/usr/local/bin
|
|
1828
|
+
return filepath.Join("/usr/local/bin", engineBinaryName())
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
func engineBinaryName() string {
|
|
1832
|
+
if runtime.GOOS == "windows" {
|
|
1833
|
+
return "devrites-engine.exe"
|
|
1834
|
+
}
|
|
1835
|
+
return "devrites-engine"
|
|
1451
1836
|
}
|
|
1452
1837
|
|
|
1453
1838
|
func binaryCandidates() []string {
|
|
1454
1839
|
candidates := []string{}
|
|
1455
1840
|
if dir := os.Getenv("DEVRITES_BIN_DIR"); dir != "" {
|
|
1456
|
-
candidates = append(candidates, filepath.Join(dir,
|
|
1841
|
+
candidates = append(candidates, filepath.Join(dir, engineBinaryName()))
|
|
1457
1842
|
}
|
|
1458
1843
|
home, _ := os.UserHomeDir()
|
|
1459
1844
|
if home != "" {
|
|
1460
|
-
candidates = append(candidates, filepath.Join(home, ".local", "bin",
|
|
1845
|
+
candidates = append(candidates, filepath.Join(home, ".local", "bin", engineBinaryName()))
|
|
1461
1846
|
}
|
|
1462
|
-
return append(candidates, "/usr/local/bin
|
|
1847
|
+
return append(candidates, filepath.Join("/usr/local/bin", engineBinaryName()))
|
|
1463
1848
|
}
|
|
1464
1849
|
|
|
1465
1850
|
func binaryReachableFromPATH(dest string) bool {
|
|
@@ -1493,31 +1878,81 @@ func pathContainsDir(dir string) bool {
|
|
|
1493
1878
|
}
|
|
1494
1879
|
|
|
1495
1880
|
func engineVersion(path string) string {
|
|
1496
|
-
|
|
1497
|
-
defer cancel()
|
|
1498
|
-
out, err := exec.CommandContext(ctx, path, "version").Output()
|
|
1881
|
+
got, err := readEngineVersion(path, 30*time.Second)
|
|
1499
1882
|
if err != nil {
|
|
1500
1883
|
return ""
|
|
1501
1884
|
}
|
|
1502
|
-
return
|
|
1885
|
+
return got
|
|
1503
1886
|
}
|
|
1504
1887
|
|
|
1505
|
-
func
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1888
|
+
func verifyEngineBinary(path, want string, timeout time.Duration) (string, error) {
|
|
1889
|
+
want = strings.TrimPrefix(strings.TrimSpace(want), "v")
|
|
1890
|
+
if want == "" || want == "dev" || !semverLike(want) {
|
|
1891
|
+
return "", fmt.Errorf("invalid requested version %q", want)
|
|
1509
1892
|
}
|
|
1510
|
-
|
|
1893
|
+
got, err := readEngineVersion(path, timeout)
|
|
1894
|
+
if err != nil {
|
|
1895
|
+
return "", err
|
|
1896
|
+
}
|
|
1897
|
+
if strings.TrimPrefix(got, "v") != want {
|
|
1898
|
+
return "", fmt.Errorf("version mismatch: got %s want %s", got, want)
|
|
1899
|
+
}
|
|
1900
|
+
return got, nil
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
func readEngineVersion(path string, timeout time.Duration) (string, error) {
|
|
1904
|
+
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
|
1905
|
+
defer cancel()
|
|
1906
|
+
out, err := exec.CommandContext(ctx, path, "version").Output()
|
|
1907
|
+
if err != nil {
|
|
1908
|
+
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
|
|
1909
|
+
return "", fmt.Errorf("version command timed out after %s", timeout)
|
|
1910
|
+
}
|
|
1911
|
+
return "", fmt.Errorf("run exact path %s version: %w", path, err)
|
|
1912
|
+
}
|
|
1913
|
+
line := strings.TrimSuffix(string(out), "\n")
|
|
1914
|
+
line = strings.TrimSuffix(line, "\r")
|
|
1915
|
+
if strings.ContainsAny(line, "\r\n") {
|
|
1916
|
+
return "", fmt.Errorf("invalid multi-line version output")
|
|
1917
|
+
}
|
|
1918
|
+
line = strings.TrimSpace(line)
|
|
1919
|
+
if line == "" || line == "dev" || !semverLike(line) {
|
|
1920
|
+
return "", fmt.Errorf("invalid version output %q", line)
|
|
1921
|
+
}
|
|
1922
|
+
return line, nil
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
func readManifest(path string) map[string]managedRecord {
|
|
1926
|
+
records, _ := parseManifest(path)
|
|
1927
|
+
return records
|
|
1511
1928
|
}
|
|
1512
1929
|
|
|
1513
1930
|
func readManifestList(path string) []string {
|
|
1931
|
+
_, entries := parseManifest(path)
|
|
1932
|
+
return entries
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
func parseManifest(path string) (map[string]managedRecord, []string) {
|
|
1514
1936
|
data, err := os.ReadFile(path)
|
|
1515
1937
|
if err != nil {
|
|
1516
|
-
return nil
|
|
1938
|
+
return map[string]managedRecord{}, nil
|
|
1517
1939
|
}
|
|
1940
|
+
hashes := map[string]string{}
|
|
1518
1941
|
var out []string
|
|
1519
1942
|
for _, line := range strings.Split(string(data), "\n") {
|
|
1520
1943
|
line = strings.TrimSpace(line)
|
|
1944
|
+
if strings.HasPrefix(line, "# managed: ") {
|
|
1945
|
+
record := strings.TrimPrefix(line, "# managed: ")
|
|
1946
|
+
i := strings.LastIndex(record, " sha256:")
|
|
1947
|
+
if i > 0 {
|
|
1948
|
+
rel := strings.TrimSpace(record[:i])
|
|
1949
|
+
hash := "sha256:" + strings.ToLower(strings.TrimSpace(record[i+len(" sha256:"):]))
|
|
1950
|
+
if filepath.IsLocal(filepath.FromSlash(rel)) && validManagedHash(hash) {
|
|
1951
|
+
hashes[rel] = hash
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
continue
|
|
1955
|
+
}
|
|
1521
1956
|
if line == "" || strings.HasPrefix(line, "#") {
|
|
1522
1957
|
continue
|
|
1523
1958
|
}
|
|
@@ -1528,7 +1963,20 @@ func readManifestList(path string) []string {
|
|
|
1528
1963
|
}
|
|
1529
1964
|
out = append(out, line)
|
|
1530
1965
|
}
|
|
1531
|
-
|
|
1966
|
+
records := make(map[string]managedRecord, len(out))
|
|
1967
|
+
for _, rel := range out {
|
|
1968
|
+
records[rel] = managedRecord{Hash: hashes[rel]}
|
|
1969
|
+
}
|
|
1970
|
+
return records, out
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
func validManagedHash(hash string) bool {
|
|
1974
|
+
const prefix = "sha256:"
|
|
1975
|
+
if !strings.HasPrefix(hash, prefix) || len(hash) != len(prefix)+sha256.Size*2 {
|
|
1976
|
+
return false
|
|
1977
|
+
}
|
|
1978
|
+
_, err := hex.DecodeString(strings.TrimPrefix(hash, prefix))
|
|
1979
|
+
return err == nil
|
|
1532
1980
|
}
|
|
1533
1981
|
|
|
1534
1982
|
func manifestHeader(path, key string) string {
|