devrites 7.3.5 → 7.3.7
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 +26 -0
- package/README.md +1 -1
- package/docs/adr/0040-early-preparation-admission.md +76 -0
- package/docs/adr/README.md +1 -0
- package/docs/engine/commands.md +48 -6
- package/engine/internal/lib/context.go +50 -2
- package/engine/internal/lib/continuation.go +2 -0
- package/engine/internal/lib/dispatch.go +75 -20
- package/engine/internal/lib/frontier.go +40 -16
- package/engine/internal/lib/frontier_controller.go +53 -38
- package/engine/internal/lib/frontier_preparation.go +210 -0
- package/engine/internal/lib/next.go +5 -2
- package/engine/internal/lib/regression.go +21 -7
- package/engine/internal/lib/taskgraph.go +2 -2
- package/engine/internal/lib/triggers.go +9 -0
- package/pack/.claude/skills/devrites-lib/SKILL.md +2 -0
- package/pack/.claude/skills/devrites-lib/reference/parallel-workspaces.md +12 -10
- package/pack/.claude/skills/rite-autocomplete/SKILL.md +7 -5
- package/pack/.claude/skills/rite-autocomplete/reference/loop.md +55 -42
- package/pack/generated/claude/skills/devrites-lib/SKILL.md +2 -0
- package/pack/generated/claude/skills/devrites-lib/reference/parallel-workspaces.md +12 -10
- package/pack/generated/claude/skills/rite-autocomplete/SKILL.md +7 -5
- package/pack/generated/claude/skills/rite-autocomplete/reference/loop.md +55 -42
- package/pack/generated/codex/skills/devrites-lib/SKILL.md +2 -0
- package/pack/generated/codex/skills/devrites-lib/reference/parallel-workspaces.md +12 -10
- package/pack/generated/codex/skills/rite-autocomplete/SKILL.md +7 -5
- package/pack/generated/codex/skills/rite-autocomplete/reference/loop.md +55 -42
- package/pack/generated/devin/skills/devrites-lib/SKILL.md +2 -0
- package/pack/generated/devin/skills/devrites-lib/reference/parallel-workspaces.md +12 -10
- package/pack/generated/devin/skills/rite-autocomplete/SKILL.md +7 -5
- package/pack/generated/devin/skills/rite-autocomplete/reference/loop.md +55 -42
- package/pack/generated/dsh/skills/devrites-lib/SKILL.md +2 -0
- package/pack/generated/dsh/skills/devrites-lib/reference/parallel-workspaces.md +12 -10
- package/pack/generated/dsh/skills/rite-autocomplete/SKILL.md +7 -5
- package/pack/generated/dsh/skills/rite-autocomplete/reference/loop.md +55 -42
- package/pack/generated/omp/skills/devrites-lib/SKILL.md +2 -0
- package/pack/generated/omp/skills/devrites-lib/reference/parallel-workspaces.md +12 -10
- package/pack/generated/omp/skills/rite-autocomplete/SKILL.md +7 -5
- package/pack/generated/omp/skills/rite-autocomplete/reference/loop.md +55 -42
- package/pack/generated/pi/skills/devrites-lib/SKILL.md +2 -0
- package/pack/generated/pi/skills/devrites-lib/reference/parallel-workspaces.md +12 -10
- package/pack/generated/pi/skills/rite-autocomplete/SKILL.md +7 -5
- package/pack/generated/pi/skills/rite-autocomplete/reference/loop.md +55 -42
- package/package.json +1 -1
|
@@ -134,19 +134,14 @@ func frontierSerialReason(records frontierRecords, source, workspace, id string)
|
|
|
134
134
|
return records.serial[parallel.SliceKey(workspace, "SLICE-"+sliceNumber(id))]
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
func frontierPendingPreparation(
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return false, nil
|
|
141
|
-
}
|
|
142
|
-
if err != nil {
|
|
143
|
-
return false, err
|
|
144
|
-
}
|
|
145
|
-
dispatch, err := decodeDispatch(raw)
|
|
146
|
-
if err != nil {
|
|
147
|
-
return false, err
|
|
137
|
+
func frontierPendingPreparation(dispatch *dispatchFile) bool {
|
|
138
|
+
if dispatch == nil {
|
|
139
|
+
return false
|
|
148
140
|
}
|
|
149
141
|
for _, wave := range dispatch.Waves {
|
|
142
|
+
if wave.SequenceResearch {
|
|
143
|
+
continue
|
|
144
|
+
}
|
|
150
145
|
if wave.State != "open" && wave.State != "sealed" {
|
|
151
146
|
continue
|
|
152
147
|
}
|
|
@@ -155,23 +150,12 @@ func frontierPendingPreparation(n *frontierWorkspace) (bool, error) {
|
|
|
155
150
|
continue
|
|
156
151
|
}
|
|
157
152
|
base := roleBase(role)
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
if base == "evidence-scout" || base == "strategy-reviewer" {
|
|
161
|
-
return true, nil
|
|
162
|
-
}
|
|
163
|
-
case "define", "plan":
|
|
164
|
-
if base == "plan-drafter" || base == "evidence-scout" {
|
|
165
|
-
return true, nil
|
|
166
|
-
}
|
|
167
|
-
case "vet":
|
|
168
|
-
if base == "plan-reviewer" || base == "devex-reviewer" || base == "strategy-reviewer" || base == "code-reviewer" {
|
|
169
|
-
return true, nil
|
|
170
|
-
}
|
|
153
|
+
if preparationRole(strings.ToLower(wave.Phase), base) {
|
|
154
|
+
return true
|
|
171
155
|
}
|
|
172
156
|
}
|
|
173
157
|
}
|
|
174
|
-
return false
|
|
158
|
+
return false
|
|
175
159
|
}
|
|
176
160
|
|
|
177
161
|
func frontierSuccessor(n *frontierWorkspace, nodes map[string]*frontierWorkspace, mapping map[int]string) (string, error) {
|
|
@@ -199,8 +183,8 @@ func frontierSuccessor(n *frontierWorkspace, nodes map[string]*frontierWorkspace
|
|
|
199
183
|
|
|
200
184
|
func frontierPreparationAdmission(root, source string, nodes map[string]*frontierWorkspace, mapping map[int]string, problem string, allow bool, records frontierRecords) ([]string, error) {
|
|
201
185
|
n := nodes[source]
|
|
202
|
-
if
|
|
203
|
-
return nil,
|
|
186
|
+
if n == nil {
|
|
187
|
+
return nil, fmt.Errorf("source %s is not an existing sequence workspace", source)
|
|
204
188
|
}
|
|
205
189
|
if hold, err := frontierQuestionHold(n); err != nil || hold != "" {
|
|
206
190
|
return nil, err
|
|
@@ -222,15 +206,20 @@ func frontierPreparationAdmission(root, source string, nodes map[string]*frontie
|
|
|
222
206
|
if reason == "" {
|
|
223
207
|
reason = "positive remaining budget has no exact recorded successor"
|
|
224
208
|
}
|
|
225
|
-
return nil, fmt.Errorf("source %s native authority-normalization required before
|
|
209
|
+
return nil, fmt.Errorf("source %s native authority-normalization required before forward-phase admission: %s; read sequence artifacts, record current roadmap through its owning decision, or retain frontier-blocked: sequence — <specific constraint>", source, reason)
|
|
226
210
|
}
|
|
227
|
-
if target == "" {
|
|
211
|
+
if target == "" || nodes[target] == nil && (!allow || n.hasRemaining && n.remaining == 0) {
|
|
228
212
|
return nil, nil
|
|
229
213
|
}
|
|
214
|
+
var successorDispatch *dispatchFile
|
|
230
215
|
if child := nodes[target]; child != nil {
|
|
231
216
|
if _, err := frontierChain(nodes, target); err != nil {
|
|
232
217
|
return nil, err
|
|
233
218
|
}
|
|
219
|
+
successorDispatch, err = frontierPreparationBindings(child, nil)
|
|
220
|
+
if err != nil {
|
|
221
|
+
return nil, err
|
|
222
|
+
}
|
|
234
223
|
}
|
|
235
224
|
if reason := records.blocked[target]; reason != "" {
|
|
236
225
|
return []string{"frontier-blocked: " + target + " — " + reason + "; preparation deferred, not approved"}, nil
|
|
@@ -242,8 +231,8 @@ func frontierPreparationAdmission(root, source string, nodes map[string]*frontie
|
|
|
242
231
|
if frontierTerminal(child) {
|
|
243
232
|
return nil, nil
|
|
244
233
|
}
|
|
245
|
-
if
|
|
246
|
-
return nil,
|
|
234
|
+
if frontierPendingPreparation(successorDispatch) {
|
|
235
|
+
return nil, nil
|
|
247
236
|
}
|
|
248
237
|
ready, notices, err := WorkspaceReady(filepath.Dir(root), source, target)
|
|
249
238
|
if err != nil {
|
|
@@ -264,7 +253,7 @@ func frontierPreparationAdmission(root, source string, nodes map[string]*frontie
|
|
|
264
253
|
return nil, nil
|
|
265
254
|
}
|
|
266
255
|
}
|
|
267
|
-
return nil, fmt.Errorf("successor %s preparation has not actually started; invoke /rite-spec %s or its native Plan/Vet cursor and retain launched planning/review handles before
|
|
256
|
+
return nil, fmt.Errorf("successor %s preparation has not actually started; invoke /rite-spec %s or its native Plan/Vet cursor and retain launched planning/review handles before continuing forward phases of %s; alternatively retain frontier-blocked: %s — <specific constraint> in evidence.md", target, frontierQuote(target), source, target)
|
|
268
257
|
}
|
|
269
258
|
|
|
270
259
|
func frontierNormalizePaths(paths []string) error {
|
|
@@ -321,7 +310,7 @@ func frontierLeaseReserved(lease *parallel.Lease, controller, source string, ids
|
|
|
321
310
|
return len(ids) > 0
|
|
322
311
|
}
|
|
323
312
|
|
|
324
|
-
func frontierWriterRetention(root, source, controller, binding string, lease *parallel.Lease, nodes map[string]*frontierWorkspace, ordered, ids []string, current *dispatchFile) error {
|
|
313
|
+
func frontierWriterRetention(root, source, controller, binding string, lease *parallel.Lease, nodes map[string]*frontierWorkspace, ordered, ids []string, current *dispatchFile, checkPreparation bool) error {
|
|
325
314
|
var claims map[string]*claimRecord
|
|
326
315
|
for _, slug := range ordered {
|
|
327
316
|
if _, err := frontierChain(nodes, slug); err != nil {
|
|
@@ -340,6 +329,11 @@ func frontierWriterRetention(root, source, controller, binding string, lease *pa
|
|
|
340
329
|
if err != nil {
|
|
341
330
|
return err
|
|
342
331
|
}
|
|
332
|
+
if checkPreparation {
|
|
333
|
+
if _, err := frontierPreparationBindings(nodes[slug], dispatch); err != nil {
|
|
334
|
+
return err
|
|
335
|
+
}
|
|
336
|
+
}
|
|
343
337
|
}
|
|
344
338
|
for name, wave := range dispatch.Waves {
|
|
345
339
|
if wave.State == "complete" || wave.TerminalAt != "" {
|
|
@@ -475,6 +469,12 @@ func autocompleteWriterAdmission(root, source string, wave *dispatchWave, curren
|
|
|
475
469
|
if err != nil {
|
|
476
470
|
return "", nil, err
|
|
477
471
|
}
|
|
472
|
+
checkPreparation := capN > 1 && (lease == nil || lease.Capacity != 1)
|
|
473
|
+
if checkPreparation {
|
|
474
|
+
if _, err := frontierPreparationBindings(nodes[source], current); err != nil {
|
|
475
|
+
return "", nil, err
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
478
|
chain, err := frontierChain(nodes, source)
|
|
479
479
|
if err != nil {
|
|
480
480
|
return "", nil, err
|
|
@@ -498,9 +498,12 @@ func autocompleteWriterAdmission(root, source string, wave *dispatchWave, curren
|
|
|
498
498
|
if err != nil {
|
|
499
499
|
return "", nil, err
|
|
500
500
|
}
|
|
501
|
-
notices
|
|
502
|
-
if
|
|
503
|
-
|
|
501
|
+
var notices []string
|
|
502
|
+
if checkPreparation {
|
|
503
|
+
notices, err = frontierPreparationAdmission(root, source, nodes, mapping, problem, allow, records)
|
|
504
|
+
if err != nil {
|
|
505
|
+
return "", nil, err
|
|
506
|
+
}
|
|
504
507
|
}
|
|
505
508
|
local, withheld, err := WorkspaceReady(filepath.Dir(root), controller, source)
|
|
506
509
|
if err != nil {
|
|
@@ -531,7 +534,7 @@ func autocompleteWriterAdmission(root, source string, wave *dispatchWave, curren
|
|
|
531
534
|
if reserved {
|
|
532
535
|
binding = controller + "/" + lease.BatchID + "@" + lease.CreatedAt
|
|
533
536
|
}
|
|
534
|
-
if err := frontierWriterRetention(root, source, controller, binding, lease, nodes, ordered, ids, current); err != nil {
|
|
537
|
+
if err := frontierWriterRetention(root, source, controller, binding, lease, nodes, ordered, ids, current, checkPreparation); err != nil {
|
|
535
538
|
return "", nil, err
|
|
536
539
|
}
|
|
537
540
|
var candidates []parallel.SlicePaths
|
|
@@ -549,7 +552,7 @@ func autocompleteWriterAdmission(root, source string, wave *dispatchWave, curren
|
|
|
549
552
|
} else if hold != "" {
|
|
550
553
|
continue
|
|
551
554
|
}
|
|
552
|
-
if _, _, err := frontierLocal(root, controller, n, &candidates); err != nil {
|
|
555
|
+
if _, _, err := frontierLocal(root, controller, n, &candidates, nil); err != nil {
|
|
553
556
|
return "", nil, err
|
|
554
557
|
}
|
|
555
558
|
}
|
|
@@ -613,6 +616,18 @@ func autocompleteWriterAdmission(root, source string, wave *dispatchWave, curren
|
|
|
613
616
|
return binding, notices, nil
|
|
614
617
|
}
|
|
615
618
|
|
|
619
|
+
const frontierInheritedPreparationWait = "preparatory descendant; continue immediate-parent preparation before this native cursor"
|
|
620
|
+
|
|
621
|
+
func frontierSourcePreparationWait(source string, lines []string) bool {
|
|
622
|
+
wait := "frontier-workspace: " + source + " location=live action=wait reason=" + frontierInheritedPreparationWait
|
|
623
|
+
for _, line := range lines {
|
|
624
|
+
if line == wait {
|
|
625
|
+
return true
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
return false
|
|
629
|
+
}
|
|
630
|
+
|
|
616
631
|
func frontierGlobalPriority(source string, lines []string) bool {
|
|
617
632
|
controller := source
|
|
618
633
|
for _, line := range lines {
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
package lib
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"os"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"strings"
|
|
8
|
+
|
|
9
|
+
"github.com/devrites/devrites/internal/devritespaths"
|
|
10
|
+
"github.com/devrites/devrites/internal/parallel"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
type preparationPurpose struct {
|
|
14
|
+
parent string
|
|
15
|
+
research bool
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
func preparationPhase(phase string) bool {
|
|
19
|
+
switch phase {
|
|
20
|
+
case "spec", "clarify", "temper", "define", "plan", "vet":
|
|
21
|
+
return true
|
|
22
|
+
}
|
|
23
|
+
return false
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
func preparationRole(phase, role string) bool {
|
|
27
|
+
switch phase {
|
|
28
|
+
case "spec", "clarify", "temper":
|
|
29
|
+
return role == "evidence-scout" || role == "strategy-reviewer"
|
|
30
|
+
case "define", "plan":
|
|
31
|
+
return role == "plan-drafter" || role == "evidence-scout"
|
|
32
|
+
case "vet":
|
|
33
|
+
return role == "plan-reviewer" || role == "devex-reviewer" || role == "strategy-reviewer" || role == "code-reviewer"
|
|
34
|
+
}
|
|
35
|
+
return false
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
func (p preparationPurpose) validate(phase string, roles []string, context bool) error {
|
|
39
|
+
phase = strings.ToLower(phase)
|
|
40
|
+
if p.parent == "" && !p.research {
|
|
41
|
+
return nil
|
|
42
|
+
}
|
|
43
|
+
if p.parent != "" && p.research {
|
|
44
|
+
return fmt.Errorf("--prepare-for and --sequence-research are mutually exclusive")
|
|
45
|
+
}
|
|
46
|
+
if !preparationPhase(phase) {
|
|
47
|
+
return fmt.Errorf("preparation purpose requires a native preparation phase")
|
|
48
|
+
}
|
|
49
|
+
if len(roles) == 0 && (!context || p.research) {
|
|
50
|
+
return fmt.Errorf("preparation dispatch/research requires an allowed named role")
|
|
51
|
+
}
|
|
52
|
+
for _, role := range roles {
|
|
53
|
+
if p.research && role != "evidence-scout" || !p.research && !preparationRole(phase, roleBase(role)) {
|
|
54
|
+
return fmt.Errorf("role %q is not permitted for this preparation purpose", role)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return nil
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
func earlyPreparationPhase(phase string) bool {
|
|
61
|
+
phase = strings.ToLower(phase)
|
|
62
|
+
return phase == "temper" || phase == "define" || phase == "plan" || phase == "vet"
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Context and dispatch share admission; neither a bundle nor a purpose flag proves a native launch.
|
|
66
|
+
func preparationAdmission(root, source, phase string, purpose preparationPurpose, current *dispatchFile) ([]string, error) {
|
|
67
|
+
phase = strings.ToLower(phase)
|
|
68
|
+
if purpose.research {
|
|
69
|
+
if current != nil {
|
|
70
|
+
return nil, nil
|
|
71
|
+
}
|
|
72
|
+
dir, err := devritespaths.ExistingFeatureDirChecked(root, source)
|
|
73
|
+
if err != nil {
|
|
74
|
+
return nil, err
|
|
75
|
+
}
|
|
76
|
+
raw, err := readBoundedRegularFile(filepath.Join(dir, "dispatch.json"), workspaceArtifactLimit)
|
|
77
|
+
if os.IsNotExist(err) {
|
|
78
|
+
return nil, nil
|
|
79
|
+
}
|
|
80
|
+
if err != nil {
|
|
81
|
+
return nil, err
|
|
82
|
+
}
|
|
83
|
+
_, err = decodeDispatch(raw)
|
|
84
|
+
return nil, err
|
|
85
|
+
}
|
|
86
|
+
if purpose.parent == "" && !earlyPreparationPhase(phase) {
|
|
87
|
+
return nil, nil
|
|
88
|
+
}
|
|
89
|
+
mode, err := autocompleteMode(root)
|
|
90
|
+
if err != nil {
|
|
91
|
+
return nil, err
|
|
92
|
+
}
|
|
93
|
+
if purpose.parent == "" && !mode {
|
|
94
|
+
return nil, nil
|
|
95
|
+
}
|
|
96
|
+
allow, capacity, err := frontierPolicy(root)
|
|
97
|
+
if err != nil {
|
|
98
|
+
return nil, err
|
|
99
|
+
}
|
|
100
|
+
if mode {
|
|
101
|
+
_, lease, leaseErr := parallel.ControllerLease(filepath.Dir(root))
|
|
102
|
+
if leaseErr != nil {
|
|
103
|
+
return nil, leaseErr
|
|
104
|
+
}
|
|
105
|
+
if lease != nil && lease.Capacity > 0 && lease.Capacity < capacity {
|
|
106
|
+
capacity = lease.Capacity
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if purpose.parent == "" && capacity <= 1 {
|
|
110
|
+
return nil, nil
|
|
111
|
+
}
|
|
112
|
+
nodes, err := frontierScan(root)
|
|
113
|
+
if err != nil {
|
|
114
|
+
return nil, err
|
|
115
|
+
}
|
|
116
|
+
chain, err := frontierChain(nodes, source)
|
|
117
|
+
if err != nil {
|
|
118
|
+
return nil, err
|
|
119
|
+
}
|
|
120
|
+
if mode && capacity > 1 {
|
|
121
|
+
if _, err := frontierPreparationBindings(nodes[source], current); err != nil {
|
|
122
|
+
return nil, err
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
ordered, _ := frontierRelated(nodes, chain)
|
|
126
|
+
mapping, _, problem, invalid := frontierRoadmap(nodes, ordered)
|
|
127
|
+
if invalid {
|
|
128
|
+
return nil, fmt.Errorf("invalid canonical roadmap: %s", problem)
|
|
129
|
+
}
|
|
130
|
+
if purpose.parent != "" {
|
|
131
|
+
parent, child := nodes[purpose.parent], nodes[source]
|
|
132
|
+
if parent == nil || child == nil || parent == child || child.parent != parent.slug || !parent.hasPosition || !child.hasPosition || child.position != parent.position+1 {
|
|
133
|
+
return nil, fmt.Errorf("--prepare-for requires an existing bound immediate parent with adjacent sequence positions")
|
|
134
|
+
}
|
|
135
|
+
if _, err := frontierChain(nodes, parent.slug); err != nil {
|
|
136
|
+
return nil, err
|
|
137
|
+
}
|
|
138
|
+
target, err := frontierSuccessor(parent, nodes, mapping)
|
|
139
|
+
if problem != "" || err != nil || target != source {
|
|
140
|
+
return nil, fmt.Errorf("--prepare-for parent/successor authority does not agree: %s %v", problem, err)
|
|
141
|
+
}
|
|
142
|
+
return nil, nil
|
|
143
|
+
}
|
|
144
|
+
records, err := frontierEvidence(root, source)
|
|
145
|
+
if err != nil {
|
|
146
|
+
return nil, err
|
|
147
|
+
}
|
|
148
|
+
return frontierPreparationAdmission(root, source, nodes, mapping, problem, allow && capacity > 1, records)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Validate the whole retained ledger before any current-scope classification or credit.
|
|
152
|
+
// A decoded account may be supplied by dispatch admission; recovery uses decode alone.
|
|
153
|
+
func frontierPreparationBindings(n *frontierWorkspace, account *dispatchFile) (*dispatchFile, error) {
|
|
154
|
+
if n == nil {
|
|
155
|
+
return nil, fmt.Errorf("preparation binding source is not an existing sequence workspace")
|
|
156
|
+
}
|
|
157
|
+
if account == nil {
|
|
158
|
+
raw, err := readBoundedRegularFile(filepath.Join(n.dir, "dispatch.json"), workspaceArtifactLimit)
|
|
159
|
+
if os.IsNotExist(err) {
|
|
160
|
+
return nil, nil
|
|
161
|
+
}
|
|
162
|
+
if err != nil {
|
|
163
|
+
return nil, err
|
|
164
|
+
}
|
|
165
|
+
account, err = decodeDispatch(raw)
|
|
166
|
+
if err != nil {
|
|
167
|
+
return nil, err
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
for _, wave := range account.Waves {
|
|
171
|
+
if wave.PreparationFor != "" && wave.PreparationFor != n.parent && wave.State != "complete" && (wave.State != "abandoned" || wave.TerminalAt == "") {
|
|
172
|
+
return nil, fmt.Errorf("workspace %s preparation account does not match its immediate parent", n.slug)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return account, nil
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
func frontierPreparatoryOnly(root string, n *frontierWorkspace, nodes map[string]*frontierWorkspace, mapping map[int]string, problem string) (bool, *dispatchFile, error) {
|
|
179
|
+
account, err := frontierPreparationBindings(n, nil)
|
|
180
|
+
if err != nil {
|
|
181
|
+
return false, nil, err
|
|
182
|
+
}
|
|
183
|
+
parent := nodes[n.parent]
|
|
184
|
+
if parent == nil || frontierTerminal(parent) || frontierTerminal(n) || problem != "" || !parent.hasPosition || !n.hasPosition || n.position != parent.position+1 {
|
|
185
|
+
return false, account, nil
|
|
186
|
+
}
|
|
187
|
+
if _, err := frontierChain(nodes, n.slug); err != nil {
|
|
188
|
+
return false, account, nil
|
|
189
|
+
}
|
|
190
|
+
target, err := frontierSuccessor(parent, nodes, mapping)
|
|
191
|
+
if err != nil || target != n.slug {
|
|
192
|
+
return false, account, nil
|
|
193
|
+
}
|
|
194
|
+
active, err := readBoundedRegularFile(filepath.Join(root, "ACTIVE"), workspaceArtifactLimit)
|
|
195
|
+
if err != nil && !os.IsNotExist(err) {
|
|
196
|
+
return false, account, err
|
|
197
|
+
}
|
|
198
|
+
if strings.TrimSpace(string(active)) == n.slug {
|
|
199
|
+
return false, account, nil
|
|
200
|
+
}
|
|
201
|
+
if account == nil {
|
|
202
|
+
return true, nil, nil
|
|
203
|
+
}
|
|
204
|
+
for _, wave := range account.Waves {
|
|
205
|
+
if wave.PreparationFor == "" && !wave.SequenceResearch && (earlyPreparationPhase(wave.Phase) || isBuildWrightWave(wave)) {
|
|
206
|
+
return false, account, nil
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return true, account, nil
|
|
210
|
+
}
|
|
@@ -91,6 +91,9 @@ func RunNext(root string, args []string, stdout, stderr io.Writer) int {
|
|
|
91
91
|
fmt.Fprintln(stdout, "next-command: /rite-autocomplete")
|
|
92
92
|
return exit
|
|
93
93
|
}
|
|
94
|
+
if mode && frontierSourcePreparationWait(slug, lines) {
|
|
95
|
+
return exit
|
|
96
|
+
}
|
|
94
97
|
}
|
|
95
98
|
if report.Phase == state.PhaseBuild {
|
|
96
99
|
printPlanBatch(stdout, root, slug)
|
|
@@ -118,11 +121,11 @@ func RunNext(root string, args []string, stdout, stderr io.Writer) int {
|
|
|
118
121
|
}
|
|
119
122
|
missing = report.MissingFiles
|
|
120
123
|
} else {
|
|
121
|
-
|
|
124
|
+
missing = missingArtifacts(featureDir, policy.RequiredArtifacts)
|
|
125
|
+
if policy.Target == state.PhaseClarify && openQuestions == 0 && len(missing) == 0 {
|
|
122
126
|
skips = append(skips, "clarify (0 open questions)")
|
|
123
127
|
continue
|
|
124
128
|
}
|
|
125
|
-
missing = missingArtifacts(featureDir, policy.RequiredArtifacts)
|
|
126
129
|
// A phase that adds no required artifact over the previous one
|
|
127
130
|
// (build after vet, polish after prove) cannot be proven done by
|
|
128
131
|
// artifact presence, so it is never skipped. Converge is the
|
|
@@ -59,6 +59,18 @@ var (
|
|
|
59
59
|
regressH2Re = regexp.MustCompile(`(?m)^## `)
|
|
60
60
|
)
|
|
61
61
|
|
|
62
|
+
// ledgerOwner returns the slice digits a checked state.md item owns: only its
|
|
63
|
+
// first slice token (documented `SLICE-003` or `Slice 3`). Later tokens on the
|
|
64
|
+
// same line are context — a blocked dependent, an INV reference, a NOT_RUN
|
|
65
|
+
// note — and do not make those slices built.
|
|
66
|
+
func ledgerOwner(item string) (string, bool) {
|
|
67
|
+
m := regressSliceIDRe.FindStringSubmatch(item)
|
|
68
|
+
if m == nil {
|
|
69
|
+
return "", false
|
|
70
|
+
}
|
|
71
|
+
return m[1], true
|
|
72
|
+
}
|
|
73
|
+
|
|
62
74
|
// progressFingerprint is the structural state one baseline records. Sets are
|
|
63
75
|
// sorted for deterministic output and diffing.
|
|
64
76
|
type progressFingerprint struct {
|
|
@@ -180,13 +192,15 @@ func fingerprintWorkspace(root, slug, workspace string) (progressFingerprint, er
|
|
|
180
192
|
note("state.md", err)
|
|
181
193
|
if err == nil {
|
|
182
194
|
for _, item := range regressBuiltRe.FindAllString(masked, -1) {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
195
|
+
digits, ok := ledgerOwner(item)
|
|
196
|
+
if !ok {
|
|
197
|
+
continue
|
|
198
|
+
}
|
|
199
|
+
// `SLICE-001` and `Slice 1` name the same slice.
|
|
200
|
+
n, _ := strconv.Atoi(digits)
|
|
201
|
+
id := fmt.Sprintf("SLICE-%03d", n)
|
|
202
|
+
if !slices.Contains(fp.DoneSlices, id) {
|
|
203
|
+
fp.DoneSlices = append(fp.DoneSlices, id)
|
|
190
204
|
}
|
|
191
205
|
}
|
|
192
206
|
}
|
|
@@ -235,8 +235,8 @@ func builtSlicesFrom(read func(string) ([]byte, error)) (map[string]bool, error)
|
|
|
235
235
|
if !builtWordRe.MatchString(item) {
|
|
236
236
|
continue
|
|
237
237
|
}
|
|
238
|
-
|
|
239
|
-
built[strings.TrimLeft(
|
|
238
|
+
if digits, ok := ledgerOwner(item); ok {
|
|
239
|
+
built[strings.TrimLeft(digits, "0")] = true
|
|
240
240
|
}
|
|
241
241
|
}
|
|
242
242
|
extra, err := evidenceBuiltFrom(read)
|
|
@@ -176,5 +176,14 @@ func triggerApplyCommand(opts contextOpts, suggested []string) string {
|
|
|
176
176
|
}
|
|
177
177
|
}
|
|
178
178
|
parts = append(parts, "--trigger", strings.Join(suggested, ","))
|
|
179
|
+
if opts.purpose.parent != "" {
|
|
180
|
+
parts = append(parts, "--prepare-for", frontierQuote(opts.purpose.parent))
|
|
181
|
+
}
|
|
182
|
+
if opts.purpose.research {
|
|
183
|
+
parts = append(parts, "--sequence-research")
|
|
184
|
+
}
|
|
185
|
+
if opts.skillsRoot != "" {
|
|
186
|
+
parts = append(parts, "--skills-root", frontierQuote(opts.skillsRoot))
|
|
187
|
+
}
|
|
179
188
|
return strings.Join(parts, " ")
|
|
180
189
|
}
|
|
@@ -39,6 +39,8 @@ lifecycle state from chat or optional `README.md`.
|
|
|
39
39
|
completion states. The host renders the response normally.
|
|
40
40
|
- [`reference/parallel-workspaces.md`](reference/parallel-workspaces.md): explicit
|
|
41
41
|
phase targets, one isolated global controller, and source-owned proof.
|
|
42
|
+
- [Autocomplete loop](../rite-autocomplete/reference/loop.md#early-preparation-admission):
|
|
43
|
+
controller-only early preparation, bounded successor routing and normalization research.
|
|
42
44
|
- [`reference/visual-playbooks/index.md`](reference/visual-playbooks/index.md): progressive
|
|
43
45
|
visual HTML playbook router (load matching ids only; dual-read outline).
|
|
44
46
|
|
|
@@ -101,16 +101,18 @@ return cursors and native handles: one controller, not another phase or queue.
|
|
|
101
101
|
Normalize unresolved legacy sequence authority through its owning decision/
|
|
102
102
|
phase. Positive budget, ambiguous prose or path disjointness grants nothing.
|
|
103
103
|
A current invalid canonical map is an error, never a fallback to history.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
104
|
+
At controller cap>1, resolve sequence authority and start actual immediate-
|
|
105
|
+
successor preparation before ordinary local Temper/Define/Plan/Vet context or
|
|
106
|
+
dispatch open, and retain the full Build gate before claims/rounds/history.
|
|
107
|
+
Follow [early preparation](../../rite-autocomplete/reference/loop.md#early-preparation-admission)
|
|
108
|
+
for native launch/start, internal markers and bounded preparatory-only routing.
|
|
109
|
+
Missing targets need native Spec → Clarify → Temper → Define/Plan → affected
|
|
110
|
+
Vet; existing targets need current completed/readiness state, registered pending
|
|
111
|
+
native preparation, or an existing human/dependency hold. Completed live/
|
|
112
|
+
archived successors need no reopening; valid finite tails discharge the
|
|
113
|
+
obligation. Placeholders, reports and unopened waves prove nothing.
|
|
114
|
+
Opt-out, zero opening budget or source human/question holds prevent new
|
|
115
|
+
openings, not existing child preparation or independent work.
|
|
114
116
|
If preparation cannot start, retain `frontier-blocked: <successor> — <constraint>`
|
|
115
117
|
in source `evidence.md`. `frontier-blocked: sequence — <constraint>` may defer
|
|
116
118
|
unresolved historical normalization for valid independent local work. Each row
|
|
@@ -115,11 +115,13 @@ next sequence continuation through Vet in a second session, then stop.
|
|
|
115
115
|
Temper → Define → Vet → Build → Prove → Polish → Review → Seal. Durable files
|
|
116
116
|
carry state. Apply the mutable post-vet budget before Build. Consume fresh scheduling
|
|
117
117
|
emitted by recorded `dispatch return`; rerun frontier after further changes
|
|
118
|
-
and before waits.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
118
|
+
and before waits. Before ordinary local Temper/Define/Plan/Vet context/dispatch,
|
|
119
|
+
resolve sequence authority and start actual immediate-successor preparation
|
|
120
|
+
under [early admission](reference/loop.md#early-preparation-admission),
|
|
121
|
+
or retain the global pass's concrete deferral; keep the full Build gate.
|
|
122
|
+
Derive initial preparatory-only routing before any marked wave; do not recurse
|
|
123
|
+
through the roadmap. Preserve local cursors, protected validation windows and
|
|
124
|
+
every admission/isolation gate; pass Build the resolved global cap.
|
|
123
125
|
**Completion:** Seal GO or a durable valid stop precedes later phases.
|
|
124
126
|
5. **Apply stops.** At every gate use
|
|
125
127
|
[stop-conditions.md](reference/stop-conditions.md). A `blocked` label alone is not a stop condition:
|