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.
Files changed (44) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +1 -1
  3. package/docs/adr/0040-early-preparation-admission.md +76 -0
  4. package/docs/adr/README.md +1 -0
  5. package/docs/engine/commands.md +48 -6
  6. package/engine/internal/lib/context.go +50 -2
  7. package/engine/internal/lib/continuation.go +2 -0
  8. package/engine/internal/lib/dispatch.go +75 -20
  9. package/engine/internal/lib/frontier.go +40 -16
  10. package/engine/internal/lib/frontier_controller.go +53 -38
  11. package/engine/internal/lib/frontier_preparation.go +210 -0
  12. package/engine/internal/lib/next.go +5 -2
  13. package/engine/internal/lib/regression.go +21 -7
  14. package/engine/internal/lib/taskgraph.go +2 -2
  15. package/engine/internal/lib/triggers.go +9 -0
  16. package/pack/.claude/skills/devrites-lib/SKILL.md +2 -0
  17. package/pack/.claude/skills/devrites-lib/reference/parallel-workspaces.md +12 -10
  18. package/pack/.claude/skills/rite-autocomplete/SKILL.md +7 -5
  19. package/pack/.claude/skills/rite-autocomplete/reference/loop.md +55 -42
  20. package/pack/generated/claude/skills/devrites-lib/SKILL.md +2 -0
  21. package/pack/generated/claude/skills/devrites-lib/reference/parallel-workspaces.md +12 -10
  22. package/pack/generated/claude/skills/rite-autocomplete/SKILL.md +7 -5
  23. package/pack/generated/claude/skills/rite-autocomplete/reference/loop.md +55 -42
  24. package/pack/generated/codex/skills/devrites-lib/SKILL.md +2 -0
  25. package/pack/generated/codex/skills/devrites-lib/reference/parallel-workspaces.md +12 -10
  26. package/pack/generated/codex/skills/rite-autocomplete/SKILL.md +7 -5
  27. package/pack/generated/codex/skills/rite-autocomplete/reference/loop.md +55 -42
  28. package/pack/generated/devin/skills/devrites-lib/SKILL.md +2 -0
  29. package/pack/generated/devin/skills/devrites-lib/reference/parallel-workspaces.md +12 -10
  30. package/pack/generated/devin/skills/rite-autocomplete/SKILL.md +7 -5
  31. package/pack/generated/devin/skills/rite-autocomplete/reference/loop.md +55 -42
  32. package/pack/generated/dsh/skills/devrites-lib/SKILL.md +2 -0
  33. package/pack/generated/dsh/skills/devrites-lib/reference/parallel-workspaces.md +12 -10
  34. package/pack/generated/dsh/skills/rite-autocomplete/SKILL.md +7 -5
  35. package/pack/generated/dsh/skills/rite-autocomplete/reference/loop.md +55 -42
  36. package/pack/generated/omp/skills/devrites-lib/SKILL.md +2 -0
  37. package/pack/generated/omp/skills/devrites-lib/reference/parallel-workspaces.md +12 -10
  38. package/pack/generated/omp/skills/rite-autocomplete/SKILL.md +7 -5
  39. package/pack/generated/omp/skills/rite-autocomplete/reference/loop.md +55 -42
  40. package/pack/generated/pi/skills/devrites-lib/SKILL.md +2 -0
  41. package/pack/generated/pi/skills/devrites-lib/reference/parallel-workspaces.md +12 -10
  42. package/pack/generated/pi/skills/rite-autocomplete/SKILL.md +7 -5
  43. package/pack/generated/pi/skills/rite-autocomplete/reference/loop.md +55 -42
  44. 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(n *frontierWorkspace) (bool, error) {
138
- raw, err := readBoundedRegularFile(filepath.Join(n.dir, "dispatch.json"), workspaceArtifactLimit)
139
- if os.IsNotExist(err) {
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
- switch strings.ToLower(wave.Phase) {
159
- case "spec", "clarify", "temper":
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, nil
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 !allow || n.hasRemaining && n.remaining == 0 {
203
- return nil, 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 writer admission: %s; read sequence artifacts, record current roadmap through its owning decision, or retain frontier-blocked: sequence — <specific constraint>", source, reason)
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 pending, err := frontierPendingPreparation(child); err != nil || pending {
246
- return nil, err
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 /rite-build %s; alternatively retain frontier-blocked: %s — <specific constraint> in evidence.md", target, frontierQuote(target), source, target)
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, err := frontierPreparationAdmission(root, source, nodes, mapping, problem, allow && capN > 1, records)
502
- if err != nil {
503
- return "", nil, err
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
- if policy.Target == state.PhaseClarify && openQuestions == 0 {
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
- for _, m := range regressSliceIDRe.FindAllStringSubmatch(item, -1) {
184
- // `SLICE-001` and `Slice 1` name the same slice.
185
- n, _ := strconv.Atoi(m[1])
186
- id := fmt.Sprintf("SLICE-%03d", n)
187
- if !slices.Contains(fp.DoneSlices, id) {
188
- fp.DoneSlices = append(fp.DoneSlices, id)
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
- for _, m := range regressSliceIDRe.FindAllStringSubmatch(item, -1) {
239
- built[strings.TrimLeft(m[1], "0")] = true
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
- For controller mode at cap>1, before a new Build-wright wave writes claims,
105
- rounds or history, start preparation of that source's immediate recorded
106
- successor. Missing targets need native Spec → Clarify → Temper → Define/Plan →
107
- affected Vet; existing targets need completed native state, actual pending
108
- planning/review handles, current Build-ready binding, or a human/dependency hold.
109
- Completed live/archived successors need no reopening; retained actors still bind.
110
- A placeholder, prose claim, unopened wave or unrelated reviewer is insufficient.
111
- Pipeline one successor per admitted source; preparing the whole roadmap first
112
- is unnecessary. Opt-out, zero opening budget or source human/question holds
113
- prevent new openings, not already-created child planning or independent work.
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. Start actual immediate-successor preparation before local
119
- writer admission, or disclose the narrow deferral in the shared global pass.
120
- A placeholder or report is not preparation; the entire sequence need not be
121
- prepared at once. Preserve local cursors and protected validation windows;
122
- pass Build the resolved global cap and retain every admission/isolation gate.
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: