devrites 5.7.2 → 5.8.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 +10 -0
- package/README.md +1 -1
- package/docs/command-map.md +1 -1
- package/docs/engine/commands.md +1 -0
- package/docs/orchestration.md +2 -2
- package/engine/internal/parallel/cli.go +69 -0
- package/engine/internal/parallel/select.go +76 -0
- package/engine/internal/parallel/select_test.go +95 -0
- package/pack/.claude/settings.json +1 -0
- package/pack/.claude/skills/devrites-lib/reference/standards/afk-hitl.md +2 -1
- package/pack/.claude/skills/rite-autocomplete/SKILL.md +4 -3
- package/pack/.claude/skills/rite-autocomplete/reference/loop.md +1 -1
- package/pack/.claude/skills/rite-build/SKILL.md +2 -1
- package/pack/.claude/skills/rite-build/reference/afk-discipline.md +3 -1
- package/pack/.claude/skills/rite-build/reference/one-slice-cycle.md +1 -1
- package/pack/.claude/skills/rite-build/reference/parallel-batch.md +17 -13
- package/pack/generated/claude/settings.json +1 -0
- package/pack/generated/claude/skills/devrites-lib/reference/standards/afk-hitl.md +2 -1
- package/pack/generated/claude/skills/rite-autocomplete/SKILL.md +4 -3
- package/pack/generated/claude/skills/rite-autocomplete/reference/loop.md +1 -1
- package/pack/generated/claude/skills/rite-build/SKILL.md +2 -1
- package/pack/generated/claude/skills/rite-build/reference/afk-discipline.md +3 -1
- package/pack/generated/claude/skills/rite-build/reference/one-slice-cycle.md +1 -1
- package/pack/generated/claude/skills/rite-build/reference/parallel-batch.md +17 -13
- package/pack/generated/codex/skills/devrites-lib/reference/standards/afk-hitl.md +2 -1
- package/pack/generated/codex/skills/rite-autocomplete/SKILL.md +4 -3
- package/pack/generated/codex/skills/rite-autocomplete/reference/loop.md +1 -1
- package/pack/generated/codex/skills/rite-build/SKILL.md +2 -1
- package/pack/generated/codex/skills/rite-build/reference/afk-discipline.md +3 -1
- package/pack/generated/codex/skills/rite-build/reference/one-slice-cycle.md +1 -1
- package/pack/generated/codex/skills/rite-build/reference/parallel-batch.md +17 -13
- package/pack/generated/omp/skills/devrites-lib/reference/standards/afk-hitl.md +2 -1
- package/pack/generated/omp/skills/rite-autocomplete/SKILL.md +4 -3
- package/pack/generated/omp/skills/rite-autocomplete/reference/loop.md +1 -1
- package/pack/generated/omp/skills/rite-build/SKILL.md +2 -1
- package/pack/generated/omp/skills/rite-build/reference/afk-discipline.md +3 -1
- package/pack/generated/omp/skills/rite-build/reference/one-slice-cycle.md +1 -1
- package/pack/generated/omp/skills/rite-build/reference/parallel-batch.md +17 -13
- package/pack/generated/pi/agents/devrites-code-reviewer.md +1 -1
- package/pack/generated/pi/agents/devrites-devex-reviewer.md +1 -1
- package/pack/generated/pi/agents/devrites-doubt-reviewer.md +1 -1
- package/pack/generated/pi/agents/devrites-evidence-scout.md +1 -1
- package/pack/generated/pi/agents/devrites-frontend-reviewer.md +1 -1
- package/pack/generated/pi/agents/devrites-performance-reviewer.md +1 -1
- package/pack/generated/pi/agents/devrites-plan-drafter.md +1 -1
- package/pack/generated/pi/agents/devrites-plan-reviewer.md +1 -1
- package/pack/generated/pi/agents/devrites-proof-runner.md +1 -1
- package/pack/generated/pi/agents/devrites-retrospector.md +1 -1
- package/pack/generated/pi/agents/devrites-security-auditor.md +1 -1
- package/pack/generated/pi/agents/devrites-simplifier-reviewer.md +1 -1
- package/pack/generated/pi/agents/devrites-slice-wright.md +1 -1
- package/pack/generated/pi/agents/devrites-spec-reviewer.md +1 -1
- package/pack/generated/pi/agents/devrites-strategy-reviewer.md +1 -1
- package/pack/generated/pi/agents/devrites-test-analyst.md +1 -1
- package/pack/generated/pi/agents/devrites-upgrade-planner.md +1 -1
- package/pack/generated/pi/skills/devrites-lib/reference/standards/afk-hitl.md +2 -1
- package/pack/generated/pi/skills/rite-autocomplete/SKILL.md +4 -3
- package/pack/generated/pi/skills/rite-autocomplete/reference/loop.md +1 -1
- package/pack/generated/pi/skills/rite-build/SKILL.md +2 -1
- package/pack/generated/pi/skills/rite-build/reference/afk-discipline.md +3 -1
- package/pack/generated/pi/skills/rite-build/reference/one-slice-cycle.md +1 -1
- package/pack/generated/pi/skills/rite-build/reference/parallel-batch.md +17 -13
- package/package.json +1 -1
- package/scripts/pi-generate.sh +52 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to DevRites are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and DevRites adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Releases are generated automatically by [semantic-release](https://semantic-release.gitbook.io/) from Conventional Commits on `main`.
|
|
4
4
|
|
|
5
|
+
## [5.8.0](https://github.com/ViktorsBaikers/DevRites/compare/v5.7.2...v5.8.0) (2026-09-14)
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
* **agents:** allowlist lean-ctx and lens tools on pi agents ([#74](https://github.com/ViktorsBaikers/DevRites/issues/74)) ([08390d3](https://github.com/ViktorsBaikers/DevRites/commit/08390d3bf3ccffec0353803e910ee7a7227ff6ba))
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
* **rite:** re-select parallel width after a serial round ([#73](https://github.com/ViktorsBaikers/DevRites/issues/73)) ([ac8f073](https://github.com/ViktorsBaikers/DevRites/commit/ac8f073c0640ab0350b92a87a86193de39dedd57))
|
|
14
|
+
|
|
5
15
|
## [5.7.2](https://github.com/ViktorsBaikers/DevRites/compare/v5.7.1...v5.7.2) (2026-09-14)
|
|
6
16
|
|
|
7
17
|
### Fixed
|
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ project-conventional push, tag, or PR action, and archive the workspace.
|
|
|
33
33
|
Unattended runs may create local WIP checkpoint commits along the way, but they
|
|
34
34
|
remain local unless Ship's disclosed plan includes an approved remote action.
|
|
35
35
|
|
|
36
|
-
**Status:** [`v5.
|
|
36
|
+
**Status:** [`v5.8.0`](https://github.com/ViktorsBaikers/DevRites/releases/tag/v5.8.0): see [`CHANGELOG.md`](CHANGELOG.md) for release notes.
|
|
37
37
|
|
|
38
38
|
This is the latest published release; `main` may contain unreleased work.
|
|
39
39
|
|
package/docs/command-map.md
CHANGED
|
@@ -40,7 +40,7 @@ The Go surface is intentionally closed and deterministic:
|
|
|
40
40
|
|
|
41
41
|
| Lane | Commands | Owner |
|
|
42
42
|
|---|---|---|
|
|
43
|
-
| Candidate and deterministic checks | `check candidate`, `check readiness [--emit-binding]`, `check seal`, `check task-graph`, `check path-disjoint`, `check skill-trust`, `check indexes` | Candidate validates/hashes the strict project manifest; readiness checks phase files, the required `tasks.md` slice graph, or emits the vetted Build-input binding; seal checks final files/open gates, that graph and binding, then exact candidate bindings. `check indexes` reports install manifest and optional code-index directory presence as JSON. |
|
|
43
|
+
| Candidate and deterministic checks | `check candidate`, `check readiness [--emit-binding]`, `check seal`, `check task-graph`, `check path-disjoint`, `parallel select`, `check skill-trust`, `check indexes` | Candidate validates/hashes the strict project manifest; readiness checks phase files, the required `tasks.md` slice graph, or emits the vetted Build-input binding; seal checks final files/open gates, that graph and binding, then exact candidate bindings. `check indexes` reports install manifest and optional code-index directory presence as JSON. `parallel select` returns the greedy path-disjoint subset ≤ cap from the supplied ready slices. |
|
|
44
44
|
| Workspace observation | `observe summary`, `orient`, `observe slice <slug> <SLICE-ID>` | JSON workspace snapshot for agents (cursor, task graph, `artifact_budgets`, `bulk_files`); `orient` is an alias for `observe summary`. `observe slice` prints one `SLICE-###` section of `tasks.md` so Build reads a slice, not the file. |
|
|
45
45
|
| Atomic workspace state | `state resolve`, `state merge-manifest`, `state close`, `migrate [--dry-run] [--answer id=choice]` | Go owns answer/drop/batch resolution, the recorded predecessor-chain manifest union for a release milestone, transactional close, and fail-closed v5 schema normalization. |
|
|
46
46
|
| Security | `secret-scan` | `/rite-ship`, safe hooks, or an operator scans staged blobs, stdin, or touched files. |
|
package/docs/engine/commands.md
CHANGED
|
@@ -16,6 +16,7 @@ workflow strategy.
|
|
|
16
16
|
| `check readiness --emit-binding <slug>` | Render the exact stable Build-input binding for Vet to record after review. |
|
|
17
17
|
| `check seal <slug>` | Check files required by target Phase `seal`, open human gates, the `tasks.md` slice graph, canonical `AC-###` presence when `tasks.md`/`test-plan.md` are required, the stable Build-input binding, and exact candidate bindings. |
|
|
18
18
|
| `check path-disjoint [--root <dir>] [<json-file> | -]` | Verify slice path sets are pairwise disjoint. |
|
|
19
|
+
| `parallel select --cap <1-10> [--root <dir>] [<json-file> | -]` | Choose the greedy path-disjoint subset ≤ cap from supplied ready slices. |
|
|
19
20
|
| `check task-graph <slug>` | Validate `tasks.md` slice dependency graph for cycles, unknown deps, malformed tokens, duplicate IDs, missing `Dependencies`, and `depends_on` mismatch. |
|
|
20
21
|
| `check skill-trust <path>` | Scan one skill/agent Markdown file for structural trust violations. |
|
|
21
22
|
| `observe summary <slug>` | Emit sanitized JSON workspace summary from one retained observation. `task_graph.ok` is true iff `task_graph.problems` is empty; `problems` lists cycle, unknown-dep, malformed-token, duplicate-id, and missing-`Dependencies` blockers. |
|
package/docs/orchestration.md
CHANGED
|
@@ -130,8 +130,8 @@ Isolation does not authorize same-worktree throughput. Parallel writers are allo
|
|
|
130
130
|
**only** under the `/rite-build` batch path when path-disjoint eligibility, abort-batch,
|
|
131
131
|
and a control `parallel-lease.md` apply. `--parallel N` (or the AFK sentinel
|
|
132
132
|
`max_parallel`, default cap 10 under `/rite-autocomplete`) is a **cap**: each round runs
|
|
133
|
-
the largest eligible set, recomputes after every
|
|
134
|
-
slice remains;
|
|
133
|
+
the largest eligible set, recomputes after every completed round, and repeats until no
|
|
134
|
+
pending slice remains; a one-slice round is serial for that round only (see
|
|
135
135
|
[`parallel-batch.md`](../pack/.claude/skills/rite-build/reference/parallel-batch.md)).
|
|
136
136
|
Same-worktree multi-writer and root-emulated worktrees remain forbidden; default
|
|
137
137
|
`/rite-build` stays one writer across linked worktrees.
|
|
@@ -5,6 +5,7 @@ import (
|
|
|
5
5
|
"fmt"
|
|
6
6
|
"io"
|
|
7
7
|
"os"
|
|
8
|
+
"strconv"
|
|
8
9
|
"strings"
|
|
9
10
|
)
|
|
10
11
|
|
|
@@ -71,6 +72,71 @@ func runPathDisjoint(args []string, stdin io.Reader, stdout, stderr io.Writer) i
|
|
|
71
72
|
return ExitOK
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
func cmdSelect(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
|
|
76
|
+
const usage = "usage: parallel select --cap <1-10> [--root <dir>] [<json-file>|-]"
|
|
77
|
+
root := ""
|
|
78
|
+
jsonPath := "-"
|
|
79
|
+
capN := 0
|
|
80
|
+
sawCap := false
|
|
81
|
+
for i := 0; i < len(args); i++ {
|
|
82
|
+
switch args[i] {
|
|
83
|
+
case "--root":
|
|
84
|
+
if i+1 >= len(args) {
|
|
85
|
+
fmt.Fprintln(stderr, usage)
|
|
86
|
+
return ExitUsage
|
|
87
|
+
}
|
|
88
|
+
i++
|
|
89
|
+
root = args[i]
|
|
90
|
+
case "--cap":
|
|
91
|
+
if i+1 >= len(args) {
|
|
92
|
+
fmt.Fprintln(stderr, usage)
|
|
93
|
+
return ExitUsage
|
|
94
|
+
}
|
|
95
|
+
i++
|
|
96
|
+
n, err := strconv.Atoi(args[i])
|
|
97
|
+
if err != nil {
|
|
98
|
+
fmt.Fprintf(stderr, "parallel select: --cap must be an integer\n")
|
|
99
|
+
return ExitUsage
|
|
100
|
+
}
|
|
101
|
+
capN = n
|
|
102
|
+
sawCap = true
|
|
103
|
+
case "-h", "--help":
|
|
104
|
+
fmt.Fprintln(stdout, usage)
|
|
105
|
+
return ExitOK
|
|
106
|
+
default:
|
|
107
|
+
if args[i] != "-" && strings.HasPrefix(args[i], "-") {
|
|
108
|
+
fmt.Fprintf(stderr, "parallel select: unknown flag %s\n", args[i])
|
|
109
|
+
return ExitUsage
|
|
110
|
+
}
|
|
111
|
+
jsonPath = args[i]
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if !sawCap {
|
|
115
|
+
fmt.Fprintln(stderr, usage)
|
|
116
|
+
return ExitUsage
|
|
117
|
+
}
|
|
118
|
+
data, err := readJSONInput(jsonPath, stdin)
|
|
119
|
+
if err != nil {
|
|
120
|
+
fmt.Fprintf(stderr, "parallel select: %v\n", err)
|
|
121
|
+
return ExitBlocked
|
|
122
|
+
}
|
|
123
|
+
slices, err := ParseSlicesJSON(data)
|
|
124
|
+
if err != nil {
|
|
125
|
+
fmt.Fprintf(stderr, "parallel select: %v\n", err)
|
|
126
|
+
return ExitBlocked
|
|
127
|
+
}
|
|
128
|
+
selected, err := SelectGreedy(capN, slices, root)
|
|
129
|
+
if err != nil {
|
|
130
|
+
fmt.Fprintf(stderr, "parallel select: %v\n", err)
|
|
131
|
+
if strings.Contains(err.Error(), "cap must be") {
|
|
132
|
+
return ExitUsage
|
|
133
|
+
}
|
|
134
|
+
return ExitBlocked
|
|
135
|
+
}
|
|
136
|
+
fmt.Fprintln(stdout, formatSelect(selected))
|
|
137
|
+
return ExitOK
|
|
138
|
+
}
|
|
139
|
+
|
|
74
140
|
func runParallel(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
|
|
75
141
|
if len(args) == 0 {
|
|
76
142
|
fmt.Fprintln(stderr, parallelUsage())
|
|
@@ -98,6 +164,8 @@ func runParallel(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
|
|
|
98
164
|
return cmdLeaseClear(rest, stdout, stderr)
|
|
99
165
|
case "check-disjoint", "path-disjoint":
|
|
100
166
|
return runPathDisjoint(rest, stdin, stdout, stderr)
|
|
167
|
+
case "select":
|
|
168
|
+
return cmdSelect(rest, stdin, stdout, stderr)
|
|
101
169
|
case "-h", "--help", "help":
|
|
102
170
|
fmt.Fprintln(stdout, parallelUsage())
|
|
103
171
|
return ExitOK
|
|
@@ -121,6 +189,7 @@ Subcommands:
|
|
|
121
189
|
lease-read --root --slug [--field name]
|
|
122
190
|
lease-clear --root --slug
|
|
123
191
|
check-disjoint [--root] [<json-file>|-]
|
|
192
|
+
select --cap <1-10> [--root] [<json-file>|-]
|
|
124
193
|
|
|
125
194
|
Exit codes: 0 ok, 2 usage, 3 blocked`)
|
|
126
195
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
package parallel
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"strings"
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
// SelectGreedy returns the largest plan-order pairwise-disjoint subset of ready
|
|
9
|
+
// whose size is ≤ cap. ready must already be dependency-satisfied and in plan
|
|
10
|
+
// order. cap is 1–MaxParallelSlices; 1 is a legal one-slice round, not a latch.
|
|
11
|
+
func SelectGreedy(cap int, ready []SlicePaths, root string) ([]SlicePaths, error) {
|
|
12
|
+
if cap < 1 || cap > MaxParallelSlices {
|
|
13
|
+
return nil, fmt.Errorf("cap must be 1-%d (got %d)", MaxParallelSlices, cap)
|
|
14
|
+
}
|
|
15
|
+
normalized := make([]SlicePaths, 0, len(ready))
|
|
16
|
+
seen := make(map[string]struct{}, len(ready))
|
|
17
|
+
for i, item := range ready {
|
|
18
|
+
label := fmt.Sprintf("slice %d", i)
|
|
19
|
+
if item.ID != "" {
|
|
20
|
+
if err := validateSliceID(item.ID); err != nil {
|
|
21
|
+
return nil, fmt.Errorf("%s: %w", label, err)
|
|
22
|
+
}
|
|
23
|
+
label = fmt.Sprintf("slice %q", item.ID)
|
|
24
|
+
} else {
|
|
25
|
+
return nil, fmt.Errorf("%s: missing id", label)
|
|
26
|
+
}
|
|
27
|
+
if _, ok := seen[item.ID]; ok {
|
|
28
|
+
return nil, fmt.Errorf("duplicate slice id %q", item.ID)
|
|
29
|
+
}
|
|
30
|
+
seen[item.ID] = struct{}{}
|
|
31
|
+
paths, err := validateSlicePaths(item.Paths, label, root)
|
|
32
|
+
if err != nil {
|
|
33
|
+
return nil, err
|
|
34
|
+
}
|
|
35
|
+
normalized = append(normalized, SlicePaths{ID: item.ID, Paths: paths})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
selected := make([]SlicePaths, 0, min(cap, len(normalized)))
|
|
39
|
+
for _, cand := range normalized {
|
|
40
|
+
if len(selected) >= cap {
|
|
41
|
+
break
|
|
42
|
+
}
|
|
43
|
+
if len(selected) == 0 {
|
|
44
|
+
selected = append(selected, cand)
|
|
45
|
+
continue
|
|
46
|
+
}
|
|
47
|
+
trial := append(append([]SlicePaths{}, selected...), cand)
|
|
48
|
+
if _, err := CheckPathDisjoint(trial, root); err != nil {
|
|
49
|
+
if strings.Contains(err.Error(), "overlap") {
|
|
50
|
+
continue
|
|
51
|
+
}
|
|
52
|
+
return nil, err
|
|
53
|
+
}
|
|
54
|
+
selected = trial
|
|
55
|
+
}
|
|
56
|
+
return selected, nil
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
func selectMode(n int) string {
|
|
60
|
+
switch {
|
|
61
|
+
case n <= 0:
|
|
62
|
+
return "none"
|
|
63
|
+
case n == 1:
|
|
64
|
+
return "serial"
|
|
65
|
+
default:
|
|
66
|
+
return "parallel"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
func formatSelect(selected []SlicePaths) string {
|
|
71
|
+
ids := make([]string, len(selected))
|
|
72
|
+
for i, item := range selected {
|
|
73
|
+
ids[i] = item.ID
|
|
74
|
+
}
|
|
75
|
+
return fmt.Sprintf("select: n_eff=%d mode=%s slices=%s", len(selected), selectMode(len(selected)), strings.Join(ids, ","))
|
|
76
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
package parallel
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"strings"
|
|
6
|
+
"testing"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
func TestSelectGreedyOneThenMany(t *testing.T) {
|
|
10
|
+
t.Parallel()
|
|
11
|
+
round1, err := SelectGreedy(5, []SlicePaths{
|
|
12
|
+
{ID: "SLICE-001", Paths: []string{"src/a.go"}},
|
|
13
|
+
}, "")
|
|
14
|
+
if err != nil {
|
|
15
|
+
t.Fatal(err)
|
|
16
|
+
}
|
|
17
|
+
if len(round1) != 1 || round1[0].ID != "SLICE-001" {
|
|
18
|
+
t.Fatalf("round1=%#v", round1)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
round2, err := SelectGreedy(5, []SlicePaths{
|
|
22
|
+
{ID: "SLICE-002", Paths: []string{"src/b.go"}},
|
|
23
|
+
{ID: "SLICE-003", Paths: []string{"src/c.go"}},
|
|
24
|
+
{ID: "SLICE-004", Paths: []string{"src/d.go"}},
|
|
25
|
+
{ID: "SLICE-005", Paths: []string{"src/e.go"}},
|
|
26
|
+
}, "")
|
|
27
|
+
if err != nil {
|
|
28
|
+
t.Fatal(err)
|
|
29
|
+
}
|
|
30
|
+
if len(round2) != 4 {
|
|
31
|
+
t.Fatalf("after a one-slice round, cap 5 with 4 ready must select 4, got %d (%#v)", len(round2), round2)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
func TestSelectGreedySkipsOverlapKeepsCap(t *testing.T) {
|
|
36
|
+
t.Parallel()
|
|
37
|
+
got, err := SelectGreedy(5, []SlicePaths{
|
|
38
|
+
{ID: "SLICE-001", Paths: []string{"src/shared.go"}},
|
|
39
|
+
{ID: "SLICE-002", Paths: []string{"src/shared.go", "src/b.go"}},
|
|
40
|
+
{ID: "SLICE-003", Paths: []string{"src/c.go"}},
|
|
41
|
+
}, "")
|
|
42
|
+
if err != nil {
|
|
43
|
+
t.Fatal(err)
|
|
44
|
+
}
|
|
45
|
+
if len(got) != 2 || got[0].ID != "SLICE-001" || got[1].ID != "SLICE-003" {
|
|
46
|
+
t.Fatalf("got %#v", got)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
func TestSelectGreedyRejectsBadCap(t *testing.T) {
|
|
51
|
+
t.Parallel()
|
|
52
|
+
if _, err := SelectGreedy(0, nil, ""); err == nil {
|
|
53
|
+
t.Fatal("cap 0 should fail")
|
|
54
|
+
}
|
|
55
|
+
if _, err := SelectGreedy(11, nil, ""); err == nil {
|
|
56
|
+
t.Fatal("cap 11 should fail")
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
func TestSelectCLIOneThenMany(t *testing.T) {
|
|
61
|
+
t.Parallel()
|
|
62
|
+
var stdout, stderr bytes.Buffer
|
|
63
|
+
in := strings.NewReader(`{"slices":[{"id":"SLICE-001","paths":["src/a.go"]}]}`)
|
|
64
|
+
code := Run("parallel", []string{"select", "--cap", "5", "-"}, in, &stdout, &stderr)
|
|
65
|
+
if code != ExitOK {
|
|
66
|
+
t.Fatalf("code=%d stderr=%s", code, stderr.String())
|
|
67
|
+
}
|
|
68
|
+
if got := stdout.String(); got != "select: n_eff=1 mode=serial slices=SLICE-001\n" {
|
|
69
|
+
t.Fatalf("round1 got %q", got)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
stdout.Reset()
|
|
73
|
+
stderr.Reset()
|
|
74
|
+
in = strings.NewReader(`{"slices":[
|
|
75
|
+
{"id":"SLICE-002","paths":["src/b.go"]},
|
|
76
|
+
{"id":"SLICE-003","paths":["src/c.go"]},
|
|
77
|
+
{"id":"SLICE-004","paths":["src/d.go"]}
|
|
78
|
+
]}`)
|
|
79
|
+
code = Run("parallel", []string{"select", "--cap", "5", "-"}, in, &stdout, &stderr)
|
|
80
|
+
if code != ExitOK {
|
|
81
|
+
t.Fatalf("code=%d stderr=%s", code, stderr.String())
|
|
82
|
+
}
|
|
83
|
+
if got := stdout.String(); got != "select: n_eff=3 mode=parallel slices=SLICE-002,SLICE-003,SLICE-004\n" {
|
|
84
|
+
t.Fatalf("round2 got %q; must not stay n_eff=1", got)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
func TestSelectCLIRequiresCap(t *testing.T) {
|
|
89
|
+
t.Parallel()
|
|
90
|
+
var stdout, stderr bytes.Buffer
|
|
91
|
+
code := Run("parallel", []string{"select", "-"}, strings.NewReader(`{"slices":[]}`), &stdout, &stderr)
|
|
92
|
+
if code != ExitUsage {
|
|
93
|
+
t.Fatalf("code=%d want %d stderr=%s", code, ExitUsage, stderr.String())
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"Bash(devrites-engine orient *)",
|
|
14
14
|
"Bash(devrites-engine observe *)",
|
|
15
15
|
"Bash(devrites-engine parallel status *)",
|
|
16
|
+
"Bash(devrites-engine parallel select *)",
|
|
16
17
|
"Bash(devrites-engine parallel create *)",
|
|
17
18
|
"Bash(devrites-engine parallel record-green *)",
|
|
18
19
|
"Bash(devrites-engine parallel integrate *)",
|
|
@@ -120,7 +120,8 @@ candidate manifest to cover the whole recorded chain — run
|
|
|
120
120
|
run exactly as without this field.
|
|
121
121
|
|
|
122
122
|
`max_parallel` caps a Build batch for unattended runs; `1` forces the serial cycle.
|
|
123
|
-
Unattended runs take the largest eligible set ≤ cap and recompute after every
|
|
123
|
+
Unattended runs take the largest eligible set ≤ cap and recompute after every completed
|
|
124
|
+
round (serial slice or parallel integrate)
|
|
124
125
|
([parallel-batch.md § Dynamic selection and re-batching](../../../rite-build/reference/parallel-batch.md#dynamic-selection-and-re-batching));
|
|
125
126
|
an explicit `/rite-build --parallel N` cap wins for that invocation.
|
|
126
127
|
|
|
@@ -94,9 +94,10 @@ executable controller/harness/bundle bytes or a missing writer, read
|
|
|
94
94
|
`/rite-review` → `/rite-seal`. Read and execute each skill; durable files, not
|
|
95
95
|
chat, carry state. Apply the mutable post-vet budget before first Build.
|
|
96
96
|
Build runs the largest eligible path-disjoint batch (cap: `--parallel N`, else
|
|
97
|
-
sentinel `max_parallel`, else 10) and recomputes after every
|
|
98
|
-
pending slice remains;
|
|
99
|
-
host
|
|
97
|
+
sentinel `max_parallel`, else 10) and recomputes after every completed round until no
|
|
98
|
+
pending slice remains; a one-slice round is serial for that round only, then
|
|
99
|
+
selection runs again (host isolation still forces serial when concurrent writers
|
|
100
|
+
cannot be separated).
|
|
100
101
|
**Completion:** loop reaches Seal GO or persists a valid stop before any later phase.
|
|
101
102
|
5. **Apply stops.** At every gate use
|
|
102
103
|
[stop-conditions.md](reference/stop-conditions.md). A `blocked` label alone is not a stop condition:
|
|
@@ -65,7 +65,7 @@ Workspace files carry state; chat does not. Read and execute each phase skill:
|
|
|
65
65
|
| 3 | `/rite-temper` | harden or reduce; expansion and irreversible risk pause |
|
|
66
66
|
| 4 | `/rite-define` | approved plan/tasks/traceability |
|
|
67
67
|
| 5 | `/rite-vet` | every plan; derive mutable budget after READY |
|
|
68
|
-
| 6 | `/rite-build` batch loop | largest eligible path-disjoint set (cap 10;
|
|
68
|
+
| 6 | `/rite-build` batch loop | largest eligible path-disjoint set (cap 10; one-slice round when <2 eligible); recompute after each round; charge once per green built slice |
|
|
69
69
|
| 7 | `/rite-prove` | all slices built; approved proof; recovery on red |
|
|
70
70
|
| 8 | `/rite-polish` | re-prove after code edits |
|
|
71
71
|
| 9 | `/rite-review` | in-scope correction then fresh proof |
|
|
@@ -60,7 +60,8 @@ Wright applies anti-slop; root verifies returns and never patches source.
|
|
|
60
60
|
|
|
61
61
|
Omitted/`1` ≡ serial; `2`–`10` is a **cap** — the root runs the largest eligible
|
|
62
62
|
path-disjoint set it can start now (`N_eff`), takes fewer when the cap cannot be
|
|
63
|
-
filled, and recomputes after every
|
|
63
|
+
filled, and recomputes after every completed round (serial slice or parallel
|
|
64
|
+
integrate) until no pending slice remains
|
|
64
65
|
([`parallel-batch.md` § Dynamic selection](reference/parallel-batch.md#dynamic-selection-and-re-batching)).
|
|
65
66
|
Unattended runs (`/rite-autocomplete`, `.devrites/AFK` `max_parallel`) repeat batches
|
|
66
67
|
inside the same run; HITL stops after each batch. Non-integer/`N≤0`/`N>10` hard refuse.
|
|
@@ -16,7 +16,9 @@ control; irreversible work (destructive/auth/public API) always pauses.
|
|
|
16
16
|
The controlling root owns the cap:
|
|
17
17
|
|
|
18
18
|
1. **Before every dispatch**, re-read `.devrites/AFK`, `state.md`, and the
|
|
19
|
-
selected slice.
|
|
19
|
+
selected slice. When `--parallel N` or sentinel `max_parallel` is greater than
|
|
20
|
+
1, recompute `N_eff` via `parallel select` before the next dispatch — a prior
|
|
21
|
+
one-slice round does not keep the run serial. A configured `max_slices` and any existing
|
|
20
22
|
`afk_slices_remaining` value, including its released bullet form, must each
|
|
21
23
|
be a decimal nonnegative integer. A missing
|
|
22
24
|
`state.md` or malformed configured value fails closed; an omitted cap is the
|
|
@@ -26,7 +26,7 @@ RECORD → orchestrator: after green verification, update state.md/evidence.m
|
|
|
26
26
|
upsert the authoritative candidate manifest from the actual scoped diff
|
|
27
27
|
(CHECKPOINT) → orchestrator: commit the proven slice local-only as WIP(<slug>)
|
|
28
28
|
with a [devrites-context] body → clean tree, survives a crash (checkpoint.md)
|
|
29
|
-
NEXT → HITL root reports and stops
|
|
29
|
+
NEXT → HITL root reports and stops. AFK with parallel cap>1: return to parallel-batch selection and run the new N_eff; otherwise repeat under afk-discipline.md
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
## Why the boundary matters
|
|
@@ -28,18 +28,20 @@ N_eff = min(
|
|
|
28
28
|
)
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
Select **greedily in plan order**:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
nothing pending → Prove. A cap that cannot be filled
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
31
|
+
Select **greedily in plan order**: pass every currently dependency-ready pending slice
|
|
32
|
+
(plan order, `Files likely touched`) to
|
|
33
|
+
`devrites-engine parallel select --cap N [--root <dir>]`. `N_eff ≥ 2` → parallel batch;
|
|
34
|
+
`N_eff = 1` → one serial round; nothing pending → Prove. A cap that cannot be filled
|
|
35
|
+
is normal: take fewer. Serial is this round's size, not the rest of the run.
|
|
36
|
+
|
|
37
|
+
**Re-batch automatically after every completed round** (parallel integrate+cleanup or
|
|
38
|
+
a serial slice). Re-read `state.md`, pending slices, and the tree, then run
|
|
39
|
+
`parallel select` again: finished work leaves the pending set, and satisfied
|
|
40
|
+
dependencies unlock new slices. Under `.devrites/AFK` and `/rite-autocomplete` the
|
|
41
|
+
repeat is the same run — HITL stops after each batch. `--parallel 5` with one eligible
|
|
42
|
+
slice runs that one; when two to five then become eligible, the next round takes that
|
|
43
|
+
many. **Failing case:** the run stays one-by-one after a serial round even though more
|
|
44
|
+
slices are now eligible under the same cap.
|
|
43
45
|
|
|
44
46
|
## Path-disjoint
|
|
45
47
|
|
|
@@ -71,7 +73,8 @@ Lease: `batch_id`, `created_at`, `base_sha`, `n`,
|
|
|
71
73
|
1. Orient/gate; parse the cap; compute `N_eff` and select the eligible set
|
|
72
74
|
([Dynamic selection and re-batching](#dynamic-selection-and-re-batching)) —
|
|
73
75
|
candidates from `orient` `task_graph.slices` + built list, each candidate's
|
|
74
|
-
`Files likely touched` via `observe slice`,
|
|
76
|
+
`Files likely touched` via `observe slice`, then
|
|
77
|
+
`parallel select --cap N` (pairwise disjointness is inside that verb).
|
|
75
78
|
2. Write lease; freeze `B=HEAD`; `parallel create` worktrees.
|
|
76
79
|
3. Dispatch ≤10 wrights in parallel (cwd=worktree; allowlist; prove `HEAD==B`).
|
|
77
80
|
4. Inspect each returned path list, cumulative diff from `B`, transfer identity,
|
|
@@ -163,6 +166,7 @@ Repair rejected work in place; never discard/rebuild while budget remains.
|
|
|
163
166
|
## Engine verbs
|
|
164
167
|
|
|
165
168
|
```text
|
|
169
|
+
devrites-engine parallel select --cap <1-10> [--root <dir>] [<json-file>|-]
|
|
166
170
|
devrites-engine parallel create --root <repo> --slug <slug> --batch <id> --base <B> --json <file>|-
|
|
167
171
|
devrites-engine parallel record-green --root <repo> --slug <slug> --slice <id> --commit <sha>
|
|
168
172
|
devrites-engine parallel integrate --root <repo> --slug <slug> --apply-to-control
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"Bash(devrites-engine orient *)",
|
|
14
14
|
"Bash(devrites-engine observe *)",
|
|
15
15
|
"Bash(devrites-engine parallel status *)",
|
|
16
|
+
"Bash(devrites-engine parallel select *)",
|
|
16
17
|
"Bash(devrites-engine parallel create *)",
|
|
17
18
|
"Bash(devrites-engine parallel record-green *)",
|
|
18
19
|
"Bash(devrites-engine parallel integrate *)",
|
|
@@ -120,7 +120,8 @@ candidate manifest to cover the whole recorded chain — run
|
|
|
120
120
|
run exactly as without this field.
|
|
121
121
|
|
|
122
122
|
`max_parallel` caps a Build batch for unattended runs; `1` forces the serial cycle.
|
|
123
|
-
Unattended runs take the largest eligible set ≤ cap and recompute after every
|
|
123
|
+
Unattended runs take the largest eligible set ≤ cap and recompute after every completed
|
|
124
|
+
round (serial slice or parallel integrate)
|
|
124
125
|
([parallel-batch.md § Dynamic selection and re-batching](../../../rite-build/reference/parallel-batch.md#dynamic-selection-and-re-batching));
|
|
125
126
|
an explicit `/rite-build --parallel N` cap wins for that invocation.
|
|
126
127
|
|
|
@@ -94,9 +94,10 @@ executable controller/harness/bundle bytes or a missing writer, read
|
|
|
94
94
|
`/rite-review` → `/rite-seal`. Read and execute each skill; durable files, not
|
|
95
95
|
chat, carry state. Apply the mutable post-vet budget before first Build.
|
|
96
96
|
Build runs the largest eligible path-disjoint batch (cap: `--parallel N`, else
|
|
97
|
-
sentinel `max_parallel`, else 10) and recomputes after every
|
|
98
|
-
pending slice remains;
|
|
99
|
-
host
|
|
97
|
+
sentinel `max_parallel`, else 10) and recomputes after every completed round until no
|
|
98
|
+
pending slice remains; a one-slice round is serial for that round only, then
|
|
99
|
+
selection runs again (host isolation still forces serial when concurrent writers
|
|
100
|
+
cannot be separated).
|
|
100
101
|
**Completion:** loop reaches Seal GO or persists a valid stop before any later phase.
|
|
101
102
|
5. **Apply stops.** At every gate use
|
|
102
103
|
[stop-conditions.md](reference/stop-conditions.md). A `blocked` label alone is not a stop condition:
|
|
@@ -65,7 +65,7 @@ Workspace files carry state; chat does not. Read and execute each phase skill:
|
|
|
65
65
|
| 3 | `/rite-temper` | harden or reduce; expansion and irreversible risk pause |
|
|
66
66
|
| 4 | `/rite-define` | approved plan/tasks/traceability |
|
|
67
67
|
| 5 | `/rite-vet` | every plan; derive mutable budget after READY |
|
|
68
|
-
| 6 | `/rite-build` batch loop | largest eligible path-disjoint set (cap 10;
|
|
68
|
+
| 6 | `/rite-build` batch loop | largest eligible path-disjoint set (cap 10; one-slice round when <2 eligible); recompute after each round; charge once per green built slice |
|
|
69
69
|
| 7 | `/rite-prove` | all slices built; approved proof; recovery on red |
|
|
70
70
|
| 8 | `/rite-polish` | re-prove after code edits |
|
|
71
71
|
| 9 | `/rite-review` | in-scope correction then fresh proof |
|
|
@@ -60,7 +60,8 @@ Wright applies anti-slop; root verifies returns and never patches source.
|
|
|
60
60
|
|
|
61
61
|
Omitted/`1` ≡ serial; `2`–`10` is a **cap** — the root runs the largest eligible
|
|
62
62
|
path-disjoint set it can start now (`N_eff`), takes fewer when the cap cannot be
|
|
63
|
-
filled, and recomputes after every
|
|
63
|
+
filled, and recomputes after every completed round (serial slice or parallel
|
|
64
|
+
integrate) until no pending slice remains
|
|
64
65
|
([`parallel-batch.md` § Dynamic selection](reference/parallel-batch.md#dynamic-selection-and-re-batching)).
|
|
65
66
|
Unattended runs (`/rite-autocomplete`, `.devrites/AFK` `max_parallel`) repeat batches
|
|
66
67
|
inside the same run; HITL stops after each batch. Non-integer/`N≤0`/`N>10` hard refuse.
|
|
@@ -16,7 +16,9 @@ control; irreversible work (destructive/auth/public API) always pauses.
|
|
|
16
16
|
The controlling root owns the cap:
|
|
17
17
|
|
|
18
18
|
1. **Before every dispatch**, re-read `.devrites/AFK`, `state.md`, and the
|
|
19
|
-
selected slice.
|
|
19
|
+
selected slice. When `--parallel N` or sentinel `max_parallel` is greater than
|
|
20
|
+
1, recompute `N_eff` via `parallel select` before the next dispatch — a prior
|
|
21
|
+
one-slice round does not keep the run serial. A configured `max_slices` and any existing
|
|
20
22
|
`afk_slices_remaining` value, including its released bullet form, must each
|
|
21
23
|
be a decimal nonnegative integer. A missing
|
|
22
24
|
`state.md` or malformed configured value fails closed; an omitted cap is the
|
|
@@ -26,7 +26,7 @@ RECORD → orchestrator: after green verification, update state.md/evidence.m
|
|
|
26
26
|
upsert the authoritative candidate manifest from the actual scoped diff
|
|
27
27
|
(CHECKPOINT) → orchestrator: commit the proven slice local-only as WIP(<slug>)
|
|
28
28
|
with a [devrites-context] body → clean tree, survives a crash (checkpoint.md)
|
|
29
|
-
NEXT → HITL root reports and stops
|
|
29
|
+
NEXT → HITL root reports and stops. AFK with parallel cap>1: return to parallel-batch selection and run the new N_eff; otherwise repeat under afk-discipline.md
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
## Why the boundary matters
|
|
@@ -28,18 +28,20 @@ N_eff = min(
|
|
|
28
28
|
)
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
Select **greedily in plan order**:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
nothing pending → Prove. A cap that cannot be filled
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
31
|
+
Select **greedily in plan order**: pass every currently dependency-ready pending slice
|
|
32
|
+
(plan order, `Files likely touched`) to
|
|
33
|
+
`devrites-engine parallel select --cap N [--root <dir>]`. `N_eff ≥ 2` → parallel batch;
|
|
34
|
+
`N_eff = 1` → one serial round; nothing pending → Prove. A cap that cannot be filled
|
|
35
|
+
is normal: take fewer. Serial is this round's size, not the rest of the run.
|
|
36
|
+
|
|
37
|
+
**Re-batch automatically after every completed round** (parallel integrate+cleanup or
|
|
38
|
+
a serial slice). Re-read `state.md`, pending slices, and the tree, then run
|
|
39
|
+
`parallel select` again: finished work leaves the pending set, and satisfied
|
|
40
|
+
dependencies unlock new slices. Under `.devrites/AFK` and `/rite-autocomplete` the
|
|
41
|
+
repeat is the same run — HITL stops after each batch. `--parallel 5` with one eligible
|
|
42
|
+
slice runs that one; when two to five then become eligible, the next round takes that
|
|
43
|
+
many. **Failing case:** the run stays one-by-one after a serial round even though more
|
|
44
|
+
slices are now eligible under the same cap.
|
|
43
45
|
|
|
44
46
|
## Path-disjoint
|
|
45
47
|
|
|
@@ -71,7 +73,8 @@ Lease: `batch_id`, `created_at`, `base_sha`, `n`,
|
|
|
71
73
|
1. Orient/gate; parse the cap; compute `N_eff` and select the eligible set
|
|
72
74
|
([Dynamic selection and re-batching](#dynamic-selection-and-re-batching)) —
|
|
73
75
|
candidates from `orient` `task_graph.slices` + built list, each candidate's
|
|
74
|
-
`Files likely touched` via `observe slice`,
|
|
76
|
+
`Files likely touched` via `observe slice`, then
|
|
77
|
+
`parallel select --cap N` (pairwise disjointness is inside that verb).
|
|
75
78
|
2. Write lease; freeze `B=HEAD`; `parallel create` worktrees.
|
|
76
79
|
3. Dispatch ≤10 wrights in parallel (cwd=worktree; allowlist; prove `HEAD==B`).
|
|
77
80
|
4. Inspect each returned path list, cumulative diff from `B`, transfer identity,
|
|
@@ -163,6 +166,7 @@ Repair rejected work in place; never discard/rebuild while budget remains.
|
|
|
163
166
|
## Engine verbs
|
|
164
167
|
|
|
165
168
|
```text
|
|
169
|
+
devrites-engine parallel select --cap <1-10> [--root <dir>] [<json-file>|-]
|
|
166
170
|
devrites-engine parallel create --root <repo> --slug <slug> --batch <id> --base <B> --json <file>|-
|
|
167
171
|
devrites-engine parallel record-green --root <repo> --slug <slug> --slice <id> --commit <sha>
|
|
168
172
|
devrites-engine parallel integrate --root <repo> --slug <slug> --apply-to-control
|
|
@@ -120,7 +120,8 @@ candidate manifest to cover the whole recorded chain — run
|
|
|
120
120
|
run exactly as without this field.
|
|
121
121
|
|
|
122
122
|
`max_parallel` caps a Build batch for unattended runs; `1` forces the serial cycle.
|
|
123
|
-
Unattended runs take the largest eligible set ≤ cap and recompute after every
|
|
123
|
+
Unattended runs take the largest eligible set ≤ cap and recompute after every completed
|
|
124
|
+
round (serial slice or parallel integrate)
|
|
124
125
|
([parallel-batch.md § Dynamic selection and re-batching](../../../rite-build/reference/parallel-batch.md#dynamic-selection-and-re-batching));
|
|
125
126
|
an explicit `$rite-build --parallel N` cap wins for that invocation.
|
|
126
127
|
|
|
@@ -94,9 +94,10 @@ executable controller/harness/bundle bytes or a missing writer, read
|
|
|
94
94
|
`$rite-review` → `$rite-seal`. Read and execute each skill; durable files, not
|
|
95
95
|
chat, carry state. Apply the mutable post-vet budget before first Build.
|
|
96
96
|
Build runs the largest eligible path-disjoint batch (cap: `--parallel N`, else
|
|
97
|
-
sentinel `max_parallel`, else 10) and recomputes after every
|
|
98
|
-
pending slice remains;
|
|
99
|
-
host
|
|
97
|
+
sentinel `max_parallel`, else 10) and recomputes after every completed round until no
|
|
98
|
+
pending slice remains; a one-slice round is serial for that round only, then
|
|
99
|
+
selection runs again (host isolation still forces serial when concurrent writers
|
|
100
|
+
cannot be separated).
|
|
100
101
|
**Completion:** loop reaches Seal GO or persists a valid stop before any later phase.
|
|
101
102
|
5. **Apply stops.** At every gate use
|
|
102
103
|
[stop-conditions.md](reference/stop-conditions.md). A `blocked` label alone is not a stop condition:
|