devrites 6.1.1 → 6.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +1 -1
- package/docs/cli.md +4 -1
- package/docs/command-map.md +1 -1
- package/docs/engine/commands.md +4 -4
- package/docs/orchestration.md +4 -3
- package/docs/usage.md +1 -1
- package/engine/commands.go +1 -1
- package/engine/help.go +5 -1
- package/engine/internal/acceptance/cli.go +14 -5
- package/engine/internal/acceptance/run.go +20 -5
- package/engine/internal/lib/apply_bundle.go +474 -0
- package/engine/internal/lib/claims.go +12 -2
- package/engine/internal/lib/claims_holder.go +124 -0
- package/engine/internal/lib/cli_observe.go +14 -0
- package/engine/internal/lib/diffscope.go +2 -1
- package/engine/internal/lib/taskgraph.go +124 -0
- package/engine/internal/parallel/cli.go +111 -9
- package/engine/internal/parallel/lease.go +58 -4
- package/engine/internal/parallel/ops.go +244 -75
- package/engine/internal/parallel/select.go +31 -6
- package/engine/main.go +4 -0
- package/pack/.claude/agents/devrites-plan-drafter.md +13 -3
- package/pack/.claude/agents/devrites-slice-wright.md +5 -1
- package/pack/.claude/skills/devrites-lib/reference/parallel-dispatch.md +14 -5
- package/pack/.claude/skills/devrites-lib/reference/standards/afk-hitl.md +24 -9
- package/pack/.claude/skills/devrites-lib/reference/standards/agents.md +6 -2
- package/pack/.claude/skills/devrites-lib/reference/standards/core.md +3 -0
- package/pack/.claude/skills/devrites-lib/reference/standards/gates.md +3 -0
- package/pack/.claude/skills/devrites-lib/reference/standards/testing.md +7 -0
- package/pack/.claude/skills/devrites-lib/reference/standards/workflow-artifacts.md +10 -6
- package/pack/.claude/skills/devrites-lib/reference/workspace-artifact-schema.md +1 -1
- package/pack/.claude/skills/rite-autocomplete/SKILL.md +2 -2
- package/pack/.claude/skills/rite-autocomplete/reference/loop.md +1 -1
- package/pack/.claude/skills/rite-autocomplete/reference/stop-conditions.md +6 -2
- package/pack/.claude/skills/rite-build/SKILL.md +7 -7
- package/pack/.claude/skills/rite-build/reference/afk-discipline.md +2 -2
- package/pack/.claude/skills/rite-build/reference/checkpoint.md +1 -1
- package/pack/.claude/skills/rite-build/reference/one-slice-cycle.md +3 -2
- package/pack/.claude/skills/rite-build/reference/parallel-batch.md +28 -21
- package/pack/.claude/skills/rite-build/reference/phase-contract.md +10 -1
- package/pack/.claude/skills/rite-build/reference/spec-drift-guard.md +3 -2
- package/pack/.claude/skills/rite-build/reference/wright-dispatch.md +9 -2
- package/pack/.claude/skills/rite-define/SKILL.md +11 -2
- package/pack/.claude/skills/rite-handoff/SKILL.md +1 -1
- package/pack/.claude/skills/rite-plan/SKILL.md +20 -9
- package/pack/.claude/skills/rite-plan/reference/slicing.md +5 -0
- package/pack/.claude/skills/rite-polish/SKILL.md +6 -4
- package/pack/.claude/skills/rite-prove/SKILL.md +3 -2
- package/pack/.claude/skills/rite-review/SKILL.md +6 -1
- package/pack/.claude/skills/rite-seal/SKILL.md +2 -2
- package/pack/.claude/skills/rite-seal/reference/phase-contract.md +7 -1
- package/pack/.claude/skills/rite-vet/SKILL.md +9 -5
- package/pack/.claude/skills/rite-vet/reference/artifacts.md +8 -0
- package/pack/.claude/skills/rite-vet/reference/review-axes.md +1 -1
- package/pack/generated/claude/agents/devrites-plan-drafter.md +13 -3
- package/pack/generated/claude/agents/devrites-slice-wright.md +5 -1
- package/pack/generated/claude/skills/devrites-lib/reference/parallel-dispatch.md +14 -5
- package/pack/generated/claude/skills/devrites-lib/reference/standards/afk-hitl.md +24 -9
- package/pack/generated/claude/skills/devrites-lib/reference/standards/agents.md +6 -2
- package/pack/generated/claude/skills/devrites-lib/reference/standards/core.md +3 -0
- package/pack/generated/claude/skills/devrites-lib/reference/standards/gates.md +3 -0
- package/pack/generated/claude/skills/devrites-lib/reference/standards/testing.md +7 -0
- package/pack/generated/claude/skills/devrites-lib/reference/standards/workflow-artifacts.md +10 -6
- package/pack/generated/claude/skills/devrites-lib/reference/workspace-artifact-schema.md +1 -1
- package/pack/generated/claude/skills/rite-autocomplete/SKILL.md +2 -2
- package/pack/generated/claude/skills/rite-autocomplete/reference/loop.md +1 -1
- package/pack/generated/claude/skills/rite-autocomplete/reference/stop-conditions.md +6 -2
- package/pack/generated/claude/skills/rite-build/SKILL.md +7 -7
- package/pack/generated/claude/skills/rite-build/reference/afk-discipline.md +2 -2
- package/pack/generated/claude/skills/rite-build/reference/checkpoint.md +1 -1
- package/pack/generated/claude/skills/rite-build/reference/one-slice-cycle.md +3 -2
- package/pack/generated/claude/skills/rite-build/reference/parallel-batch.md +28 -21
- package/pack/generated/claude/skills/rite-build/reference/phase-contract.md +10 -1
- package/pack/generated/claude/skills/rite-build/reference/spec-drift-guard.md +3 -2
- package/pack/generated/claude/skills/rite-build/reference/wright-dispatch.md +9 -2
- package/pack/generated/claude/skills/rite-define/SKILL.md +11 -2
- package/pack/generated/claude/skills/rite-handoff/SKILL.md +1 -1
- package/pack/generated/claude/skills/rite-plan/SKILL.md +20 -9
- package/pack/generated/claude/skills/rite-plan/reference/slicing.md +5 -0
- package/pack/generated/claude/skills/rite-polish/SKILL.md +6 -4
- package/pack/generated/claude/skills/rite-prove/SKILL.md +3 -2
- package/pack/generated/claude/skills/rite-review/SKILL.md +6 -1
- package/pack/generated/claude/skills/rite-seal/SKILL.md +2 -2
- package/pack/generated/claude/skills/rite-seal/reference/phase-contract.md +7 -1
- package/pack/generated/claude/skills/rite-vet/SKILL.md +9 -5
- package/pack/generated/claude/skills/rite-vet/reference/artifacts.md +8 -0
- package/pack/generated/claude/skills/rite-vet/reference/review-axes.md +1 -1
- package/pack/generated/codex/agents/devrites-plan-drafter.toml +13 -3
- package/pack/generated/codex/agents/devrites-slice-wright.toml +5 -1
- package/pack/generated/codex/skills/devrites-lib/reference/parallel-dispatch.md +14 -5
- package/pack/generated/codex/skills/devrites-lib/reference/standards/afk-hitl.md +24 -9
- package/pack/generated/codex/skills/devrites-lib/reference/standards/agents.md +6 -2
- package/pack/generated/codex/skills/devrites-lib/reference/standards/core.md +3 -0
- package/pack/generated/codex/skills/devrites-lib/reference/standards/gates.md +3 -0
- package/pack/generated/codex/skills/devrites-lib/reference/standards/testing.md +7 -0
- package/pack/generated/codex/skills/devrites-lib/reference/standards/workflow-artifacts.md +10 -6
- package/pack/generated/codex/skills/devrites-lib/reference/workspace-artifact-schema.md +1 -1
- package/pack/generated/codex/skills/rite-autocomplete/SKILL.md +2 -2
- package/pack/generated/codex/skills/rite-autocomplete/reference/loop.md +1 -1
- package/pack/generated/codex/skills/rite-autocomplete/reference/stop-conditions.md +6 -2
- package/pack/generated/codex/skills/rite-build/SKILL.md +7 -7
- package/pack/generated/codex/skills/rite-build/reference/afk-discipline.md +2 -2
- package/pack/generated/codex/skills/rite-build/reference/checkpoint.md +1 -1
- package/pack/generated/codex/skills/rite-build/reference/one-slice-cycle.md +3 -2
- package/pack/generated/codex/skills/rite-build/reference/parallel-batch.md +28 -21
- package/pack/generated/codex/skills/rite-build/reference/phase-contract.md +10 -1
- package/pack/generated/codex/skills/rite-build/reference/spec-drift-guard.md +3 -2
- package/pack/generated/codex/skills/rite-build/reference/wright-dispatch.md +9 -2
- package/pack/generated/codex/skills/rite-define/SKILL.md +11 -2
- package/pack/generated/codex/skills/rite-handoff/SKILL.md +1 -1
- package/pack/generated/codex/skills/rite-plan/SKILL.md +20 -9
- package/pack/generated/codex/skills/rite-plan/reference/slicing.md +5 -0
- package/pack/generated/codex/skills/rite-polish/SKILL.md +6 -4
- package/pack/generated/codex/skills/rite-prove/SKILL.md +3 -2
- package/pack/generated/codex/skills/rite-review/SKILL.md +6 -1
- package/pack/generated/codex/skills/rite-seal/SKILL.md +2 -2
- package/pack/generated/codex/skills/rite-seal/reference/phase-contract.md +7 -1
- package/pack/generated/codex/skills/rite-vet/SKILL.md +9 -5
- package/pack/generated/codex/skills/rite-vet/reference/artifacts.md +8 -0
- package/pack/generated/codex/skills/rite-vet/reference/review-axes.md +1 -1
- package/pack/generated/devin/agents/devrites-plan-drafter.md +13 -3
- package/pack/generated/devin/agents/devrites-slice-wright.md +5 -1
- package/pack/generated/devin/skills/devrites-lib/reference/parallel-dispatch.md +14 -5
- package/pack/generated/devin/skills/devrites-lib/reference/standards/afk-hitl.md +24 -9
- package/pack/generated/devin/skills/devrites-lib/reference/standards/agents.md +6 -2
- package/pack/generated/devin/skills/devrites-lib/reference/standards/core.md +3 -0
- package/pack/generated/devin/skills/devrites-lib/reference/standards/gates.md +3 -0
- package/pack/generated/devin/skills/devrites-lib/reference/standards/testing.md +7 -0
- package/pack/generated/devin/skills/devrites-lib/reference/standards/workflow-artifacts.md +10 -6
- package/pack/generated/devin/skills/devrites-lib/reference/workspace-artifact-schema.md +1 -1
- package/pack/generated/devin/skills/rite-autocomplete/SKILL.md +2 -2
- package/pack/generated/devin/skills/rite-autocomplete/reference/loop.md +1 -1
- package/pack/generated/devin/skills/rite-autocomplete/reference/stop-conditions.md +6 -2
- package/pack/generated/devin/skills/rite-build/SKILL.md +7 -7
- package/pack/generated/devin/skills/rite-build/reference/afk-discipline.md +2 -2
- package/pack/generated/devin/skills/rite-build/reference/checkpoint.md +1 -1
- package/pack/generated/devin/skills/rite-build/reference/one-slice-cycle.md +3 -2
- package/pack/generated/devin/skills/rite-build/reference/parallel-batch.md +28 -21
- package/pack/generated/devin/skills/rite-build/reference/phase-contract.md +10 -1
- package/pack/generated/devin/skills/rite-build/reference/spec-drift-guard.md +3 -2
- package/pack/generated/devin/skills/rite-build/reference/wright-dispatch.md +9 -2
- package/pack/generated/devin/skills/rite-define/SKILL.md +11 -2
- package/pack/generated/devin/skills/rite-handoff/SKILL.md +1 -1
- package/pack/generated/devin/skills/rite-plan/SKILL.md +20 -9
- package/pack/generated/devin/skills/rite-plan/reference/slicing.md +5 -0
- package/pack/generated/devin/skills/rite-polish/SKILL.md +6 -4
- package/pack/generated/devin/skills/rite-prove/SKILL.md +3 -2
- package/pack/generated/devin/skills/rite-review/SKILL.md +6 -1
- package/pack/generated/devin/skills/rite-seal/SKILL.md +2 -2
- package/pack/generated/devin/skills/rite-seal/reference/phase-contract.md +7 -1
- package/pack/generated/devin/skills/rite-vet/SKILL.md +9 -5
- package/pack/generated/devin/skills/rite-vet/reference/artifacts.md +8 -0
- package/pack/generated/devin/skills/rite-vet/reference/review-axes.md +1 -1
- package/pack/generated/omp/agents/devrites-plan-drafter.md +13 -3
- package/pack/generated/omp/agents/devrites-slice-wright.md +5 -1
- package/pack/generated/omp/skills/devrites-lib/reference/parallel-dispatch.md +14 -5
- package/pack/generated/omp/skills/devrites-lib/reference/standards/afk-hitl.md +24 -9
- package/pack/generated/omp/skills/devrites-lib/reference/standards/agents.md +6 -2
- package/pack/generated/omp/skills/devrites-lib/reference/standards/core.md +3 -0
- package/pack/generated/omp/skills/devrites-lib/reference/standards/gates.md +3 -0
- package/pack/generated/omp/skills/devrites-lib/reference/standards/testing.md +7 -0
- package/pack/generated/omp/skills/devrites-lib/reference/standards/workflow-artifacts.md +10 -6
- package/pack/generated/omp/skills/devrites-lib/reference/workspace-artifact-schema.md +1 -1
- package/pack/generated/omp/skills/rite-autocomplete/SKILL.md +2 -2
- package/pack/generated/omp/skills/rite-autocomplete/reference/loop.md +1 -1
- package/pack/generated/omp/skills/rite-autocomplete/reference/stop-conditions.md +6 -2
- package/pack/generated/omp/skills/rite-build/SKILL.md +7 -7
- package/pack/generated/omp/skills/rite-build/reference/afk-discipline.md +2 -2
- package/pack/generated/omp/skills/rite-build/reference/checkpoint.md +1 -1
- package/pack/generated/omp/skills/rite-build/reference/one-slice-cycle.md +3 -2
- package/pack/generated/omp/skills/rite-build/reference/parallel-batch.md +28 -21
- package/pack/generated/omp/skills/rite-build/reference/phase-contract.md +10 -1
- package/pack/generated/omp/skills/rite-build/reference/spec-drift-guard.md +3 -2
- package/pack/generated/omp/skills/rite-build/reference/wright-dispatch.md +9 -2
- package/pack/generated/omp/skills/rite-define/SKILL.md +11 -2
- package/pack/generated/omp/skills/rite-handoff/SKILL.md +1 -1
- package/pack/generated/omp/skills/rite-plan/SKILL.md +20 -9
- package/pack/generated/omp/skills/rite-plan/reference/slicing.md +5 -0
- package/pack/generated/omp/skills/rite-polish/SKILL.md +6 -4
- package/pack/generated/omp/skills/rite-prove/SKILL.md +3 -2
- package/pack/generated/omp/skills/rite-review/SKILL.md +6 -1
- package/pack/generated/omp/skills/rite-seal/SKILL.md +2 -2
- package/pack/generated/omp/skills/rite-seal/reference/phase-contract.md +7 -1
- package/pack/generated/omp/skills/rite-vet/SKILL.md +9 -5
- package/pack/generated/omp/skills/rite-vet/reference/artifacts.md +8 -0
- package/pack/generated/omp/skills/rite-vet/reference/review-axes.md +1 -1
- package/pack/generated/pi/agents/devrites-plan-drafter.md +13 -3
- package/pack/generated/pi/agents/devrites-slice-wright.md +5 -1
- package/pack/generated/pi/skills/devrites-lib/reference/parallel-dispatch.md +14 -5
- package/pack/generated/pi/skills/devrites-lib/reference/standards/afk-hitl.md +24 -9
- package/pack/generated/pi/skills/devrites-lib/reference/standards/agents.md +6 -2
- package/pack/generated/pi/skills/devrites-lib/reference/standards/core.md +3 -0
- package/pack/generated/pi/skills/devrites-lib/reference/standards/gates.md +3 -0
- package/pack/generated/pi/skills/devrites-lib/reference/standards/testing.md +7 -0
- package/pack/generated/pi/skills/devrites-lib/reference/standards/workflow-artifacts.md +10 -6
- package/pack/generated/pi/skills/devrites-lib/reference/workspace-artifact-schema.md +1 -1
- package/pack/generated/pi/skills/rite-autocomplete/SKILL.md +2 -2
- package/pack/generated/pi/skills/rite-autocomplete/reference/loop.md +1 -1
- package/pack/generated/pi/skills/rite-autocomplete/reference/stop-conditions.md +6 -2
- package/pack/generated/pi/skills/rite-build/SKILL.md +7 -7
- package/pack/generated/pi/skills/rite-build/reference/afk-discipline.md +2 -2
- package/pack/generated/pi/skills/rite-build/reference/checkpoint.md +1 -1
- package/pack/generated/pi/skills/rite-build/reference/one-slice-cycle.md +3 -2
- package/pack/generated/pi/skills/rite-build/reference/parallel-batch.md +28 -21
- package/pack/generated/pi/skills/rite-build/reference/phase-contract.md +10 -1
- package/pack/generated/pi/skills/rite-build/reference/spec-drift-guard.md +3 -2
- package/pack/generated/pi/skills/rite-build/reference/wright-dispatch.md +9 -2
- package/pack/generated/pi/skills/rite-define/SKILL.md +11 -2
- package/pack/generated/pi/skills/rite-handoff/SKILL.md +1 -1
- package/pack/generated/pi/skills/rite-plan/SKILL.md +20 -9
- package/pack/generated/pi/skills/rite-plan/reference/slicing.md +5 -0
- package/pack/generated/pi/skills/rite-polish/SKILL.md +6 -4
- package/pack/generated/pi/skills/rite-prove/SKILL.md +3 -2
- package/pack/generated/pi/skills/rite-review/SKILL.md +6 -1
- package/pack/generated/pi/skills/rite-seal/SKILL.md +2 -2
- package/pack/generated/pi/skills/rite-seal/reference/phase-contract.md +7 -1
- package/pack/generated/pi/skills/rite-vet/SKILL.md +9 -5
- package/pack/generated/pi/skills/rite-vet/reference/artifacts.md +8 -0
- package/pack/generated/pi/skills/rite-vet/reference/review-axes.md +1 -1
- package/package.json +1 -1
|
@@ -2,8 +2,11 @@ package lib
|
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"fmt"
|
|
5
|
+
"path"
|
|
6
|
+
"sort"
|
|
5
7
|
"strings"
|
|
6
8
|
|
|
9
|
+
"github.com/devrites/devrites/internal/parallel"
|
|
7
10
|
"github.com/devrites/devrites/internal/state"
|
|
8
11
|
)
|
|
9
12
|
|
|
@@ -27,6 +30,127 @@ func CheckTaskGraph(root, slug string) (TaskGraphResult, error) {
|
|
|
27
30
|
return ParseTaskGraph(raw), nil
|
|
28
31
|
}
|
|
29
32
|
|
|
33
|
+
// hubAdvisoryMin is how many slices must write one path before it is flagged:
|
|
34
|
+
// slices sharing a write path never share a parallel build round.
|
|
35
|
+
const hubAdvisoryMin = 3
|
|
36
|
+
|
|
37
|
+
// hubAdvisoryShown caps printed hub lines; the metric still counts every hub.
|
|
38
|
+
const hubAdvisoryShown = 5
|
|
39
|
+
|
|
40
|
+
// sliceWritePaths returns each slice's write set (Writer allowlist, else Files
|
|
41
|
+
// likely touched) and whether it records a `Serial rationale:`.
|
|
42
|
+
func sliceWritePaths(tasksMarkdown []byte, graph TaskGraphResult) (map[string][]string, map[string]bool) {
|
|
43
|
+
paths := make(map[string][]string, len(graph.Slices))
|
|
44
|
+
serial := make(map[string]bool)
|
|
45
|
+
for _, s := range graph.Slices {
|
|
46
|
+
block, _ := state.ExtractTaskSlice(tasksMarkdown, s.ID)
|
|
47
|
+
fields := sliceFields(block)
|
|
48
|
+
allow, _ := fieldValue(fields, "writer allowlist")
|
|
49
|
+
paths[s.ID] = pathTokens(allow)
|
|
50
|
+
if len(paths[s.ID]) == 0 {
|
|
51
|
+
touched, _ := fieldValue(fields, "files likely touched")
|
|
52
|
+
paths[s.ID] = pathTokens(touched)
|
|
53
|
+
}
|
|
54
|
+
if v, _ := fieldValue(fields, "serial rationale"); v != "" {
|
|
55
|
+
serial[s.ID] = true
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return paths, serial
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// pathTokens keeps the list entries that can be paths; prose and Markdown links
|
|
62
|
+
// (anything with whitespace) are not write paths.
|
|
63
|
+
func pathTokens(raw string) []string {
|
|
64
|
+
var out []string
|
|
65
|
+
for _, p := range parsePathList(raw) {
|
|
66
|
+
if !strings.ContainsAny(p, " \t") {
|
|
67
|
+
out = append(out, p)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return out
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// scheduleRounds replays /rite-build batch selection at the maximum cap: each
|
|
74
|
+
// round runs the plan-order path-disjoint subset of dependency-ready slices.
|
|
75
|
+
// It returns 0 when a slice has no write paths or parallel select rejects them.
|
|
76
|
+
func scheduleRounds(graph TaskGraphResult, paths map[string][]string, root string) int {
|
|
77
|
+
done := make(map[string]bool, len(graph.Slices))
|
|
78
|
+
rounds := 0
|
|
79
|
+
for len(done) < len(graph.Slices) {
|
|
80
|
+
var ready []parallel.SlicePaths
|
|
81
|
+
for _, s := range graph.Slices {
|
|
82
|
+
if done[s.ID] || !allDone(s.Dependencies, done) {
|
|
83
|
+
continue
|
|
84
|
+
}
|
|
85
|
+
if len(paths[s.ID]) == 0 {
|
|
86
|
+
return 0
|
|
87
|
+
}
|
|
88
|
+
ready = append(ready, parallel.SlicePaths{ID: s.ID, Paths: paths[s.ID]})
|
|
89
|
+
}
|
|
90
|
+
if len(ready) == 0 {
|
|
91
|
+
return 0
|
|
92
|
+
}
|
|
93
|
+
picked, err := parallel.SelectGreedy(parallel.MaxParallelSlices, ready, root)
|
|
94
|
+
if err != nil || len(picked) == 0 {
|
|
95
|
+
return 0
|
|
96
|
+
}
|
|
97
|
+
for _, p := range picked {
|
|
98
|
+
done[p.ID] = true
|
|
99
|
+
}
|
|
100
|
+
rounds++
|
|
101
|
+
}
|
|
102
|
+
return rounds
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
func allDone(ids []string, done map[string]bool) bool {
|
|
106
|
+
for _, id := range ids {
|
|
107
|
+
if !done[id] {
|
|
108
|
+
return false
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return true
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// hubAdvisories names each path written by hubAdvisoryMin+ slices that carry
|
|
115
|
+
// no `Serial rationale:`, most-shared first.
|
|
116
|
+
func hubAdvisories(graph TaskGraphResult, paths map[string][]string, serial map[string]bool) []string {
|
|
117
|
+
writers := map[string][]string{}
|
|
118
|
+
shown := map[string]string{} // first spelling seen, for display
|
|
119
|
+
for _, s := range graph.Slices {
|
|
120
|
+
if serial[s.ID] {
|
|
121
|
+
continue
|
|
122
|
+
}
|
|
123
|
+
seen := map[string]bool{}
|
|
124
|
+
for _, p := range paths[s.ID] {
|
|
125
|
+
key := strings.ToLower(path.Clean(strings.ReplaceAll(p, `\`, "/")))
|
|
126
|
+
if !seen[key] {
|
|
127
|
+
seen[key] = true
|
|
128
|
+
writers[key] = append(writers[key], s.ID)
|
|
129
|
+
if _, ok := shown[key]; !ok {
|
|
130
|
+
shown[key] = p
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
hubs := make([]string, 0, len(writers))
|
|
136
|
+
for p, ids := range writers {
|
|
137
|
+
if len(ids) >= hubAdvisoryMin {
|
|
138
|
+
hubs = append(hubs, p)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
sort.Slice(hubs, func(i, j int) bool {
|
|
142
|
+
if len(writers[hubs[i]]) != len(writers[hubs[j]]) {
|
|
143
|
+
return len(writers[hubs[i]]) > len(writers[hubs[j]])
|
|
144
|
+
}
|
|
145
|
+
return hubs[i] < hubs[j]
|
|
146
|
+
})
|
|
147
|
+
lines := make([]string, len(hubs))
|
|
148
|
+
for i, p := range hubs {
|
|
149
|
+
lines[i] = fmt.Sprintf("ADVISORY hub: %s is written by %d slices (%s), so they build one at a time; move the shared edit into one seam slice they depend on, or record `Serial rationale:`", shown[p], len(writers[p]), strings.Join(writers[p], ", "))
|
|
150
|
+
}
|
|
151
|
+
return lines
|
|
152
|
+
}
|
|
153
|
+
|
|
30
154
|
// edgeAdvisories names each dependency edge that does not say what crosses it:
|
|
31
155
|
// no `(via <interface>)`, or a via the dependency's Consumes / Produces lacks.
|
|
32
156
|
// An edge the dependent reads nothing across only serializes work.
|
|
@@ -74,18 +74,23 @@ func runPathDisjoint(args []string, stdin io.Reader, stdout, stderr io.Writer) i
|
|
|
74
74
|
|
|
75
75
|
func cmdSelect(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
|
|
76
76
|
root := ""
|
|
77
|
+
slug := ""
|
|
77
78
|
jsonPath := "-"
|
|
78
79
|
capN := 0
|
|
79
80
|
sawCap := false
|
|
80
81
|
for i := 0; i < len(args); i++ {
|
|
81
82
|
switch args[i] {
|
|
82
|
-
case "--root":
|
|
83
|
+
case "--root", "--slug":
|
|
83
84
|
if i+1 >= len(args) {
|
|
84
85
|
fmt.Fprintln(stderr, usageSelect)
|
|
85
86
|
return ExitUsage
|
|
86
87
|
}
|
|
87
88
|
i++
|
|
88
|
-
|
|
89
|
+
if args[i-1] == "--root" {
|
|
90
|
+
root = args[i]
|
|
91
|
+
} else {
|
|
92
|
+
slug = args[i]
|
|
93
|
+
}
|
|
89
94
|
case "--cap":
|
|
90
95
|
if i+1 >= len(args) {
|
|
91
96
|
fmt.Fprintln(stderr, usageSelect)
|
|
@@ -124,7 +129,33 @@ func cmdSelect(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
|
|
|
124
129
|
fmt.Fprintf(stderr, "parallel select: %v\n", err)
|
|
125
130
|
return ExitBlocked
|
|
126
131
|
}
|
|
127
|
-
|
|
132
|
+
var seed []SlicePaths
|
|
133
|
+
running := -1
|
|
134
|
+
if slug != "" {
|
|
135
|
+
lease, err := runningLease(root, slug)
|
|
136
|
+
if err != nil {
|
|
137
|
+
fmt.Fprintf(stderr, "parallel select: %v\n", err)
|
|
138
|
+
return ExitBlocked
|
|
139
|
+
}
|
|
140
|
+
if lease != nil {
|
|
141
|
+
// The lease's active slices keep their slots and paths; nothing it
|
|
142
|
+
// already names (active or landed) is picked again.
|
|
143
|
+
seed = activeSlices(lease)
|
|
144
|
+
running = len(seed)
|
|
145
|
+
named := make(map[string]bool, len(lease.Slices))
|
|
146
|
+
for _, sl := range lease.Slices {
|
|
147
|
+
named[sl.ID] = true
|
|
148
|
+
}
|
|
149
|
+
var fresh []SlicePaths
|
|
150
|
+
for _, s := range slices {
|
|
151
|
+
if !named[s.ID] {
|
|
152
|
+
fresh = append(fresh, s)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
slices = fresh
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
selected, err := SelectGreedySeeded(capN, seed, slices, root)
|
|
128
159
|
if err != nil {
|
|
129
160
|
fmt.Fprintf(stderr, "parallel select: %v\n", err)
|
|
130
161
|
if strings.Contains(err.Error(), "cap must be") {
|
|
@@ -132,10 +163,30 @@ func cmdSelect(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
|
|
|
132
163
|
}
|
|
133
164
|
return ExitBlocked
|
|
134
165
|
}
|
|
135
|
-
fmt.Fprintln(stdout, formatSelect(selected))
|
|
166
|
+
fmt.Fprintln(stdout, formatSelect(selected, running))
|
|
136
167
|
return ExitOK
|
|
137
168
|
}
|
|
138
169
|
|
|
170
|
+
// runningLease returns slug's lease when it is still in flight
|
|
171
|
+
// (running|integrate-failed), or nil when there is none to seed from.
|
|
172
|
+
func runningLease(root, slug string) (*Lease, error) {
|
|
173
|
+
path, err := LeasePath(root, slug)
|
|
174
|
+
if err != nil {
|
|
175
|
+
return nil, err
|
|
176
|
+
}
|
|
177
|
+
lease, err := ReadLease(path)
|
|
178
|
+
if os.IsNotExist(err) {
|
|
179
|
+
return nil, nil
|
|
180
|
+
}
|
|
181
|
+
if err != nil {
|
|
182
|
+
return nil, err
|
|
183
|
+
}
|
|
184
|
+
if lease.Status != StatusRunning && lease.Status != StatusIntegrateFailed {
|
|
185
|
+
return nil, nil
|
|
186
|
+
}
|
|
187
|
+
return lease, nil
|
|
188
|
+
}
|
|
189
|
+
|
|
139
190
|
func runParallel(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
|
|
140
191
|
if len(args) == 0 {
|
|
141
192
|
fmt.Fprintln(stderr, parallelUsage())
|
|
@@ -145,6 +196,8 @@ func runParallel(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
|
|
|
145
196
|
switch sub {
|
|
146
197
|
case "create":
|
|
147
198
|
return cmdCreate(rest, stdin, stdout, stderr)
|
|
199
|
+
case "add":
|
|
200
|
+
return cmdAdd(rest, stdin, stdout, stderr)
|
|
148
201
|
case "record-green":
|
|
149
202
|
return cmdRecordGreen(rest, stdout, stderr)
|
|
150
203
|
case "abort":
|
|
@@ -174,16 +227,17 @@ func runParallel(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
|
|
|
174
227
|
|
|
175
228
|
const (
|
|
176
229
|
usageCreate = "usage: parallel create --root --slug --batch --base --json"
|
|
230
|
+
usageAdd = "usage: parallel add --root --slug --json <json-file>|-"
|
|
177
231
|
usageRecordGreen = "usage: parallel record-green --root --slug --slice --commit"
|
|
178
232
|
usageAbort = "usage: parallel abort --root --slug"
|
|
179
|
-
usageIntegrate = "usage: parallel integrate --root --slug [--apply-to-control]"
|
|
233
|
+
usageIntegrate = "usage: parallel integrate --root --slug [--apply-to-control [--slice <id>]]"
|
|
180
234
|
usageCleanup = "usage: parallel cleanup --root --slug [--force]"
|
|
181
235
|
usageStatus = "usage: parallel status --root --slug"
|
|
182
236
|
usageLeaseWrite = "usage: parallel lease-write --root --slug --json"
|
|
183
237
|
usageLeaseRead = "usage: parallel lease-read --root --slug [--field name]"
|
|
184
238
|
usageLeaseClear = "usage: parallel lease-clear --root --slug"
|
|
185
239
|
usagePathDisjoint = "usage: check path-disjoint [--root <dir>] [<json-file>|-]"
|
|
186
|
-
usageSelect = "usage: parallel select --cap <1-10> [--root <dir>] [<json-file>|-]"
|
|
240
|
+
usageSelect = "usage: parallel select --cap <1-10> [--root <dir>] [--slug <slug>] [<json-file>|-]"
|
|
187
241
|
)
|
|
188
242
|
|
|
189
243
|
func parallelUsage() string {
|
|
@@ -191,15 +245,16 @@ func parallelUsage() string {
|
|
|
191
245
|
|
|
192
246
|
Subcommands:
|
|
193
247
|
create --root --slug --batch --base --json
|
|
248
|
+
add --root --slug --json
|
|
194
249
|
record-green --root --slug --slice --commit
|
|
195
250
|
abort --root --slug
|
|
196
|
-
integrate --root --slug [--apply-to-control]
|
|
251
|
+
integrate --root --slug [--apply-to-control [--slice <id>]]
|
|
197
252
|
cleanup --root --slug [--force]
|
|
198
253
|
status --root --slug
|
|
199
254
|
lease-write --root --slug --json
|
|
200
255
|
lease-read --root --slug [--field name]
|
|
201
256
|
lease-clear --root --slug
|
|
202
|
-
select --cap <1-10> [--root] [<json-file>|-]
|
|
257
|
+
select --cap <1-10> [--root] [--slug] [<json-file>|-]
|
|
203
258
|
|
|
204
259
|
Exit codes: 0 ok, 2 usage, 3 blocked`)
|
|
205
260
|
}
|
|
@@ -210,6 +265,8 @@ func CommandUsage(name string) string {
|
|
|
210
265
|
switch name {
|
|
211
266
|
case "create":
|
|
212
267
|
return usageCreate
|
|
268
|
+
case "add":
|
|
269
|
+
return usageAdd
|
|
213
270
|
case "record-green":
|
|
214
271
|
return usageRecordGreen
|
|
215
272
|
case "abort":
|
|
@@ -414,6 +471,41 @@ func cmdCreate(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
|
|
|
414
471
|
return ExitOK
|
|
415
472
|
}
|
|
416
473
|
|
|
474
|
+
func cmdAdd(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
|
|
475
|
+
f, code, done := parseFlagsOrHelp(args, stdout, stderr, usageAdd)
|
|
476
|
+
if done {
|
|
477
|
+
return code
|
|
478
|
+
}
|
|
479
|
+
if code := requireRootSlug(f, stderr); code != ExitOK {
|
|
480
|
+
return code
|
|
481
|
+
}
|
|
482
|
+
if code := rejectUnsupportedFlags(f, stderr, false, false); code != ExitOK {
|
|
483
|
+
return code
|
|
484
|
+
}
|
|
485
|
+
if f.JSON == "" {
|
|
486
|
+
fmt.Fprintln(stderr, usageAdd)
|
|
487
|
+
return ExitUsage
|
|
488
|
+
}
|
|
489
|
+
data, err := readJSONInput(f.JSON, stdin)
|
|
490
|
+
if err != nil {
|
|
491
|
+
fmt.Fprintf(stderr, "parallel add: %v\n", err)
|
|
492
|
+
return ExitBlocked
|
|
493
|
+
}
|
|
494
|
+
slices, err := ParseSlicesJSON(data)
|
|
495
|
+
if err != nil {
|
|
496
|
+
fmt.Fprintf(stderr, "parallel add: %v\n", err)
|
|
497
|
+
return ExitBlocked
|
|
498
|
+
}
|
|
499
|
+
lease, err := Add(AddOpts{Root: f.Root, Slug: f.Slug, Slices: slices})
|
|
500
|
+
if err != nil {
|
|
501
|
+
fmt.Fprintf(stderr, "parallel add: %v\n", err)
|
|
502
|
+
return ExitBlocked
|
|
503
|
+
}
|
|
504
|
+
fmt.Fprintf(stdout, "added: %d worktrees; batch=%s active=%d base=%s\n",
|
|
505
|
+
len(slices), lease.BatchID, len(activeSlices(lease)), lease.Slices[len(lease.Slices)-1].BaseSHA)
|
|
506
|
+
return ExitOK
|
|
507
|
+
}
|
|
508
|
+
|
|
417
509
|
// rejectUnsupportedFlags reports flags a subcommand parses but must not
|
|
418
510
|
// silently ignore: --force only exists for cleanup, --apply-to-control only
|
|
419
511
|
// for integrate.
|
|
@@ -484,16 +576,26 @@ func cmdIntegrate(args []string, stdout, stderr io.Writer) int {
|
|
|
484
576
|
if code := rejectUnsupportedFlags(f, stderr, false, true); code != ExitOK {
|
|
485
577
|
return code
|
|
486
578
|
}
|
|
579
|
+
if f.Slice != "" && !f.ApplyToControl {
|
|
580
|
+
fmt.Fprintln(stderr, "parallel integrate: --slice requires --apply-to-control")
|
|
581
|
+
fmt.Fprintln(stderr, usageIntegrate)
|
|
582
|
+
return ExitUsage
|
|
583
|
+
}
|
|
487
584
|
tip, lease, err := Integrate(IntegrateOpts{
|
|
488
585
|
Root: f.Root,
|
|
489
586
|
Slug: f.Slug,
|
|
587
|
+
Slice: f.Slice,
|
|
490
588
|
ApplyToControl: f.ApplyToControl,
|
|
491
589
|
})
|
|
492
590
|
if err != nil {
|
|
493
591
|
fmt.Fprintf(stderr, "parallel integrate: %v\n", err)
|
|
494
592
|
return ExitBlocked
|
|
495
593
|
}
|
|
496
|
-
fmt.Fprintf(stdout, "integrate-ok: tip=%s status=%s apply_to_control=%v
|
|
594
|
+
fmt.Fprintf(stdout, "integrate-ok: tip=%s status=%s apply_to_control=%v", tip, lease.Status, f.ApplyToControl)
|
|
595
|
+
if f.Slice != "" {
|
|
596
|
+
fmt.Fprintf(stdout, " slice=%s", f.Slice)
|
|
597
|
+
}
|
|
598
|
+
fmt.Fprintln(stdout)
|
|
497
599
|
return ExitOK
|
|
498
600
|
}
|
|
499
601
|
|
|
@@ -22,6 +22,9 @@ const (
|
|
|
22
22
|
WrightGreen = "green"
|
|
23
23
|
WrightRed = "red"
|
|
24
24
|
WrightGap = "gap"
|
|
25
|
+
// WrightIntegrated marks a slice a per-slice integrate already landed on
|
|
26
|
+
// control; it no longer holds a parallel slot.
|
|
27
|
+
WrightIntegrated = "integrated"
|
|
25
28
|
|
|
26
29
|
MinParallelSlices = 2
|
|
27
30
|
MaxParallelSlices = 10
|
|
@@ -36,17 +39,22 @@ var (
|
|
|
36
39
|
)
|
|
37
40
|
|
|
38
41
|
// Lease is the machine-readable parallel batch lease (control tree only).
|
|
42
|
+
// ControlTip is control HEAD after this lease's most recent per-slice landing;
|
|
43
|
+
// empty means BaseSHA (nothing landed yet).
|
|
39
44
|
type Lease struct {
|
|
40
45
|
BatchID string `json:"batch_id"`
|
|
41
46
|
CreatedAt string `json:"created_at"`
|
|
42
47
|
BaseSHA string `json:"base_sha"`
|
|
48
|
+
ControlTip string `json:"control_tip,omitempty"`
|
|
43
49
|
N int `json:"n"`
|
|
44
50
|
Status string `json:"status"`
|
|
45
51
|
ControlPIDOrSession string `json:"control_pid_or_session"`
|
|
46
52
|
Slices []LeaseSlice `json:"slices"`
|
|
47
53
|
}
|
|
48
54
|
|
|
49
|
-
// LeaseSlice records one worker worktree under the lease.
|
|
55
|
+
// LeaseSlice records one worker worktree under the lease. BaseSHA is the
|
|
56
|
+
// commit its worktree was created from; empty means Lease.BaseSHA (a slice
|
|
57
|
+
// from the original create).
|
|
50
58
|
type LeaseSlice struct {
|
|
51
59
|
ID string `json:"id"`
|
|
52
60
|
Paths []string `json:"paths"`
|
|
@@ -54,6 +62,35 @@ type LeaseSlice struct {
|
|
|
54
62
|
Branch string `json:"branch"`
|
|
55
63
|
WrightStatus string `json:"wright_status"`
|
|
56
64
|
TransferCommit string `json:"transfer_commit,omitempty"`
|
|
65
|
+
BaseSHA string `json:"base_sha,omitempty"`
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// expectedControl is where control must sit: the tip of this lease's latest
|
|
69
|
+
// landing, or the batch base before any landing.
|
|
70
|
+
func expectedControl(lease *Lease) string {
|
|
71
|
+
if lease.ControlTip != "" {
|
|
72
|
+
return lease.ControlTip
|
|
73
|
+
}
|
|
74
|
+
return lease.BaseSHA
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// sliceBase is the commit sl's worktree was created from.
|
|
78
|
+
func sliceBase(lease *Lease, sl LeaseSlice) string {
|
|
79
|
+
if sl.BaseSHA != "" {
|
|
80
|
+
return sl.BaseSHA
|
|
81
|
+
}
|
|
82
|
+
return lease.BaseSHA
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// activeSlices returns the slices that still hold a parallel slot.
|
|
86
|
+
func activeSlices(lease *Lease) []SlicePaths {
|
|
87
|
+
var active []SlicePaths
|
|
88
|
+
for _, sl := range lease.Slices {
|
|
89
|
+
if sl.WrightStatus != WrightIntegrated {
|
|
90
|
+
active = append(active, SlicePaths{ID: sl.ID, Paths: sl.Paths})
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return active
|
|
57
94
|
}
|
|
58
95
|
|
|
59
96
|
func LeasePath(repoRoot, slug string) (string, error) {
|
|
@@ -132,8 +169,16 @@ func ValidateLease(lease *Lease) error {
|
|
|
132
169
|
default:
|
|
133
170
|
return fmt.Errorf("invalid lease status %q", lease.Status)
|
|
134
171
|
}
|
|
135
|
-
if
|
|
136
|
-
return
|
|
172
|
+
if lease.ControlTip != "" && !shaRE.MatchString(lease.ControlTip) {
|
|
173
|
+
return fmt.Errorf("invalid control_tip %q", lease.ControlTip)
|
|
174
|
+
}
|
|
175
|
+
// A rolling lease grows past the cap as landed slices free their slots;
|
|
176
|
+
// only the slices still active are bounded.
|
|
177
|
+
if len(lease.Slices) < MinParallelSlices {
|
|
178
|
+
return checkParallelSliceCount(len(lease.Slices))
|
|
179
|
+
}
|
|
180
|
+
if active := len(activeSlices(lease)); active > MaxParallelSlices {
|
|
181
|
+
return fmt.Errorf("parallel requires %d-%d slices active at once (got %d)", MinParallelSlices, MaxParallelSlices, active)
|
|
137
182
|
}
|
|
138
183
|
if lease.N != len(lease.Slices) {
|
|
139
184
|
return fmt.Errorf("lease n=%d does not match slices=%d", lease.N, len(lease.Slices))
|
|
@@ -151,13 +196,16 @@ func ValidateLease(lease *Lease) error {
|
|
|
151
196
|
return err
|
|
152
197
|
}
|
|
153
198
|
switch sl.WrightStatus {
|
|
154
|
-
case "", WrightPending, WrightGreen, WrightRed, WrightGap:
|
|
199
|
+
case "", WrightPending, WrightGreen, WrightRed, WrightGap, WrightIntegrated:
|
|
155
200
|
default:
|
|
156
201
|
return fmt.Errorf("slice %q: invalid wright_status %q", sl.ID, sl.WrightStatus)
|
|
157
202
|
}
|
|
158
203
|
if sl.TransferCommit != "" && !shaRE.MatchString(sl.TransferCommit) {
|
|
159
204
|
return fmt.Errorf("slice %q: invalid transfer_commit %q", sl.ID, sl.TransferCommit)
|
|
160
205
|
}
|
|
206
|
+
if sl.BaseSHA != "" && !shaRE.MatchString(sl.BaseSHA) {
|
|
207
|
+
return fmt.Errorf("slice %q: invalid base_sha %q", sl.ID, sl.BaseSHA)
|
|
208
|
+
}
|
|
161
209
|
}
|
|
162
210
|
return nil
|
|
163
211
|
}
|
|
@@ -176,6 +224,9 @@ func EmitLeaseMarkdown(lease *Lease) (string, error) {
|
|
|
176
224
|
fmt.Fprintf(&b, "batch_id: %s\n", yamlQuote(lease.BatchID))
|
|
177
225
|
fmt.Fprintf(&b, "created_at: %s\n", yamlQuote(lease.CreatedAt))
|
|
178
226
|
fmt.Fprintf(&b, "base_sha: %s\n", yamlQuote(lease.BaseSHA))
|
|
227
|
+
if lease.ControlTip != "" {
|
|
228
|
+
fmt.Fprintf(&b, "control_tip: %s\n", yamlQuote(lease.ControlTip))
|
|
229
|
+
}
|
|
179
230
|
fmt.Fprintf(&b, "n: %d\n", lease.N)
|
|
180
231
|
fmt.Fprintf(&b, "status: %s\n", yamlQuote(lease.Status))
|
|
181
232
|
fmt.Fprintf(&b, "control_pid_or_session: %s\n", yamlQuote(lease.ControlPIDOrSession))
|
|
@@ -192,6 +243,9 @@ func EmitLeaseMarkdown(lease *Lease) (string, error) {
|
|
|
192
243
|
}
|
|
193
244
|
fmt.Fprintf(&b, " worktree_path: %s\n", yamlQuote(sl.WorktreePath))
|
|
194
245
|
fmt.Fprintf(&b, " branch: %s\n", yamlQuote(sl.Branch))
|
|
246
|
+
if sl.BaseSHA != "" {
|
|
247
|
+
fmt.Fprintf(&b, " base_sha: %s\n", yamlQuote(sl.BaseSHA))
|
|
248
|
+
}
|
|
195
249
|
status := sl.WrightStatus
|
|
196
250
|
if status == "" {
|
|
197
251
|
status = WrightPending
|