devrites 7.2.8 → 7.3.1
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 +42 -0
- package/README.md +1 -1
- package/docs/candidate-integrity.md +10 -0
- package/docs/cli.md +7 -1
- package/docs/command-map.md +5 -2
- package/docs/engine/commands.md +34 -4
- package/docs/orchestration.md +26 -13
- package/docs/skills.md +5 -0
- package/engine/internal/acceptance/cli.go +27 -26
- package/engine/internal/acceptance/preconditions.go +1 -1
- package/engine/internal/acceptance/run.go +42 -16
- package/engine/internal/acceptance/transcript.go +236 -45
- package/engine/internal/devritespaths/paths.go +16 -3
- package/engine/internal/gate/gate.go +6 -0
- package/engine/internal/gate/readiness_binding.go +4 -3
- package/engine/internal/lib/candidate.go +20 -7
- package/engine/internal/lib/candidate_coverage.go +2 -19
- package/engine/internal/lib/closeout.go +16 -4
- package/engine/internal/lib/context.go +64 -45
- package/engine/internal/lib/context_io.go +171 -0
- package/engine/internal/lib/detect.go +8 -8
- package/engine/internal/lib/dispatch.go +2 -0
- package/engine/internal/lib/packet.go +37 -7
- package/engine/internal/lib/packet_freeze.go +60 -12
- package/engine/internal/lib/packet_integrity.go +179 -0
- package/engine/internal/lib/perturb.go +3 -3
- package/engine/internal/lib/slicecheck.go +7 -1
- package/engine/internal/lib/taskgraph.go +20 -36
- package/engine/internal/lib/triggers.go +28 -29
- package/engine/internal/lib/workspace_pool.go +330 -0
- package/engine/internal/parallel/cli.go +190 -49
- package/engine/internal/parallel/git.go +47 -14
- package/engine/internal/parallel/landing.go +232 -0
- package/engine/internal/parallel/lease.go +97 -18
- package/engine/internal/parallel/ops.go +312 -155
- package/engine/internal/parallel/pathdisjoint.go +48 -4
- package/engine/internal/parallel/prune.go +133 -0
- package/engine/internal/parallel/repository.go +239 -0
- package/engine/internal/parallel/select.go +64 -42
- package/engine/internal/state/observation.go +49 -2
- package/engine/main.go +2 -1
- package/pack/.claude/agents/devrites-proof-runner.md +16 -9
- package/pack/.claude/skills/devrites-lib/SKILL.md +2 -0
- package/pack/.claude/skills/devrites-lib/reference/candidate-integrity.md +7 -3
- package/pack/.claude/skills/devrites-lib/reference/parallel-dispatch.md +3 -0
- package/pack/.claude/skills/devrites-lib/reference/parallel-workspaces.md +236 -0
- package/pack/.claude/skills/devrites-lib/reference/standards/afk-hitl.md +9 -12
- package/pack/.claude/skills/devrites-lib/reference/standards/agents.md +1 -1
- package/pack/.claude/skills/devrites-lib/reference/standards/context-hygiene.md +1 -1
- package/pack/.claude/skills/devrites-lib/reference/standards/gates.md +18 -9
- package/pack/.claude/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -0
- package/pack/.claude/skills/rite-autocomplete/SKILL.md +12 -10
- package/pack/.claude/skills/rite-autocomplete/reference/lookahead.md +4 -4
- package/pack/.claude/skills/rite-autocomplete/reference/loop.md +20 -9
- package/pack/.claude/skills/rite-build/SKILL.md +13 -10
- package/pack/.claude/skills/rite-build/reference/afk-discipline.md +1 -1
- package/pack/.claude/skills/rite-build/reference/checkpoint.md +1 -1
- package/pack/.claude/skills/rite-build/reference/parallel-batch.md +57 -36
- package/pack/.claude/skills/rite-build/reference/phase-contract.md +8 -5
- package/pack/.claude/skills/rite-build/reference/wright-dispatch.md +7 -7
- package/pack/.claude/skills/rite-clarify/SKILL.md +2 -2
- package/pack/.claude/skills/rite-converge/SKILL.md +4 -4
- package/pack/.claude/skills/rite-define/SKILL.md +7 -3
- package/pack/.claude/skills/rite-define/reference/plan-template.md +4 -2
- package/pack/.claude/skills/rite-plan/SKILL.md +11 -8
- package/pack/.claude/skills/rite-plan/reference/dependency-graph.md +9 -2
- package/pack/.claude/skills/rite-plan/reference/slicing.md +2 -0
- package/pack/.claude/skills/rite-polish/SKILL.md +3 -3
- package/pack/.claude/skills/rite-prove/SKILL.md +24 -12
- package/pack/.claude/skills/rite-resolve/SKILL.md +3 -3
- package/pack/.claude/skills/rite-resolve/reference/answer-protocol.md +2 -0
- package/pack/.claude/skills/rite-review/SKILL.md +11 -3
- package/pack/.claude/skills/rite-review/reference/feature-scoped-review.md +2 -2
- package/pack/.claude/skills/rite-seal/SKILL.md +1 -1
- package/pack/.claude/skills/rite-seal/reference/phase-contract.md +1 -1
- package/pack/.claude/skills/rite-ship/SKILL.md +2 -2
- package/pack/.claude/skills/rite-ship/reference/git-ship.md +2 -2
- package/pack/.claude/skills/rite-spec/SKILL.md +8 -5
- package/pack/.claude/skills/rite-spec/reference/state-workspace.md +5 -0
- package/pack/.claude/skills/rite-temper/SKILL.md +3 -3
- package/pack/.claude/skills/rite-vet/SKILL.md +19 -6
- package/pack/.claude/skills/rite-vet/reference/review-axes.md +12 -5
- package/pack/generated/claude/agents/devrites-proof-runner.md +16 -9
- package/pack/generated/claude/skills/devrites-lib/SKILL.md +2 -0
- package/pack/generated/claude/skills/devrites-lib/reference/candidate-integrity.md +7 -3
- package/pack/generated/claude/skills/devrites-lib/reference/parallel-dispatch.md +3 -0
- package/pack/generated/claude/skills/devrites-lib/reference/parallel-workspaces.md +236 -0
- package/pack/generated/claude/skills/devrites-lib/reference/standards/afk-hitl.md +9 -12
- package/pack/generated/claude/skills/devrites-lib/reference/standards/agents.md +1 -1
- package/pack/generated/claude/skills/devrites-lib/reference/standards/context-hygiene.md +1 -1
- package/pack/generated/claude/skills/devrites-lib/reference/standards/gates.md +18 -9
- package/pack/generated/claude/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -0
- package/pack/generated/claude/skills/rite-autocomplete/SKILL.md +12 -10
- package/pack/generated/claude/skills/rite-autocomplete/reference/lookahead.md +4 -4
- package/pack/generated/claude/skills/rite-autocomplete/reference/loop.md +20 -9
- package/pack/generated/claude/skills/rite-build/SKILL.md +13 -10
- package/pack/generated/claude/skills/rite-build/reference/afk-discipline.md +1 -1
- package/pack/generated/claude/skills/rite-build/reference/checkpoint.md +1 -1
- package/pack/generated/claude/skills/rite-build/reference/parallel-batch.md +57 -36
- package/pack/generated/claude/skills/rite-build/reference/phase-contract.md +8 -5
- package/pack/generated/claude/skills/rite-build/reference/wright-dispatch.md +7 -7
- package/pack/generated/claude/skills/rite-clarify/SKILL.md +2 -2
- package/pack/generated/claude/skills/rite-converge/SKILL.md +4 -4
- package/pack/generated/claude/skills/rite-define/SKILL.md +7 -3
- package/pack/generated/claude/skills/rite-define/reference/plan-template.md +4 -2
- package/pack/generated/claude/skills/rite-plan/SKILL.md +11 -8
- package/pack/generated/claude/skills/rite-plan/reference/dependency-graph.md +9 -2
- package/pack/generated/claude/skills/rite-plan/reference/slicing.md +2 -0
- package/pack/generated/claude/skills/rite-polish/SKILL.md +3 -3
- package/pack/generated/claude/skills/rite-prove/SKILL.md +24 -12
- package/pack/generated/claude/skills/rite-resolve/SKILL.md +3 -3
- package/pack/generated/claude/skills/rite-resolve/reference/answer-protocol.md +2 -0
- package/pack/generated/claude/skills/rite-review/SKILL.md +11 -3
- package/pack/generated/claude/skills/rite-review/reference/feature-scoped-review.md +2 -2
- package/pack/generated/claude/skills/rite-seal/SKILL.md +1 -1
- package/pack/generated/claude/skills/rite-seal/reference/phase-contract.md +1 -1
- package/pack/generated/claude/skills/rite-ship/SKILL.md +2 -2
- package/pack/generated/claude/skills/rite-ship/reference/git-ship.md +2 -2
- package/pack/generated/claude/skills/rite-spec/SKILL.md +8 -5
- package/pack/generated/claude/skills/rite-spec/reference/state-workspace.md +5 -0
- package/pack/generated/claude/skills/rite-temper/SKILL.md +3 -3
- package/pack/generated/claude/skills/rite-vet/SKILL.md +19 -6
- package/pack/generated/claude/skills/rite-vet/reference/review-axes.md +12 -5
- package/pack/generated/codex/agents/devrites-proof-runner.toml +16 -9
- package/pack/generated/codex/skills/devrites-lib/SKILL.md +2 -0
- package/pack/generated/codex/skills/devrites-lib/reference/candidate-integrity.md +7 -3
- package/pack/generated/codex/skills/devrites-lib/reference/parallel-dispatch.md +3 -0
- package/pack/generated/codex/skills/devrites-lib/reference/parallel-workspaces.md +236 -0
- package/pack/generated/codex/skills/devrites-lib/reference/standards/afk-hitl.md +9 -12
- package/pack/generated/codex/skills/devrites-lib/reference/standards/agents.md +1 -1
- package/pack/generated/codex/skills/devrites-lib/reference/standards/context-hygiene.md +1 -1
- package/pack/generated/codex/skills/devrites-lib/reference/standards/gates.md +18 -9
- package/pack/generated/codex/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -0
- package/pack/generated/codex/skills/rite-autocomplete/SKILL.md +12 -10
- package/pack/generated/codex/skills/rite-autocomplete/reference/lookahead.md +4 -4
- package/pack/generated/codex/skills/rite-autocomplete/reference/loop.md +20 -9
- package/pack/generated/codex/skills/rite-build/SKILL.md +13 -10
- package/pack/generated/codex/skills/rite-build/reference/afk-discipline.md +1 -1
- package/pack/generated/codex/skills/rite-build/reference/checkpoint.md +1 -1
- package/pack/generated/codex/skills/rite-build/reference/parallel-batch.md +57 -36
- package/pack/generated/codex/skills/rite-build/reference/phase-contract.md +8 -5
- package/pack/generated/codex/skills/rite-build/reference/wright-dispatch.md +7 -7
- package/pack/generated/codex/skills/rite-clarify/SKILL.md +2 -2
- package/pack/generated/codex/skills/rite-converge/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-define/SKILL.md +7 -3
- package/pack/generated/codex/skills/rite-define/reference/plan-template.md +4 -2
- package/pack/generated/codex/skills/rite-plan/SKILL.md +11 -8
- package/pack/generated/codex/skills/rite-plan/reference/dependency-graph.md +9 -2
- package/pack/generated/codex/skills/rite-plan/reference/slicing.md +2 -0
- package/pack/generated/codex/skills/rite-polish/SKILL.md +3 -3
- package/pack/generated/codex/skills/rite-prove/SKILL.md +24 -12
- package/pack/generated/codex/skills/rite-resolve/SKILL.md +3 -3
- package/pack/generated/codex/skills/rite-resolve/reference/answer-protocol.md +2 -0
- package/pack/generated/codex/skills/rite-review/SKILL.md +11 -3
- package/pack/generated/codex/skills/rite-review/reference/feature-scoped-review.md +2 -2
- package/pack/generated/codex/skills/rite-seal/SKILL.md +1 -1
- package/pack/generated/codex/skills/rite-seal/reference/phase-contract.md +1 -1
- package/pack/generated/codex/skills/rite-ship/SKILL.md +2 -2
- package/pack/generated/codex/skills/rite-ship/reference/git-ship.md +2 -2
- package/pack/generated/codex/skills/rite-spec/SKILL.md +8 -5
- package/pack/generated/codex/skills/rite-spec/reference/state-workspace.md +5 -0
- package/pack/generated/codex/skills/rite-temper/SKILL.md +3 -3
- package/pack/generated/codex/skills/rite-vet/SKILL.md +19 -6
- package/pack/generated/codex/skills/rite-vet/reference/review-axes.md +12 -5
- package/pack/generated/devin/agents/devrites-proof-runner.md +16 -9
- package/pack/generated/devin/skills/devrites-lib/SKILL.md +2 -0
- package/pack/generated/devin/skills/devrites-lib/reference/candidate-integrity.md +7 -3
- package/pack/generated/devin/skills/devrites-lib/reference/parallel-dispatch.md +3 -0
- package/pack/generated/devin/skills/devrites-lib/reference/parallel-workspaces.md +236 -0
- package/pack/generated/devin/skills/devrites-lib/reference/standards/afk-hitl.md +9 -12
- package/pack/generated/devin/skills/devrites-lib/reference/standards/agents.md +1 -1
- package/pack/generated/devin/skills/devrites-lib/reference/standards/context-hygiene.md +1 -1
- package/pack/generated/devin/skills/devrites-lib/reference/standards/gates.md +18 -9
- package/pack/generated/devin/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -0
- package/pack/generated/devin/skills/rite-autocomplete/SKILL.md +12 -10
- package/pack/generated/devin/skills/rite-autocomplete/reference/lookahead.md +4 -4
- package/pack/generated/devin/skills/rite-autocomplete/reference/loop.md +20 -9
- package/pack/generated/devin/skills/rite-build/SKILL.md +13 -10
- package/pack/generated/devin/skills/rite-build/reference/afk-discipline.md +1 -1
- package/pack/generated/devin/skills/rite-build/reference/checkpoint.md +1 -1
- package/pack/generated/devin/skills/rite-build/reference/parallel-batch.md +57 -36
- package/pack/generated/devin/skills/rite-build/reference/phase-contract.md +8 -5
- package/pack/generated/devin/skills/rite-build/reference/wright-dispatch.md +7 -7
- package/pack/generated/devin/skills/rite-clarify/SKILL.md +2 -2
- package/pack/generated/devin/skills/rite-converge/SKILL.md +4 -4
- package/pack/generated/devin/skills/rite-define/SKILL.md +7 -3
- package/pack/generated/devin/skills/rite-define/reference/plan-template.md +4 -2
- package/pack/generated/devin/skills/rite-plan/SKILL.md +11 -8
- package/pack/generated/devin/skills/rite-plan/reference/dependency-graph.md +9 -2
- package/pack/generated/devin/skills/rite-plan/reference/slicing.md +2 -0
- package/pack/generated/devin/skills/rite-polish/SKILL.md +3 -3
- package/pack/generated/devin/skills/rite-prove/SKILL.md +24 -12
- package/pack/generated/devin/skills/rite-resolve/SKILL.md +3 -3
- package/pack/generated/devin/skills/rite-resolve/reference/answer-protocol.md +2 -0
- package/pack/generated/devin/skills/rite-review/SKILL.md +11 -3
- package/pack/generated/devin/skills/rite-review/reference/feature-scoped-review.md +2 -2
- package/pack/generated/devin/skills/rite-seal/SKILL.md +1 -1
- package/pack/generated/devin/skills/rite-seal/reference/phase-contract.md +1 -1
- package/pack/generated/devin/skills/rite-ship/SKILL.md +2 -2
- package/pack/generated/devin/skills/rite-ship/reference/git-ship.md +2 -2
- package/pack/generated/devin/skills/rite-spec/SKILL.md +8 -5
- package/pack/generated/devin/skills/rite-spec/reference/state-workspace.md +5 -0
- package/pack/generated/devin/skills/rite-temper/SKILL.md +3 -3
- package/pack/generated/devin/skills/rite-vet/SKILL.md +19 -6
- package/pack/generated/devin/skills/rite-vet/reference/review-axes.md +12 -5
- package/pack/generated/dsh/agents/devrites-proof-runner.md +16 -9
- package/pack/generated/dsh/runtime/cordis.patch.yml +1 -1
- package/pack/generated/dsh/skills/devrites-lib/SKILL.md +2 -0
- package/pack/generated/dsh/skills/devrites-lib/reference/candidate-integrity.md +7 -3
- package/pack/generated/dsh/skills/devrites-lib/reference/parallel-dispatch.md +3 -0
- package/pack/generated/dsh/skills/devrites-lib/reference/parallel-workspaces.md +236 -0
- package/pack/generated/dsh/skills/devrites-lib/reference/standards/afk-hitl.md +9 -12
- package/pack/generated/dsh/skills/devrites-lib/reference/standards/agents.md +1 -1
- package/pack/generated/dsh/skills/devrites-lib/reference/standards/context-hygiene.md +1 -1
- package/pack/generated/dsh/skills/devrites-lib/reference/standards/gates.md +18 -9
- package/pack/generated/dsh/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -0
- package/pack/generated/dsh/skills/rite-autocomplete/SKILL.md +12 -10
- package/pack/generated/dsh/skills/rite-autocomplete/reference/lookahead.md +4 -4
- package/pack/generated/dsh/skills/rite-autocomplete/reference/loop.md +20 -9
- package/pack/generated/dsh/skills/rite-build/SKILL.md +13 -10
- package/pack/generated/dsh/skills/rite-build/reference/afk-discipline.md +1 -1
- package/pack/generated/dsh/skills/rite-build/reference/checkpoint.md +1 -1
- package/pack/generated/dsh/skills/rite-build/reference/parallel-batch.md +57 -36
- package/pack/generated/dsh/skills/rite-build/reference/phase-contract.md +8 -5
- package/pack/generated/dsh/skills/rite-build/reference/wright-dispatch.md +7 -7
- package/pack/generated/dsh/skills/rite-clarify/SKILL.md +2 -2
- package/pack/generated/dsh/skills/rite-converge/SKILL.md +4 -4
- package/pack/generated/dsh/skills/rite-define/SKILL.md +7 -3
- package/pack/generated/dsh/skills/rite-define/reference/plan-template.md +4 -2
- package/pack/generated/dsh/skills/rite-plan/SKILL.md +11 -8
- package/pack/generated/dsh/skills/rite-plan/reference/dependency-graph.md +9 -2
- package/pack/generated/dsh/skills/rite-plan/reference/slicing.md +2 -0
- package/pack/generated/dsh/skills/rite-polish/SKILL.md +3 -3
- package/pack/generated/dsh/skills/rite-prove/SKILL.md +24 -12
- package/pack/generated/dsh/skills/rite-resolve/SKILL.md +3 -3
- package/pack/generated/dsh/skills/rite-resolve/reference/answer-protocol.md +2 -0
- package/pack/generated/dsh/skills/rite-review/SKILL.md +11 -3
- package/pack/generated/dsh/skills/rite-review/reference/feature-scoped-review.md +2 -2
- package/pack/generated/dsh/skills/rite-seal/SKILL.md +1 -1
- package/pack/generated/dsh/skills/rite-seal/reference/phase-contract.md +1 -1
- package/pack/generated/dsh/skills/rite-ship/SKILL.md +2 -2
- package/pack/generated/dsh/skills/rite-ship/reference/git-ship.md +2 -2
- package/pack/generated/dsh/skills/rite-spec/SKILL.md +8 -5
- package/pack/generated/dsh/skills/rite-spec/reference/state-workspace.md +5 -0
- package/pack/generated/dsh/skills/rite-temper/SKILL.md +3 -3
- package/pack/generated/dsh/skills/rite-vet/SKILL.md +19 -6
- package/pack/generated/dsh/skills/rite-vet/reference/review-axes.md +12 -5
- package/pack/generated/omp/agents/devrites-proof-runner.md +16 -9
- package/pack/generated/omp/commands/rite-build.md +1 -1
- package/pack/generated/omp/skills/devrites-lib/SKILL.md +2 -0
- package/pack/generated/omp/skills/devrites-lib/reference/candidate-integrity.md +7 -3
- package/pack/generated/omp/skills/devrites-lib/reference/parallel-dispatch.md +3 -0
- package/pack/generated/omp/skills/devrites-lib/reference/parallel-workspaces.md +236 -0
- package/pack/generated/omp/skills/devrites-lib/reference/standards/afk-hitl.md +9 -12
- package/pack/generated/omp/skills/devrites-lib/reference/standards/agents.md +1 -1
- package/pack/generated/omp/skills/devrites-lib/reference/standards/context-hygiene.md +1 -1
- package/pack/generated/omp/skills/devrites-lib/reference/standards/gates.md +18 -9
- package/pack/generated/omp/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -0
- package/pack/generated/omp/skills/rite-autocomplete/SKILL.md +12 -10
- package/pack/generated/omp/skills/rite-autocomplete/reference/lookahead.md +4 -4
- package/pack/generated/omp/skills/rite-autocomplete/reference/loop.md +20 -9
- package/pack/generated/omp/skills/rite-build/SKILL.md +13 -10
- package/pack/generated/omp/skills/rite-build/reference/afk-discipline.md +1 -1
- package/pack/generated/omp/skills/rite-build/reference/checkpoint.md +1 -1
- package/pack/generated/omp/skills/rite-build/reference/parallel-batch.md +57 -36
- package/pack/generated/omp/skills/rite-build/reference/phase-contract.md +8 -5
- package/pack/generated/omp/skills/rite-build/reference/wright-dispatch.md +7 -7
- package/pack/generated/omp/skills/rite-clarify/SKILL.md +2 -2
- package/pack/generated/omp/skills/rite-converge/SKILL.md +4 -4
- package/pack/generated/omp/skills/rite-define/SKILL.md +7 -3
- package/pack/generated/omp/skills/rite-define/reference/plan-template.md +4 -2
- package/pack/generated/omp/skills/rite-plan/SKILL.md +11 -8
- package/pack/generated/omp/skills/rite-plan/reference/dependency-graph.md +9 -2
- package/pack/generated/omp/skills/rite-plan/reference/slicing.md +2 -0
- package/pack/generated/omp/skills/rite-polish/SKILL.md +3 -3
- package/pack/generated/omp/skills/rite-prove/SKILL.md +24 -12
- package/pack/generated/omp/skills/rite-resolve/SKILL.md +3 -3
- package/pack/generated/omp/skills/rite-resolve/reference/answer-protocol.md +2 -0
- package/pack/generated/omp/skills/rite-review/SKILL.md +11 -3
- package/pack/generated/omp/skills/rite-review/reference/feature-scoped-review.md +2 -2
- package/pack/generated/omp/skills/rite-seal/SKILL.md +1 -1
- package/pack/generated/omp/skills/rite-seal/reference/phase-contract.md +1 -1
- package/pack/generated/omp/skills/rite-ship/SKILL.md +2 -2
- package/pack/generated/omp/skills/rite-ship/reference/git-ship.md +2 -2
- package/pack/generated/omp/skills/rite-spec/SKILL.md +8 -5
- package/pack/generated/omp/skills/rite-spec/reference/state-workspace.md +5 -0
- package/pack/generated/omp/skills/rite-temper/SKILL.md +3 -3
- package/pack/generated/omp/skills/rite-vet/SKILL.md +19 -6
- package/pack/generated/omp/skills/rite-vet/reference/review-axes.md +12 -5
- package/pack/generated/pi/agents/devrites-proof-runner.md +16 -9
- package/pack/generated/pi/prompts/rite-build.md +1 -1
- package/pack/generated/pi/skills/devrites-lib/SKILL.md +2 -0
- package/pack/generated/pi/skills/devrites-lib/reference/candidate-integrity.md +7 -3
- package/pack/generated/pi/skills/devrites-lib/reference/parallel-dispatch.md +3 -0
- package/pack/generated/pi/skills/devrites-lib/reference/parallel-workspaces.md +236 -0
- package/pack/generated/pi/skills/devrites-lib/reference/standards/afk-hitl.md +9 -12
- package/pack/generated/pi/skills/devrites-lib/reference/standards/agents.md +1 -1
- package/pack/generated/pi/skills/devrites-lib/reference/standards/context-hygiene.md +1 -1
- package/pack/generated/pi/skills/devrites-lib/reference/standards/gates.md +18 -9
- package/pack/generated/pi/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -0
- package/pack/generated/pi/skills/rite-autocomplete/SKILL.md +12 -10
- package/pack/generated/pi/skills/rite-autocomplete/reference/lookahead.md +4 -4
- package/pack/generated/pi/skills/rite-autocomplete/reference/loop.md +20 -9
- package/pack/generated/pi/skills/rite-build/SKILL.md +13 -10
- package/pack/generated/pi/skills/rite-build/reference/afk-discipline.md +1 -1
- package/pack/generated/pi/skills/rite-build/reference/checkpoint.md +1 -1
- package/pack/generated/pi/skills/rite-build/reference/parallel-batch.md +57 -36
- package/pack/generated/pi/skills/rite-build/reference/phase-contract.md +8 -5
- package/pack/generated/pi/skills/rite-build/reference/wright-dispatch.md +7 -7
- package/pack/generated/pi/skills/rite-clarify/SKILL.md +2 -2
- package/pack/generated/pi/skills/rite-converge/SKILL.md +4 -4
- package/pack/generated/pi/skills/rite-define/SKILL.md +7 -3
- package/pack/generated/pi/skills/rite-define/reference/plan-template.md +4 -2
- package/pack/generated/pi/skills/rite-plan/SKILL.md +11 -8
- package/pack/generated/pi/skills/rite-plan/reference/dependency-graph.md +9 -2
- package/pack/generated/pi/skills/rite-plan/reference/slicing.md +2 -0
- package/pack/generated/pi/skills/rite-polish/SKILL.md +3 -3
- package/pack/generated/pi/skills/rite-prove/SKILL.md +24 -12
- package/pack/generated/pi/skills/rite-resolve/SKILL.md +3 -3
- package/pack/generated/pi/skills/rite-resolve/reference/answer-protocol.md +2 -0
- package/pack/generated/pi/skills/rite-review/SKILL.md +11 -3
- package/pack/generated/pi/skills/rite-review/reference/feature-scoped-review.md +2 -2
- package/pack/generated/pi/skills/rite-seal/SKILL.md +1 -1
- package/pack/generated/pi/skills/rite-seal/reference/phase-contract.md +1 -1
- package/pack/generated/pi/skills/rite-ship/SKILL.md +2 -2
- package/pack/generated/pi/skills/rite-ship/reference/git-ship.md +2 -2
- package/pack/generated/pi/skills/rite-spec/SKILL.md +8 -5
- package/pack/generated/pi/skills/rite-spec/reference/state-workspace.md +5 -0
- package/pack/generated/pi/skills/rite-temper/SKILL.md +3 -3
- package/pack/generated/pi/skills/rite-vet/SKILL.md +19 -6
- package/pack/generated/pi/skills/rite-vet/reference/review-axes.md +12 -5
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@ import (
|
|
|
4
4
|
"bytes"
|
|
5
5
|
"crypto/sha256"
|
|
6
6
|
"encoding/json"
|
|
7
|
+
"errors"
|
|
7
8
|
"fmt"
|
|
8
9
|
"io"
|
|
9
10
|
"os"
|
|
@@ -93,12 +94,18 @@ func RunContext(root string, args []string, stdout, stderr io.Writer) int {
|
|
|
93
94
|
return 2
|
|
94
95
|
}
|
|
95
96
|
}
|
|
97
|
+
sources := contextSources{dir: featureDir}
|
|
96
98
|
// One active slice per bundle, except a parallel batch: the root has to
|
|
97
99
|
// see the other slices to start them. Whole tasks/plan/decisions ride
|
|
98
100
|
// that batch's turns; serial runs stay scoped.
|
|
99
101
|
autoScope := false
|
|
100
102
|
if featureDir != "" && opts.role == "" && opts.slice == "" && !opts.whole && (opts.phase == "build" || opts.phase == "autocomplete") && !parallelPlanVisible(root, opts.slug) {
|
|
101
|
-
|
|
103
|
+
id, err := activeSliceScope(&sources)
|
|
104
|
+
if err != nil {
|
|
105
|
+
fmt.Fprintf(stderr, "context: %v\n", err)
|
|
106
|
+
return 2
|
|
107
|
+
}
|
|
108
|
+
if id != "" {
|
|
102
109
|
opts.slices, opts.slice, autoScope = []string{id}, id, true
|
|
103
110
|
}
|
|
104
111
|
}
|
|
@@ -110,10 +117,21 @@ func RunContext(root string, args []string, stdout, stderr io.Writer) int {
|
|
|
110
117
|
var inventory []byte
|
|
111
118
|
recheckPath := opts.recheck
|
|
112
119
|
if recheckPath != "" {
|
|
113
|
-
if
|
|
120
|
+
if filepath.IsAbs(recheckPath) {
|
|
121
|
+
absoluteFeature, absErr := filepath.Abs(featureDir)
|
|
122
|
+
if absErr != nil {
|
|
123
|
+
fmt.Fprintf(stderr, "context: --recheck: %v\n", absErr)
|
|
124
|
+
return 2
|
|
125
|
+
}
|
|
126
|
+
if rel, relErr := filepath.Rel(absoluteFeature, recheckPath); relErr == nil && filepath.IsLocal(rel) {
|
|
127
|
+
inventory, err = sources.read(rel)
|
|
128
|
+
} else {
|
|
129
|
+
inventory, err = readBoundedRegularFile(recheckPath, workspaceArtifactLimit)
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
inventory, err = sources.read(recheckPath)
|
|
114
133
|
recheckPath = filepath.Join(featureDir, filepath.FromSlash(recheckPath))
|
|
115
134
|
}
|
|
116
|
-
inventory, err = os.ReadFile(recheckPath) // #nosec G304 -- the root names its own recheck inventory file
|
|
117
135
|
if err == nil && len(bytes.TrimSpace(inventory)) == 0 {
|
|
118
136
|
err = fmt.Errorf("%s is empty", opts.recheck)
|
|
119
137
|
}
|
|
@@ -141,7 +159,7 @@ func RunContext(root string, args []string, stdout, stderr io.Writer) int {
|
|
|
141
159
|
var sliceBlock string
|
|
142
160
|
var sliceIDs []citedID
|
|
143
161
|
if opts.slice != "" {
|
|
144
|
-
raw, err :=
|
|
162
|
+
raw, err := sources.read("tasks.md")
|
|
145
163
|
if err != nil {
|
|
146
164
|
fmt.Fprintf(stderr, "context: %v\n", err)
|
|
147
165
|
return 2
|
|
@@ -238,12 +256,17 @@ func RunContext(root string, args []string, stdout, stderr io.Writer) int {
|
|
|
238
256
|
}
|
|
239
257
|
// The open findings lead a recheck bundle, before any reference text.
|
|
240
258
|
if inventory != nil {
|
|
241
|
-
|
|
259
|
+
writeContextData(&bundle, recheckPath, "recheck inventory: "+opts.recheck, inventory, nil, seen, false)
|
|
242
260
|
loaded++
|
|
243
261
|
}
|
|
244
262
|
var rootOnly []string
|
|
245
263
|
citedSections, packByReference := 0, 0
|
|
246
|
-
|
|
264
|
+
facts, err := gatherTriggerFacts(root, opts.role, &sources)
|
|
265
|
+
if err != nil {
|
|
266
|
+
fmt.Fprintf(stderr, "context: %v\n", err)
|
|
267
|
+
return 2
|
|
268
|
+
}
|
|
269
|
+
keepModel := opts.role == "" || keepModelSurface(opts.role, facts)
|
|
247
270
|
for _, rel := range files {
|
|
248
271
|
abs := filepath.Join(skillsRoot, filepath.FromSlash(rel))
|
|
249
272
|
if seen[abs] {
|
|
@@ -297,16 +320,21 @@ func RunContext(root string, args []string, stdout, stderr io.Writer) int {
|
|
|
297
320
|
}
|
|
298
321
|
for _, rel := range workspaceList {
|
|
299
322
|
abs := filepath.Join(featureDir, filepath.FromSlash(rel))
|
|
323
|
+
data, err := sources.read(rel)
|
|
324
|
+
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
|
325
|
+
fmt.Fprintf(stderr, "context: %v\n", err)
|
|
326
|
+
return 2
|
|
327
|
+
}
|
|
300
328
|
// Role bundles without --slice (full Vet, Review, Seal) read
|
|
301
329
|
// everything; root and --slice bundles take over-budget
|
|
302
330
|
// artifacts by anchor.
|
|
303
331
|
if opts.slice == "" && opts.role != "" {
|
|
304
|
-
if
|
|
332
|
+
if writeContextData(&bundle, abs, "workspace/"+rel, data, err, seen, false) {
|
|
305
333
|
loaded++
|
|
306
334
|
}
|
|
307
335
|
continue
|
|
308
336
|
}
|
|
309
|
-
ok, byRef := writeSliceEntry(&bundle, filepath.Dir(root), abs, "workspace/"+rel, opts.slice, sliceBlock, sliceIDs, seen)
|
|
337
|
+
ok, byRef := writeSliceEntry(&bundle, filepath.Dir(root), abs, "workspace/"+rel, data, err, opts.slice, sliceBlock, sliceIDs, seen)
|
|
310
338
|
if ok {
|
|
311
339
|
loaded++
|
|
312
340
|
}
|
|
@@ -322,34 +350,11 @@ func RunContext(root string, args []string, stdout, stderr io.Writer) int {
|
|
|
322
350
|
withTOC := insertSectionTOC(bundle.String())
|
|
323
351
|
bundle.Reset()
|
|
324
352
|
bundle.WriteString(withTOC)
|
|
325
|
-
out := opts.
|
|
326
|
-
if
|
|
327
|
-
out = filepath.Join(featureDir, "ctx", bundleName(opts))
|
|
328
|
-
} else if !filepath.IsAbs(out) && featureDir != "" {
|
|
329
|
-
out = filepath.Join(featureDir, filepath.FromSlash(out))
|
|
330
|
-
}
|
|
331
|
-
if err := os.MkdirAll(filepath.Dir(out), 0o755); err != nil {
|
|
353
|
+
out, unchanged, err := publishContext(featureDir, opts, []byte(bundle.String()))
|
|
354
|
+
if err != nil {
|
|
332
355
|
fmt.Fprintf(stderr, "context: %v\n", err)
|
|
333
356
|
return 2
|
|
334
357
|
}
|
|
335
|
-
digestPath := out + ".sha256"
|
|
336
|
-
digest := fmt.Sprintf("%x", sha256.Sum256([]byte(bundle.String())))
|
|
337
|
-
unchanged := false
|
|
338
|
-
if prev, err := os.ReadFile(digestPath); err == nil && strings.TrimSpace(string(prev)) == digest { // #nosec G304 -- digestPath is generated under the feature workspace
|
|
339
|
-
if _, err := os.Stat(out); err == nil {
|
|
340
|
-
unchanged = true
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
if !unchanged {
|
|
344
|
-
if err := os.WriteFile(out, []byte(bundle.String()), 0o600); err != nil {
|
|
345
|
-
fmt.Fprintf(stderr, "context: %v\n", err)
|
|
346
|
-
return 2
|
|
347
|
-
}
|
|
348
|
-
if err := os.WriteFile(digestPath, []byte(digest+"\n"), 0o600); err != nil {
|
|
349
|
-
fmt.Fprintf(stderr, "context: %v\n", err)
|
|
350
|
-
return 2
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
358
|
if opts.slug != "" {
|
|
354
359
|
phaseLabel := opts.phase
|
|
355
360
|
if phaseLabel == "" {
|
|
@@ -396,7 +401,7 @@ func RunContext(root string, args []string, stdout, stderr io.Writer) int {
|
|
|
396
401
|
fmt.Fprintf(stdout, "triggers: auto=[%s] (fired by --role dispatch)\n",
|
|
397
402
|
strings.Join(autoTriggers, ","))
|
|
398
403
|
}
|
|
399
|
-
if suggested := suggestTriggers(manifest,
|
|
404
|
+
if suggested := suggestTriggers(manifest, opts, facts); len(suggested) > 0 {
|
|
400
405
|
fmt.Fprintf(stdout, "triggers: suggested=[%s] (advisory — confirm each before use)\n",
|
|
401
406
|
strings.Join(suggested, ","))
|
|
402
407
|
// The advisory list is only actionable if the agent is told how to
|
|
@@ -558,11 +563,10 @@ const sliceInlineMaxBytes = 16 << 10
|
|
|
558
563
|
// other artifact but spec.md over sliceInlineMaxBytes as a by-reference index;
|
|
559
564
|
// any canonical artifact over its line/byte budget as a by-reference index;
|
|
560
565
|
// everything else whole.
|
|
561
|
-
func writeSliceEntry(bundle *strings.Builder, project, abs, label, slice, block string, ids []citedID, seen map[string]bool) (loaded, byRef bool) {
|
|
566
|
+
func writeSliceEntry(bundle *strings.Builder, project, abs, label string, data []byte, err error, slice, block string, ids []citedID, seen map[string]bool) (loaded, byRef bool) {
|
|
562
567
|
if seen[abs] {
|
|
563
568
|
return false, false
|
|
564
569
|
}
|
|
565
|
-
data, err := os.ReadFile(abs) // #nosec G304 -- abs is resolved from the validated feature workspace
|
|
566
570
|
name := filepath.Base(abs)
|
|
567
571
|
if err == nil && name == "tasks.md" && slice != "" {
|
|
568
572
|
seen[abs] = true
|
|
@@ -570,7 +574,7 @@ func writeSliceEntry(bundle *strings.Builder, project, abs, label, slice, block
|
|
|
570
574
|
} else if err == nil && slice != "" && name != "spec.md" && len(data) > sliceInlineMaxBytes {
|
|
571
575
|
seen[abs] = true
|
|
572
576
|
} else if status, ok := state.ArtifactBudget(name, data); err != nil || !ok || !status.Over {
|
|
573
|
-
return
|
|
577
|
+
return writeContextData(bundle, abs, label, data, err, seen, false), false
|
|
574
578
|
} else {
|
|
575
579
|
seen[abs] = true
|
|
576
580
|
}
|
|
@@ -726,12 +730,19 @@ func clipRunes(s string, n int) string {
|
|
|
726
730
|
}
|
|
727
731
|
|
|
728
732
|
func writeContextEntry(bundle *strings.Builder, abs, label string, seen map[string]bool, expand bool) bool {
|
|
733
|
+
if seen[abs] {
|
|
734
|
+
return false
|
|
735
|
+
}
|
|
736
|
+
data, err := os.ReadFile(abs) // #nosec G304 -- abs is resolved from the validated context source set
|
|
737
|
+
return writeContextData(bundle, abs, label, data, err, seen, expand)
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
func writeContextData(bundle *strings.Builder, abs, label string, data []byte, err error, seen map[string]bool, expand bool) bool {
|
|
729
741
|
if seen[abs] {
|
|
730
742
|
return false
|
|
731
743
|
}
|
|
732
744
|
seen[abs] = true
|
|
733
745
|
fmt.Fprintf(bundle, "===== %s =====\n", label)
|
|
734
|
-
data, err := os.ReadFile(abs) // #nosec G304 -- abs is resolved from the validated context source set
|
|
735
746
|
if err != nil {
|
|
736
747
|
fmt.Fprintf(bundle, "MISSING: %s\n\n", label)
|
|
737
748
|
return false
|
|
@@ -926,20 +937,28 @@ func roleAgentPath(manifest loadsManifest, role string) string {
|
|
|
926
937
|
|
|
927
938
|
// activeSliceScope is state.md's active_slice when it names a slice block in
|
|
928
939
|
// tasks.md, else "" (unset, "none", or stale cursors keep the whole bundle).
|
|
929
|
-
func activeSliceScope(
|
|
930
|
-
|
|
940
|
+
func activeSliceScope(sources *contextSources) (string, error) {
|
|
941
|
+
raw, err := sources.read("state.md")
|
|
942
|
+
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
|
943
|
+
return "", err
|
|
944
|
+
}
|
|
945
|
+
lines := strings.Split(string(raw), "\n")
|
|
931
946
|
// active_slice outlives Build; after it the root's autocomplete bundle stays whole.
|
|
932
947
|
if phase, _ := state.CursorField(lines, state.CursorPhase); strings.TrimSpace(phase) != "build" {
|
|
933
|
-
return ""
|
|
948
|
+
return "", nil
|
|
934
949
|
}
|
|
935
950
|
id, _ := state.CursorField(lines, state.CursorActiveSlice)
|
|
936
951
|
if id = strings.TrimSpace(id); !state.ValidSliceID(id) {
|
|
937
|
-
return ""
|
|
952
|
+
return "", nil
|
|
953
|
+
}
|
|
954
|
+
tasks, err := sources.read("tasks.md")
|
|
955
|
+
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
|
956
|
+
return "", err
|
|
938
957
|
}
|
|
939
|
-
if _, ok := state.ExtractTaskSlice(
|
|
940
|
-
return ""
|
|
958
|
+
if _, ok := state.ExtractTaskSlice(tasks, id); !ok {
|
|
959
|
+
return "", nil
|
|
941
960
|
}
|
|
942
|
-
return id
|
|
961
|
+
return id, nil
|
|
943
962
|
}
|
|
944
963
|
|
|
945
964
|
func bundleName(opts contextOpts) string {
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
package lib
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"crypto/sha256"
|
|
6
|
+
"fmt"
|
|
7
|
+
"io"
|
|
8
|
+
"os"
|
|
9
|
+
"path/filepath"
|
|
10
|
+
"strings"
|
|
11
|
+
|
|
12
|
+
"github.com/devrites/devrites/internal/fsutil"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
type contextSource struct {
|
|
16
|
+
data []byte
|
|
17
|
+
err error
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// contextSources retains one invocation's bytes, including optional absence.
|
|
21
|
+
type contextSources struct {
|
|
22
|
+
dir string
|
|
23
|
+
files map[string]contextSource
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
func (s *contextSources) read(name string) ([]byte, error) {
|
|
27
|
+
if s.dir == "" {
|
|
28
|
+
return nil, os.ErrNotExist
|
|
29
|
+
}
|
|
30
|
+
name = filepath.Clean(filepath.FromSlash(name))
|
|
31
|
+
if !filepath.IsLocal(name) {
|
|
32
|
+
return nil, fmt.Errorf("workspace artifact %q escapes workspace", name)
|
|
33
|
+
}
|
|
34
|
+
if source, ok := s.files[name]; ok {
|
|
35
|
+
return source.data, source.err
|
|
36
|
+
}
|
|
37
|
+
var source contextSource
|
|
38
|
+
if source.err = contextParents(s.dir, name); source.err == nil {
|
|
39
|
+
source.data, source.err = readBoundedRegularFile(filepath.Join(s.dir, name), workspaceArtifactLimit)
|
|
40
|
+
}
|
|
41
|
+
if source.err != nil {
|
|
42
|
+
source.err = fmt.Errorf("workspace artifact %s: %w", name, source.err)
|
|
43
|
+
}
|
|
44
|
+
if s.files == nil {
|
|
45
|
+
s.files = make(map[string]contextSource)
|
|
46
|
+
}
|
|
47
|
+
s.files[name] = source
|
|
48
|
+
return source.data, source.err
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Reject even in-workspace parent links: managed paths name actual directories.
|
|
52
|
+
func contextParents(dir, rel string) error {
|
|
53
|
+
parent := filepath.Dir(rel)
|
|
54
|
+
if parent == "." {
|
|
55
|
+
return nil
|
|
56
|
+
}
|
|
57
|
+
for _, part := range strings.Split(parent, string(filepath.Separator)) {
|
|
58
|
+
dir = filepath.Join(dir, part)
|
|
59
|
+
info, err := os.Lstat(dir)
|
|
60
|
+
if os.IsNotExist(err) {
|
|
61
|
+
return nil
|
|
62
|
+
}
|
|
63
|
+
if err != nil {
|
|
64
|
+
return err
|
|
65
|
+
}
|
|
66
|
+
if !info.IsDir() {
|
|
67
|
+
return fmt.Errorf("context parent %s is not a directory (symlinks are not allowed)", dir)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return nil
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
func publishContext(featureDir string, opts contextOpts, body []byte) (out string, unchanged bool, err error) {
|
|
74
|
+
out = opts.out
|
|
75
|
+
managed := featureDir != "" && (out == "" || !filepath.IsAbs(out))
|
|
76
|
+
if out == "" {
|
|
77
|
+
out = filepath.Join("ctx", bundleName(opts))
|
|
78
|
+
}
|
|
79
|
+
base, rel := featureDir, out
|
|
80
|
+
if !managed && featureDir != "" {
|
|
81
|
+
absoluteFeature, absErr := filepath.Abs(featureDir)
|
|
82
|
+
if absErr != nil {
|
|
83
|
+
return out, false, absErr
|
|
84
|
+
}
|
|
85
|
+
if within, relErr := filepath.Rel(absoluteFeature, out); relErr == nil && filepath.IsLocal(within) {
|
|
86
|
+
managed, rel = true, within
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if managed {
|
|
90
|
+
if !filepath.IsLocal(rel) {
|
|
91
|
+
return out, false, fmt.Errorf("context output %q escapes workspace", out)
|
|
92
|
+
}
|
|
93
|
+
if err := contextParents(base, rel); err != nil {
|
|
94
|
+
return out, false, err
|
|
95
|
+
}
|
|
96
|
+
out = filepath.Join(base, rel)
|
|
97
|
+
} else {
|
|
98
|
+
base, rel = filepath.Dir(out), filepath.Base(out)
|
|
99
|
+
if err := os.MkdirAll(base, 0o755); err != nil {
|
|
100
|
+
return out, false, err
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
ws, err := os.OpenRoot(base)
|
|
104
|
+
if err != nil {
|
|
105
|
+
return out, false, err
|
|
106
|
+
}
|
|
107
|
+
defer func() { _ = ws.Close() }()
|
|
108
|
+
if err := ws.MkdirAll(filepath.Dir(rel), 0o755); err != nil {
|
|
109
|
+
return out, false, err
|
|
110
|
+
}
|
|
111
|
+
digest := []byte(fmt.Sprintf("%x\n", sha256.Sum256(body)))
|
|
112
|
+
// Check both destinations before publishing either; never follow a leaf link.
|
|
113
|
+
matches := true
|
|
114
|
+
for _, entry := range []struct {
|
|
115
|
+
name string
|
|
116
|
+
data []byte
|
|
117
|
+
}{{rel, body}, {rel + ".sha256", digest}} {
|
|
118
|
+
info, err := ws.Lstat(entry.name)
|
|
119
|
+
if os.IsNotExist(err) {
|
|
120
|
+
matches = false
|
|
121
|
+
continue
|
|
122
|
+
}
|
|
123
|
+
if err != nil {
|
|
124
|
+
return out, false, err
|
|
125
|
+
}
|
|
126
|
+
if !info.Mode().IsRegular() {
|
|
127
|
+
return out, false, fmt.Errorf("context output %s is not a regular file", entry.name)
|
|
128
|
+
}
|
|
129
|
+
// shortcut: Windows FileMode cannot prove POSIX 0600; rebuild until ACL-aware reuse is needed.
|
|
130
|
+
if info.Mode() != 0o600 || info.Size() != int64(len(entry.data)) {
|
|
131
|
+
matches = false
|
|
132
|
+
continue
|
|
133
|
+
}
|
|
134
|
+
// Open through the confined root; replacement still uses an atomic rename.
|
|
135
|
+
file, err := ws.Open(entry.name)
|
|
136
|
+
if err != nil {
|
|
137
|
+
return out, false, err
|
|
138
|
+
}
|
|
139
|
+
opened, err := file.Stat()
|
|
140
|
+
if err == nil && (opened.Mode() != 0o600 || !os.SameFile(info, opened)) {
|
|
141
|
+
err = fmt.Errorf("context output %s changed while opening", entry.name)
|
|
142
|
+
}
|
|
143
|
+
var data []byte
|
|
144
|
+
if err == nil {
|
|
145
|
+
data, err = io.ReadAll(io.LimitReader(file, int64(len(entry.data))+1))
|
|
146
|
+
}
|
|
147
|
+
if err == nil {
|
|
148
|
+
current, statErr := ws.Lstat(entry.name)
|
|
149
|
+
if statErr != nil {
|
|
150
|
+
err = statErr
|
|
151
|
+
} else if current.Mode() != 0o600 || !os.SameFile(opened, current) {
|
|
152
|
+
err = fmt.Errorf("context output %s changed while reading", entry.name)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
_ = file.Close()
|
|
156
|
+
if err != nil {
|
|
157
|
+
return out, false, err
|
|
158
|
+
}
|
|
159
|
+
matches = matches && bytes.Equal(data, entry.data)
|
|
160
|
+
}
|
|
161
|
+
if matches {
|
|
162
|
+
return out, true, nil
|
|
163
|
+
}
|
|
164
|
+
if err := fsutil.WriteFileAtomicIn(ws, rel, body, 0o600); err != nil {
|
|
165
|
+
return out, false, err
|
|
166
|
+
}
|
|
167
|
+
if err := fsutil.WriteFileAtomicIn(ws, rel+".sha256", digest, 0o600); err != nil {
|
|
168
|
+
return out, false, err
|
|
169
|
+
}
|
|
170
|
+
return out, false, nil
|
|
171
|
+
}
|
|
@@ -251,27 +251,27 @@ func manifestFallback(root string, slot string) (detectedCommand, bool) {
|
|
|
251
251
|
}
|
|
252
252
|
manifests := []manifest{
|
|
253
253
|
{"go.mod", "go.mod", map[string]string{
|
|
254
|
-
"unit": "go test ./...",
|
|
254
|
+
"unit": "go test ./...",
|
|
255
255
|
"lint": "golangci-lint run", "vet": "go vet ./...", "build": "go build ./...",
|
|
256
256
|
}},
|
|
257
257
|
{"Cargo.toml", "Cargo.toml", map[string]string{
|
|
258
|
-
"unit": "cargo test",
|
|
258
|
+
"unit": "cargo test",
|
|
259
259
|
"lint": "cargo clippy --all-targets", "vet": "cargo check", "build": "cargo build",
|
|
260
260
|
}},
|
|
261
261
|
{"pyproject.toml", "pyproject.toml", map[string]string{
|
|
262
|
-
"unit": "python -m pytest",
|
|
262
|
+
"unit": "python -m pytest",
|
|
263
263
|
"lint": "ruff check", "vet": "mypy .", "build": "python -m build",
|
|
264
264
|
}},
|
|
265
265
|
{"setup.py", "setup.py", map[string]string{
|
|
266
|
-
"unit": "python -m pytest",
|
|
266
|
+
"unit": "python -m pytest",
|
|
267
267
|
"lint": "flake8", "vet": "mypy .",
|
|
268
268
|
}},
|
|
269
269
|
{"pom.xml", "pom.xml", map[string]string{
|
|
270
|
-
"unit": "mvn test",
|
|
271
|
-
"vet":
|
|
270
|
+
"unit": "mvn test",
|
|
271
|
+
"vet": "mvn -q compile", "build": "mvn package",
|
|
272
272
|
}},
|
|
273
273
|
{"build.gradle", "build.gradle", map[string]string{
|
|
274
|
-
"unit": "gradle test",
|
|
274
|
+
"unit": "gradle test",
|
|
275
275
|
"lint": "gradle check", "build": "gradle build",
|
|
276
276
|
}},
|
|
277
277
|
{"composer.json", "composer.json", map[string]string{
|
|
@@ -279,7 +279,7 @@ func manifestFallback(root string, slot string) (detectedCommand, bool) {
|
|
|
279
279
|
"vet": "vendor/bin/phpstan analyse",
|
|
280
280
|
}},
|
|
281
281
|
{"Gemfile", "Gemfile", map[string]string{
|
|
282
|
-
"unit": "bundle exec rspec",
|
|
282
|
+
"unit": "bundle exec rspec",
|
|
283
283
|
"lint": "bundle exec rubocop",
|
|
284
284
|
}},
|
|
285
285
|
{"mix.exs", "mix.exs", map[string]string{
|
|
@@ -325,6 +325,8 @@ func dispatchLocked(root, slug, sub, phase, wave, role, handle, reason string, r
|
|
|
325
325
|
if err := lintReviewCandidate(root, slug, packet); err != nil {
|
|
326
326
|
return fail("BLOCKED: %v", err)
|
|
327
327
|
}
|
|
328
|
+
} else if err := lintPacketDiffs(root, slug, packet, false); err != nil {
|
|
329
|
+
return fail("BLOCKED: %v", err)
|
|
328
330
|
}
|
|
329
331
|
break
|
|
330
332
|
}
|
|
@@ -91,6 +91,21 @@ func RunPacket(root string, args []string, stdout, stderr io.Writer) int {
|
|
|
91
91
|
if err != nil {
|
|
92
92
|
return fail(err)
|
|
93
93
|
}
|
|
94
|
+
code := 0
|
|
95
|
+
if err := state.WithFeatureLock(root, slug, func() error {
|
|
96
|
+
code = renderPacketLocked(root, slug, workspace, wave, phase, role, slice, checks, focus, recheck, wright, doubt, stdout, stderr)
|
|
97
|
+
return nil
|
|
98
|
+
}); err != nil {
|
|
99
|
+
return fail(err)
|
|
100
|
+
}
|
|
101
|
+
return code
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
func renderPacketLocked(root, slug, workspace, wave, phase, role, slice, checks, focus, recheck string, wright, doubt bool, stdout, stderr io.Writer) int {
|
|
105
|
+
fail := func(err error) int {
|
|
106
|
+
fmt.Fprintf(stderr, "packet: %v\n", err)
|
|
107
|
+
return 3
|
|
108
|
+
}
|
|
94
109
|
rel := workspaceRel(root, workspace)
|
|
95
110
|
bundle := "ctx/" + bundleName(contextOpts{phase: phase, role: role, slice: slice, recheck: recheck})
|
|
96
111
|
if info, err := os.Stat(filepath.Join(workspace, filepath.FromSlash(bundle))); err != nil || !info.Mode().IsRegular() {
|
|
@@ -188,16 +203,24 @@ func RunPacket(root string, args []string, stdout, stderr io.Writer) int {
|
|
|
188
203
|
return fail(err)
|
|
189
204
|
}
|
|
190
205
|
// Lint a sibling draft so a refused re-render keeps the packet already on disk.
|
|
191
|
-
|
|
192
|
-
if err
|
|
206
|
+
draftFile, err := os.CreateTemp(filepath.Dir(out), "."+name+".draft-*")
|
|
207
|
+
if err != nil {
|
|
208
|
+
return fail(err)
|
|
209
|
+
}
|
|
210
|
+
draft := draftFile.Name()
|
|
211
|
+
defer func() { _ = os.Remove(draft) }()
|
|
212
|
+
_, writeErr := io.WriteString(draftFile, b.String())
|
|
213
|
+
if writeErr == nil {
|
|
214
|
+
writeErr = draftFile.Sync()
|
|
215
|
+
}
|
|
216
|
+
closeErr := draftFile.Close()
|
|
217
|
+
if err := errors.Join(writeErr, closeErr); err != nil {
|
|
193
218
|
return fail(err)
|
|
194
219
|
}
|
|
195
220
|
if err := lintRenderedPacket(root, slug, draft, role, wright, finalPhase); err != nil {
|
|
196
|
-
_ = os.Remove(draft)
|
|
197
221
|
return fail(fmt.Errorf("BLOCKED: %w", err))
|
|
198
222
|
}
|
|
199
223
|
if err := os.Rename(draft, out); err != nil {
|
|
200
|
-
_ = os.Remove(draft)
|
|
201
224
|
return fail(err)
|
|
202
225
|
}
|
|
203
226
|
fmt.Fprintf(stdout, "packet: %s/packets/%s (%s, %d bytes)\n", rel, name, role, b.Len())
|
|
@@ -216,7 +239,7 @@ func lintRenderedPacket(root, slug, path, role string, wright, finalPhase bool)
|
|
|
216
239
|
if finalPhase {
|
|
217
240
|
return lintReviewCandidate(root, slug, path)
|
|
218
241
|
}
|
|
219
|
-
return
|
|
242
|
+
return lintPacketDiffs(root, slug, path, false)
|
|
220
243
|
}
|
|
221
244
|
|
|
222
245
|
// frozenCandidate returns packets/<wave>.sha256 when candidate freeze wrote
|
|
@@ -231,8 +254,15 @@ func frozenCandidate(workspace, wave string) (sums string, partial bool, err err
|
|
|
231
254
|
if err != nil {
|
|
232
255
|
return "", false, fmt.Errorf("packets/%s.sha256: %w", wave, err)
|
|
233
256
|
}
|
|
234
|
-
|
|
235
|
-
|
|
257
|
+
bindings, err := frozenDiffBindings(raw)
|
|
258
|
+
if err != nil || len(bindings) != 1 || bindings[0].name != "packets/"+wave+".diff" {
|
|
259
|
+
return "", false, fmt.Errorf("packets/%s.sha256 must bind exactly packets/%s.diff with a diff SHA256 checksum", wave, wave)
|
|
260
|
+
}
|
|
261
|
+
if _, _, err := candidatePathInfo(workspace, "packets/"+wave+".diff"); err != nil {
|
|
262
|
+
return "", false, err
|
|
263
|
+
}
|
|
264
|
+
if err := verifyFrozenDiff(filepath.Join(workspace, "packets", wave+".diff"), bindings[0].digest); err != nil {
|
|
265
|
+
return "", false, fmt.Errorf("packets/%s.diff: %w", wave, err)
|
|
236
266
|
}
|
|
237
267
|
if len(raw) <= maxContractBytes/2 {
|
|
238
268
|
return string(raw), false, nil
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package lib
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
+
"bytes"
|
|
4
5
|
"crypto/sha256"
|
|
5
6
|
"errors"
|
|
6
7
|
"fmt"
|
|
@@ -80,6 +81,22 @@ func RunCandidateFreeze(root string, args []string, stdout, stderr io.Writer) in
|
|
|
80
81
|
if err != nil {
|
|
81
82
|
return fail(err)
|
|
82
83
|
}
|
|
84
|
+
code := 0
|
|
85
|
+
if err := state.WithFeatureLock(root, slug, func() error {
|
|
86
|
+
code = freezeCandidateLocked(root, slug, workspace, wave, slice, base, worktree, paths, stdout, stderr)
|
|
87
|
+
return nil
|
|
88
|
+
}); err != nil {
|
|
89
|
+
return fail(err)
|
|
90
|
+
}
|
|
91
|
+
return code
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
func freezeCandidateLocked(root, slug, workspace, wave, slice, base, worktree string, paths []string, stdout, stderr io.Writer) int {
|
|
95
|
+
fail := func(err error) int {
|
|
96
|
+
fmt.Fprintf(stderr, "candidate freeze: %v\n", err)
|
|
97
|
+
return 3
|
|
98
|
+
}
|
|
99
|
+
var err error
|
|
83
100
|
if slice != "" {
|
|
84
101
|
if paths, err = slicePaths(root, slug, slice); err != nil {
|
|
85
102
|
return fail(err)
|
|
@@ -98,13 +115,10 @@ func RunCandidateFreeze(root string, args []string, stdout, stderr io.Writer) in
|
|
|
98
115
|
if err := validateCandidateRows(project, rows); err != nil {
|
|
99
116
|
return fail(err)
|
|
100
117
|
}
|
|
101
|
-
|
|
118
|
+
digest, err := hashCandidate(project, rows, true)
|
|
102
119
|
if err != nil {
|
|
103
120
|
return fail(err)
|
|
104
121
|
}
|
|
105
|
-
if len(diff) == 0 {
|
|
106
|
-
return fail(errors.New("the path set has no change against the base; there is no candidate to freeze"))
|
|
107
|
-
}
|
|
108
122
|
head := ""
|
|
109
123
|
if worktree != "" {
|
|
110
124
|
// Review binds to the transfer commit, so the frozen tree must be exactly it.
|
|
@@ -115,24 +129,27 @@ func RunCandidateFreeze(root string, args []string, stdout, stderr io.Writer) in
|
|
|
115
129
|
if len(dirty) != 0 {
|
|
116
130
|
return fail(errors.New("the worktree has uncommitted changes on the path set; the frozen candidate must be its HEAD transfer commit"))
|
|
117
131
|
}
|
|
118
|
-
head =
|
|
132
|
+
head = commit
|
|
119
133
|
}
|
|
120
|
-
|
|
134
|
+
diff, baseCommit, err := freezeDiff(project, base, rows)
|
|
121
135
|
if err != nil {
|
|
122
136
|
return fail(err)
|
|
123
137
|
}
|
|
138
|
+
if len(diff) == 0 {
|
|
139
|
+
return fail(errors.New("the path set has no change against the base; there is no candidate to freeze"))
|
|
140
|
+
}
|
|
124
141
|
var sums strings.Builder
|
|
125
|
-
fmt.Fprintf(&sums, "%s %s\nbase %s\n
|
|
142
|
+
fmt.Fprintf(&sums, "%s %s\nbase %s\n", candidateDigestDomain, candidateDigestVersion, baseCommit)
|
|
143
|
+
if head != "" {
|
|
144
|
+
fmt.Fprintf(&sums, "head %s\n", head)
|
|
145
|
+
}
|
|
146
|
+
fmt.Fprintf(&sums, "diff %x packets/%s.diff\n", sha256.Sum256(diff), wave)
|
|
126
147
|
for _, row := range rows {
|
|
127
148
|
if row.state != "present" {
|
|
128
149
|
fmt.Fprintf(&sums, "file absent %s\n", row.path)
|
|
129
150
|
continue
|
|
130
151
|
}
|
|
131
|
-
file
|
|
132
|
-
if err := hashCandidateFile(file, project, row); err != nil {
|
|
133
|
-
return fail(fmt.Errorf("candidate %q: %w", row.path, err))
|
|
134
|
-
}
|
|
135
|
-
fmt.Fprintf(&sums, "file %x %s\n", file.Sum(nil), row.path)
|
|
152
|
+
fmt.Fprintf(&sums, "file %s %s\n", row.fileDigest, row.path)
|
|
136
153
|
}
|
|
137
154
|
fmt.Fprintf(&sums, "aggregate %s\n", digest)
|
|
138
155
|
|
|
@@ -143,6 +160,9 @@ func RunCandidateFreeze(root string, args []string, stdout, stderr io.Writer) in
|
|
|
143
160
|
} else if err != nil && !errors.Is(err, os.ErrNotExist) {
|
|
144
161
|
return fail(err)
|
|
145
162
|
}
|
|
163
|
+
if err := revalidateFrozenCandidate(project, baseCommit, rows, digest, diff, head); err != nil {
|
|
164
|
+
return fail(err)
|
|
165
|
+
}
|
|
146
166
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
147
167
|
return fail(err)
|
|
148
168
|
}
|
|
@@ -157,6 +177,34 @@ func RunCandidateFreeze(root string, args []string, stdout, stderr io.Writer) in
|
|
|
157
177
|
return 0
|
|
158
178
|
}
|
|
159
179
|
|
|
180
|
+
// Bracket Git's mutable worktree reads; refuse observed drift, not a filesystem snapshot guarantee.
|
|
181
|
+
func revalidateFrozenCandidate(project, base string, rows []candidateRow, digest string, diff []byte, head string) error {
|
|
182
|
+
currentDiff, commit, err := freezeDiff(project, base, rows)
|
|
183
|
+
if err != nil {
|
|
184
|
+
return err
|
|
185
|
+
}
|
|
186
|
+
if commit != base || !bytes.Equal(currentDiff, diff) {
|
|
187
|
+
return errors.New("candidate diff changed during freeze; retry under a new wave")
|
|
188
|
+
}
|
|
189
|
+
current, err := hashCandidate(project, rows, false)
|
|
190
|
+
if err != nil {
|
|
191
|
+
return fmt.Errorf("candidate changed during freeze: %w", err)
|
|
192
|
+
}
|
|
193
|
+
if current != digest {
|
|
194
|
+
return errors.New("candidate bytes changed during freeze; retry under a new wave")
|
|
195
|
+
}
|
|
196
|
+
if head != "" {
|
|
197
|
+
dirty, currentHead, err := freezeDiff(project, "HEAD", rows)
|
|
198
|
+
if err != nil {
|
|
199
|
+
return err
|
|
200
|
+
}
|
|
201
|
+
if currentHead != head || len(dirty) != 0 {
|
|
202
|
+
return errors.New("worktree HEAD or path set changed during freeze; candidate must remain its clean transfer commit")
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return nil
|
|
206
|
+
}
|
|
207
|
+
|
|
160
208
|
// slicePaths is the slice's tasks.md write set: Writer allowlist, else Files
|
|
161
209
|
// likely touched.
|
|
162
210
|
func slicePaths(root, slug, slice string) ([]string, error) {
|