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
|
@@ -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"
|
|
@@ -16,6 +17,7 @@ import (
|
|
|
16
17
|
"os/exec"
|
|
17
18
|
"path/filepath"
|
|
18
19
|
"reflect"
|
|
20
|
+
"runtime"
|
|
19
21
|
"slices"
|
|
20
22
|
"sort"
|
|
21
23
|
"strings"
|
|
@@ -25,11 +27,13 @@ import (
|
|
|
25
27
|
"github.com/devrites/devrites/internal/fsutil"
|
|
26
28
|
"github.com/devrites/devrites/internal/hostpack"
|
|
27
29
|
"github.com/devrites/devrites/internal/iohooks"
|
|
30
|
+
"github.com/devrites/devrites/internal/safepath"
|
|
28
31
|
"github.com/devrites/devrites/internal/version"
|
|
29
32
|
)
|
|
30
33
|
|
|
31
34
|
const ManifestName = devritespaths.ManifestName
|
|
32
35
|
const defaultRepo = "ViktorsBaikers/DevRites"
|
|
36
|
+
const githubBaseURL = "https://github.com"
|
|
33
37
|
|
|
34
38
|
type Mode string
|
|
35
39
|
|
|
@@ -69,15 +73,27 @@ type stats struct {
|
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
type runner struct {
|
|
72
|
-
opts
|
|
73
|
-
target
|
|
74
|
-
payload
|
|
75
|
-
payloadFS
|
|
76
|
-
source
|
|
77
|
-
manifest
|
|
78
|
-
records
|
|
79
|
-
prev
|
|
80
|
-
|
|
76
|
+
opts Options
|
|
77
|
+
target string
|
|
78
|
+
payload string
|
|
79
|
+
payloadFS fs.FS
|
|
80
|
+
source string
|
|
81
|
+
manifest []string
|
|
82
|
+
records map[string]string
|
|
83
|
+
prev map[string]managedRecord
|
|
84
|
+
preflight map[string]pathSnapshot
|
|
85
|
+
stats stats
|
|
86
|
+
releaseBinaryTag string
|
|
87
|
+
preparedBinary string
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
type managedRecord struct {
|
|
91
|
+
Hash string
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
type pathSnapshot struct {
|
|
95
|
+
missing bool
|
|
96
|
+
hash string
|
|
81
97
|
}
|
|
82
98
|
|
|
83
99
|
func DefaultOptions(mode Mode) Options {
|
|
@@ -209,7 +225,7 @@ func flagWasSet(flags *flag.FlagSet, name string) bool {
|
|
|
209
225
|
func usage(mode Mode) string {
|
|
210
226
|
switch mode {
|
|
211
227
|
case ModeUninstall:
|
|
212
|
-
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"
|
|
213
229
|
case ModeUpdate:
|
|
214
230
|
return "usage: devrites-engine update [--target DIR] [--dry-run] [--force] [--check] [--to TAG] [install flags]\n"
|
|
215
231
|
default:
|
|
@@ -273,7 +289,16 @@ func newRunner(opts Options) (*runner, error) {
|
|
|
273
289
|
if payload != "" {
|
|
274
290
|
payload, _ = filepath.Abs(payload)
|
|
275
291
|
}
|
|
276
|
-
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
|
+
}
|
|
277
302
|
if opts.Mode != ModeUninstall {
|
|
278
303
|
if err := r.validatePayload(); err != nil {
|
|
279
304
|
return nil, fmt.Errorf("validate payload: %w", err)
|
|
@@ -339,6 +364,14 @@ func (r *runner) install() error {
|
|
|
339
364
|
fmt.Fprintln(r.opts.Stdout, " (dry run - no changes will be made)")
|
|
340
365
|
}
|
|
341
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
|
+
}
|
|
342
375
|
|
|
343
376
|
for _, tree := range hostpack.InstallTrees(r.opts.WithSkills, r.opts.WithAgents, r.opts.WithCodex) {
|
|
344
377
|
if err := r.installTree(tree.PayloadPrefix, tree.TargetPrefix); err != nil {
|
|
@@ -420,12 +453,233 @@ func (r *runner) installFile(src, rel string) error {
|
|
|
420
453
|
return r.installData(data, rel)
|
|
421
454
|
}
|
|
422
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
|
+
|
|
423
674
|
func (r *runner) installData(data []byte, rel string) error {
|
|
424
675
|
dest := filepath.Join(r.target, filepath.FromSlash(rel))
|
|
425
676
|
action := "install"
|
|
426
677
|
if exists(dest) {
|
|
678
|
+
record, managed := r.prev[rel]
|
|
427
679
|
switch {
|
|
428
|
-
case r.
|
|
680
|
+
case managed && managedConflict(record, r.preflight[filepath.ToSlash(rel)]) != "" && r.opts.Force:
|
|
681
|
+
action = "overwrite(force-customized)"
|
|
682
|
+
case managed:
|
|
429
683
|
action = "overwrite"
|
|
430
684
|
case r.opts.Force:
|
|
431
685
|
action = "overwrite(force)"
|
|
@@ -441,8 +695,13 @@ func (r *runner) installData(data []byte, rel string) error {
|
|
|
441
695
|
}
|
|
442
696
|
if r.opts.DryRun {
|
|
443
697
|
fmt.Fprintf(r.opts.Stdout, " [%s] %s\n", action, rel)
|
|
444
|
-
} else
|
|
445
|
-
|
|
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
|
+
}
|
|
446
705
|
}
|
|
447
706
|
r.addManifest(rel)
|
|
448
707
|
r.addInstallRecord(rel, data)
|
|
@@ -488,6 +747,9 @@ func (r *runner) mergeMarkerFile(merge hostpack.MarkerMerge) error {
|
|
|
488
747
|
}
|
|
489
748
|
fmt.Fprintf(r.opts.Stdout, " [merge] %s (%s)\n", merge.TargetRel, verb)
|
|
490
749
|
} else {
|
|
750
|
+
if err := r.recheckPath(merge.TargetRel); err != nil {
|
|
751
|
+
return err
|
|
752
|
+
}
|
|
491
753
|
next := block
|
|
492
754
|
if current, err := os.ReadFile(dest); err == nil {
|
|
493
755
|
next = hostpack.MergeMarkerBlock(current, block, merge.Begin, merge.End)
|
|
@@ -509,6 +771,9 @@ func (r *runner) mergeJSONHooks(merge hostpack.JSONMerge) error {
|
|
|
509
771
|
fmt.Fprintf(r.opts.Stdout, " [merge] %s\n", merge.DryRunText)
|
|
510
772
|
return r.installMarker(merge.MarkerRel, merge.MarkerText)
|
|
511
773
|
}
|
|
774
|
+
if err := r.recheckPath(merge.TargetRel); err != nil {
|
|
775
|
+
return err
|
|
776
|
+
}
|
|
512
777
|
next := devrites
|
|
513
778
|
if current, err := readJSON(dest); err == nil {
|
|
514
779
|
if currentStatus, exists := current["statusLine"]; exists && !isDevritesHookCommand(currentStatus) {
|
|
@@ -542,7 +807,7 @@ func (r *runner) seedClaudeSettings() error {
|
|
|
542
807
|
if err != nil {
|
|
543
808
|
return fmt.Errorf("load Claude settings payload: %w", err)
|
|
544
809
|
}
|
|
545
|
-
managed := r.prev[merge.MarkerRel]
|
|
810
|
+
_, managed := r.prev[merge.MarkerRel]
|
|
546
811
|
if reflect.DeepEqual(current, devrites) {
|
|
547
812
|
if managed {
|
|
548
813
|
return r.installMarker(merge.MarkerRel, merge.MarkerText)
|
|
@@ -558,6 +823,9 @@ func (r *runner) seedClaudeSettings() error {
|
|
|
558
823
|
if err != nil {
|
|
559
824
|
return fmt.Errorf("read payload claude/settings.json: %w", err)
|
|
560
825
|
}
|
|
826
|
+
if err := r.recheckPath(rel); err != nil {
|
|
827
|
+
return err
|
|
828
|
+
}
|
|
561
829
|
return fsutil.WriteFileAtomic(dest, data, 0o644)
|
|
562
830
|
}
|
|
563
831
|
return r.mergeJSONHooks(merge)
|
|
@@ -570,6 +838,9 @@ func (r *runner) seedClaudeSettings() error {
|
|
|
570
838
|
if err != nil {
|
|
571
839
|
return fmt.Errorf("read payload claude/settings.json: %w", err)
|
|
572
840
|
}
|
|
841
|
+
if err := r.recheckPath(rel); err != nil {
|
|
842
|
+
return err
|
|
843
|
+
}
|
|
573
844
|
return fsutil.WriteFileAtomic(dest, data, 0o644)
|
|
574
845
|
}
|
|
575
846
|
|
|
@@ -623,10 +894,20 @@ func (r *runner) pruneDropped() error {
|
|
|
623
894
|
if !exists(dead) {
|
|
624
895
|
continue
|
|
625
896
|
}
|
|
897
|
+
action := "prune"
|
|
898
|
+
if managedConflict(r.prev[rel], r.preflight[rel]) != "" && r.opts.Force {
|
|
899
|
+
action = "prune(force-customized)"
|
|
900
|
+
}
|
|
626
901
|
if r.opts.DryRun {
|
|
627
|
-
fmt.Fprintf(r.opts.Stdout, " [
|
|
902
|
+
fmt.Fprintf(r.opts.Stdout, " [%s] %s (dropped from pack)\n", action, rel)
|
|
628
903
|
} else {
|
|
904
|
+
if err := r.recheckPath(rel); err != nil {
|
|
905
|
+
return err
|
|
906
|
+
}
|
|
629
907
|
if merge, ok := hostpack.ManagedMergeForMarker(rel); ok {
|
|
908
|
+
if err := r.recheckPath(merge.TargetRel); err != nil {
|
|
909
|
+
return err
|
|
910
|
+
}
|
|
630
911
|
if merge.TargetRel == hostpack.CodexHooksMerge.TargetRel || merge.TargetRel == hostpack.ClaudeSettingsMerge.TargetRel {
|
|
631
912
|
preserveEmpty := merge.TargetRel == hostpack.ClaudeSettingsMerge.TargetRel
|
|
632
913
|
_ = r.stripHooksPath(filepath.Join(r.target, filepath.FromSlash(merge.TargetRel)), preserveEmpty)
|
|
@@ -638,7 +919,7 @@ func (r *runner) pruneDropped() error {
|
|
|
638
919
|
return fmt.Errorf("remove %s: %w", rel, err)
|
|
639
920
|
}
|
|
640
921
|
pruneEmptyDirs(filepath.Dir(dead), r.target)
|
|
641
|
-
fmt.Fprintf(r.opts.Stdout, " [
|
|
922
|
+
fmt.Fprintf(r.opts.Stdout, " [%s] %s\n", action, rel)
|
|
642
923
|
}
|
|
643
924
|
r.stats.pruned++
|
|
644
925
|
}
|
|
@@ -662,6 +943,9 @@ func (r *runner) writeManifest() error {
|
|
|
662
943
|
for _, rel := range r.manifest {
|
|
663
944
|
b.WriteString(rel + "\n")
|
|
664
945
|
}
|
|
946
|
+
if err := r.recheckPath(ManifestName); err != nil {
|
|
947
|
+
return err
|
|
948
|
+
}
|
|
665
949
|
return fsutil.WriteFileAtomic(filepath.Join(r.target, ManifestName), []byte(b.String()), 0o644)
|
|
666
950
|
}
|
|
667
951
|
|
|
@@ -701,6 +985,9 @@ func (r *runner) uninstall() error {
|
|
|
701
985
|
fmt.Fprintln(r.opts.Stdout, " (dry run - no changes)")
|
|
702
986
|
}
|
|
703
987
|
fmt.Fprintln(r.opts.Stdout)
|
|
988
|
+
if err := r.preflightUninstall(entries); err != nil {
|
|
989
|
+
return err
|
|
990
|
+
}
|
|
704
991
|
|
|
705
992
|
for _, rel := range entries {
|
|
706
993
|
merge, ok := hostpack.ManagedMergeForMarker(rel)
|
|
@@ -711,6 +998,12 @@ func (r *runner) uninstall() error {
|
|
|
711
998
|
fmt.Fprintf(r.opts.Stdout, " [merge-remove] %s\n", merge.DryRun)
|
|
712
999
|
continue
|
|
713
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
|
+
}
|
|
714
1007
|
if merge.TargetRel == hostpack.CodexHooksMerge.TargetRel || merge.TargetRel == hostpack.ClaudeSettingsMerge.TargetRel {
|
|
715
1008
|
preserveEmpty := merge.TargetRel == hostpack.ClaudeSettingsMerge.TargetRel
|
|
716
1009
|
if err := r.stripHooksPath(filepath.Join(r.target, filepath.FromSlash(merge.TargetRel)), preserveEmpty); err != nil {
|
|
@@ -730,10 +1023,19 @@ func (r *runner) uninstall() error {
|
|
|
730
1023
|
}
|
|
731
1024
|
dest := filepath.Join(r.target, filepath.FromSlash(rel))
|
|
732
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
|
+
}
|
|
733
1030
|
if r.opts.DryRun {
|
|
734
|
-
fmt.Fprintf(r.opts.Stdout, " [
|
|
735
|
-
} else
|
|
736
|
-
|
|
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
|
+
}
|
|
737
1039
|
}
|
|
738
1040
|
dirs = append(dirs, filepath.Dir(dest))
|
|
739
1041
|
r.stats.removed++
|
|
@@ -744,6 +1046,9 @@ func (r *runner) uninstall() error {
|
|
|
744
1046
|
if r.opts.DryRun {
|
|
745
1047
|
fmt.Fprintf(r.opts.Stdout, " [remove] %s\n", ManifestName)
|
|
746
1048
|
} else {
|
|
1049
|
+
if err := r.recheckPath(ManifestName); err != nil {
|
|
1050
|
+
return err
|
|
1051
|
+
}
|
|
747
1052
|
_ = os.Remove(mf)
|
|
748
1053
|
dirs = append(dirs, filepath.Dir(mf))
|
|
749
1054
|
for _, d := range dirs {
|
|
@@ -845,11 +1150,24 @@ func runUpdate(opts Options) error {
|
|
|
845
1150
|
next.Target = target
|
|
846
1151
|
next.PayloadDir = payload
|
|
847
1152
|
next.SourceDir = source
|
|
848
|
-
next.Force =
|
|
1153
|
+
next.Force = opts.Force
|
|
849
1154
|
if opts.DryRun {
|
|
850
1155
|
next.DryRun = true
|
|
851
1156
|
}
|
|
852
|
-
|
|
1157
|
+
r, err := newRunner(next)
|
|
1158
|
+
if err != nil {
|
|
1159
|
+
return fmt.Errorf("prepare installer: %w", err)
|
|
1160
|
+
}
|
|
1161
|
+
r.releaseBinaryTag = latestTag
|
|
1162
|
+
if next.WithBinary && os.Getenv("DEVRITES_NO_BINARY") != "1" && !next.DryRun {
|
|
1163
|
+
staged, cleanup, err := r.acquireBinary(latestTag, githubBaseURL)
|
|
1164
|
+
if err != nil {
|
|
1165
|
+
return fmt.Errorf("prepare engine binary: %w", err)
|
|
1166
|
+
}
|
|
1167
|
+
defer cleanup()
|
|
1168
|
+
r.preparedBinary = staged
|
|
1169
|
+
}
|
|
1170
|
+
return r.install()
|
|
853
1171
|
}
|
|
854
1172
|
|
|
855
1173
|
func resolveUpdateTag(opts Options) (string, error) {
|
|
@@ -1285,10 +1603,7 @@ func (r *runner) installBinary() error {
|
|
|
1285
1603
|
fmt.Fprintln(r.opts.Stdout, " would install the global devrites-engine control-plane binary")
|
|
1286
1604
|
return nil
|
|
1287
1605
|
}
|
|
1288
|
-
tag :=
|
|
1289
|
-
if tag == "" {
|
|
1290
|
-
tag = "v" + strings.TrimPrefix(installedVersion(r.source), "v")
|
|
1291
|
-
}
|
|
1606
|
+
tag := r.binaryTag()
|
|
1292
1607
|
incoming := strings.TrimPrefix(tag, "v")
|
|
1293
1608
|
dest := binaryDest()
|
|
1294
1609
|
if exists(dest) {
|
|
@@ -1297,26 +1612,48 @@ func (r *runner) installBinary() error {
|
|
|
1297
1612
|
return nil
|
|
1298
1613
|
}
|
|
1299
1614
|
}
|
|
1300
|
-
staged, cleanup
|
|
1301
|
-
if
|
|
1302
|
-
|
|
1303
|
-
|
|
1615
|
+
staged, cleanup := r.preparedBinary, func() {}
|
|
1616
|
+
if staged == "" {
|
|
1617
|
+
var err error
|
|
1618
|
+
staged, cleanup, err = r.acquireBinary(tag, githubBaseURL)
|
|
1619
|
+
if err != nil {
|
|
1620
|
+
return r.binaryInstallFailure(err)
|
|
1621
|
+
}
|
|
1304
1622
|
}
|
|
1305
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
|
+
}
|
|
1306
1627
|
if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil {
|
|
1307
|
-
|
|
1308
|
-
return nil
|
|
1628
|
+
return err
|
|
1309
1629
|
}
|
|
1310
1630
|
data, err := os.ReadFile(staged)
|
|
1311
1631
|
if err != nil {
|
|
1312
|
-
|
|
1313
|
-
|
|
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)
|
|
1314
1640
|
}
|
|
1315
1641
|
if err := fsutil.WriteFileAtomic(dest, data, 0o755); err != nil {
|
|
1316
|
-
|
|
1317
|
-
|
|
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
|
|
1318
1646
|
}
|
|
1319
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
|
+
}
|
|
1320
1657
|
fmt.Fprintf(r.opts.Stdout, " engine binary: installed %s\n", dest)
|
|
1321
1658
|
if !binaryReachableFromPATH(dest) {
|
|
1322
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)
|
|
@@ -1324,32 +1661,142 @@ func (r *runner) installBinary() error {
|
|
|
1324
1661
|
return nil
|
|
1325
1662
|
}
|
|
1326
1663
|
|
|
1327
|
-
func (r *runner)
|
|
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
|
+
|
|
1719
|
+
func (r *runner) binaryInstallFailure(err error) error {
|
|
1720
|
+
if r.preparedBinary != "" {
|
|
1721
|
+
return err
|
|
1722
|
+
}
|
|
1723
|
+
fmt.Fprintf(r.opts.Stderr, "warning: engine binary: %v - hooks fail open until you install it.\n", err)
|
|
1724
|
+
return nil
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
func (r *runner) acquireBinary(tag, baseURL string) (string, func(), error) {
|
|
1328
1728
|
if v := os.Getenv("DEVRITES_ENGINE_CLI"); v != "" {
|
|
1329
1729
|
if exists(v) {
|
|
1330
|
-
|
|
1730
|
+
if r.releaseBinaryTag == "" || strings.TrimPrefix(engineVersion(v), "v") == strings.TrimPrefix(r.releaseBinaryTag, "v") {
|
|
1731
|
+
return v, func() {}, nil
|
|
1732
|
+
}
|
|
1733
|
+
} else {
|
|
1734
|
+
return "", func() {}, fmt.Errorf("DEVRITES_ENGINE_CLI points to a missing binary: %s", v)
|
|
1331
1735
|
}
|
|
1332
|
-
return "", func() {}, fmt.Errorf("DEVRITES_ENGINE_CLI points to a missing binary: %s", v)
|
|
1333
1736
|
}
|
|
1334
1737
|
tmp, err := os.MkdirTemp("", "devrites-engine-*")
|
|
1335
1738
|
if err != nil {
|
|
1336
1739
|
return "", func() {}, fmt.Errorf("create temp dir: %w", err)
|
|
1337
1740
|
}
|
|
1338
1741
|
cleanup := func() { _ = os.RemoveAll(tmp) }
|
|
1339
|
-
staged := filepath.Join(tmp,
|
|
1742
|
+
staged := filepath.Join(tmp, engineBinaryName())
|
|
1743
|
+
var releaseErr error
|
|
1744
|
+
if r.releaseBinaryTag != "" && os.Getenv("DEVRITES_UPDATE_BUNDLE") == "" {
|
|
1745
|
+
repo := os.Getenv("DEVRITES_REPO")
|
|
1746
|
+
if repo == "" {
|
|
1747
|
+
repo = defaultRepo
|
|
1748
|
+
}
|
|
1749
|
+
downloaded, err := downloadReleaseBinary(baseURL, repo, r.releaseBinaryTag, staged)
|
|
1750
|
+
if err == nil {
|
|
1751
|
+
return staged, cleanup, nil
|
|
1752
|
+
}
|
|
1753
|
+
if downloaded {
|
|
1754
|
+
return "", cleanup, err
|
|
1755
|
+
}
|
|
1756
|
+
releaseErr = err
|
|
1757
|
+
}
|
|
1340
1758
|
if r.source != "" && exists(filepath.Join(r.source, "engine", "go.mod")) {
|
|
1341
1759
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
|
|
1342
1760
|
defer cancel()
|
|
1343
|
-
cmd := exec.CommandContext(ctx, "go", "build", "-trimpath", "-ldflags", "-s -w -X github.com/devrites/devrites/internal/version.Version="+tag, "-o", staged, ".")
|
|
1761
|
+
cmd := exec.CommandContext(ctx, "go", "build", "-buildvcs=false", "-trimpath", "-ldflags", "-s -w -X github.com/devrites/devrites/internal/version.Version="+tag, "-o", staged, ".")
|
|
1344
1762
|
cmd.Dir = filepath.Join(r.source, "engine")
|
|
1345
1763
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
1764
|
+
if releaseErr != nil {
|
|
1765
|
+
return "", cleanup, fmt.Errorf("could not download release binary: %v; source fallback failed: %w: %s", releaseErr, err, strings.TrimSpace(string(out)))
|
|
1766
|
+
}
|
|
1346
1767
|
return "", cleanup, fmt.Errorf("could not build from source: %w: %s", err, strings.TrimSpace(string(out)))
|
|
1347
1768
|
}
|
|
1348
1769
|
return staged, cleanup, nil
|
|
1349
1770
|
}
|
|
1771
|
+
if releaseErr != nil {
|
|
1772
|
+
return "", cleanup, fmt.Errorf("could not obtain a binary: %v; no Go source fallback", releaseErr)
|
|
1773
|
+
}
|
|
1350
1774
|
return "", cleanup, fmt.Errorf("could not obtain a binary (no DEVRITES_ENGINE_CLI handoff, no Go source)")
|
|
1351
1775
|
}
|
|
1352
1776
|
|
|
1777
|
+
func downloadReleaseBinary(baseURL, repo, tag, dest string) (bool, error) {
|
|
1778
|
+
asset := fmt.Sprintf("devrites-%s-%s", runtime.GOOS, runtime.GOARCH)
|
|
1779
|
+
if runtime.GOOS == "windows" {
|
|
1780
|
+
asset += ".exe"
|
|
1781
|
+
}
|
|
1782
|
+
url := fmt.Sprintf("%s/%s/releases/download/%s/%s", strings.TrimRight(baseURL, "/"), repo, tag, asset)
|
|
1783
|
+
if err := iohooks.DownloadFile(url, dest); err != nil {
|
|
1784
|
+
return false, fmt.Errorf("download %s: %w", asset, err)
|
|
1785
|
+
}
|
|
1786
|
+
sumPath := dest + ".sha256"
|
|
1787
|
+
defer os.Remove(sumPath)
|
|
1788
|
+
if err := iohooks.DownloadFile(url+".sha256", sumPath); err != nil {
|
|
1789
|
+
return true, fmt.Errorf("could not verify release binary %s: missing checksum: %w", asset, err)
|
|
1790
|
+
}
|
|
1791
|
+
if err := verifySHA256(dest, sumPath); err != nil {
|
|
1792
|
+
return true, fmt.Errorf("could not verify release binary %s: %w", asset, err)
|
|
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
|
+
}
|
|
1797
|
+
return true, nil
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1353
1800
|
func (r *runner) removeBinary() error {
|
|
1354
1801
|
if r.opts.KeepBinary {
|
|
1355
1802
|
fmt.Fprintln(r.opts.Stdout, " kept the global devrites-engine binary (--keep-binary).")
|
|
@@ -1373,24 +1820,31 @@ func (r *runner) removeBinary() error {
|
|
|
1373
1820
|
|
|
1374
1821
|
func binaryDest() string {
|
|
1375
1822
|
if dir := os.Getenv("DEVRITES_BIN_DIR"); dir != "" {
|
|
1376
|
-
return filepath.Join(dir,
|
|
1823
|
+
return filepath.Join(dir, engineBinaryName())
|
|
1377
1824
|
}
|
|
1378
1825
|
if home, err := os.UserHomeDir(); err == nil {
|
|
1379
|
-
return filepath.Join(home, ".local", "bin",
|
|
1826
|
+
return filepath.Join(home, ".local", "bin", engineBinaryName())
|
|
1380
1827
|
}
|
|
1381
|
-
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"
|
|
1382
1836
|
}
|
|
1383
1837
|
|
|
1384
1838
|
func binaryCandidates() []string {
|
|
1385
1839
|
candidates := []string{}
|
|
1386
1840
|
if dir := os.Getenv("DEVRITES_BIN_DIR"); dir != "" {
|
|
1387
|
-
candidates = append(candidates, filepath.Join(dir,
|
|
1841
|
+
candidates = append(candidates, filepath.Join(dir, engineBinaryName()))
|
|
1388
1842
|
}
|
|
1389
1843
|
home, _ := os.UserHomeDir()
|
|
1390
1844
|
if home != "" {
|
|
1391
|
-
candidates = append(candidates, filepath.Join(home, ".local", "bin",
|
|
1845
|
+
candidates = append(candidates, filepath.Join(home, ".local", "bin", engineBinaryName()))
|
|
1392
1846
|
}
|
|
1393
|
-
return append(candidates, "/usr/local/bin
|
|
1847
|
+
return append(candidates, filepath.Join("/usr/local/bin", engineBinaryName()))
|
|
1394
1848
|
}
|
|
1395
1849
|
|
|
1396
1850
|
func binaryReachableFromPATH(dest string) bool {
|
|
@@ -1424,31 +1878,81 @@ func pathContainsDir(dir string) bool {
|
|
|
1424
1878
|
}
|
|
1425
1879
|
|
|
1426
1880
|
func engineVersion(path string) string {
|
|
1427
|
-
|
|
1428
|
-
defer cancel()
|
|
1429
|
-
out, err := exec.CommandContext(ctx, path, "version").Output()
|
|
1881
|
+
got, err := readEngineVersion(path, 30*time.Second)
|
|
1430
1882
|
if err != nil {
|
|
1431
1883
|
return ""
|
|
1432
1884
|
}
|
|
1433
|
-
return
|
|
1885
|
+
return got
|
|
1434
1886
|
}
|
|
1435
1887
|
|
|
1436
|
-
func
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
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)
|
|
1440
1892
|
}
|
|
1441
|
-
|
|
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
|
|
1442
1928
|
}
|
|
1443
1929
|
|
|
1444
1930
|
func readManifestList(path string) []string {
|
|
1931
|
+
_, entries := parseManifest(path)
|
|
1932
|
+
return entries
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
func parseManifest(path string) (map[string]managedRecord, []string) {
|
|
1445
1936
|
data, err := os.ReadFile(path)
|
|
1446
1937
|
if err != nil {
|
|
1447
|
-
return nil
|
|
1938
|
+
return map[string]managedRecord{}, nil
|
|
1448
1939
|
}
|
|
1940
|
+
hashes := map[string]string{}
|
|
1449
1941
|
var out []string
|
|
1450
1942
|
for _, line := range strings.Split(string(data), "\n") {
|
|
1451
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
|
+
}
|
|
1452
1956
|
if line == "" || strings.HasPrefix(line, "#") {
|
|
1453
1957
|
continue
|
|
1454
1958
|
}
|
|
@@ -1459,7 +1963,20 @@ func readManifestList(path string) []string {
|
|
|
1459
1963
|
}
|
|
1460
1964
|
out = append(out, line)
|
|
1461
1965
|
}
|
|
1462
|
-
|
|
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
|
|
1463
1980
|
}
|
|
1464
1981
|
|
|
1465
1982
|
func manifestHeader(path, key string) string {
|