devrites 5.10.0 → 5.10.2

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 (72) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +5 -2
  3. package/docs/architecture.md +8 -4
  4. package/docs/cli.md +4 -1
  5. package/docs/command-map.md +3 -2
  6. package/docs/engine/commands.md +7 -5
  7. package/docs/orchestration.md +5 -2
  8. package/docs/skills.md +1 -1
  9. package/docs/usage.md +5 -3
  10. package/engine/commands.go +3 -0
  11. package/engine/help.go +190 -0
  12. package/engine/help_test.go +98 -0
  13. package/engine/internal/parallel/cli.go +90 -32
  14. package/engine/internal/parallel/cli_test.go +32 -0
  15. package/engine/main.go +15 -8
  16. package/engine/root_routing_test.go +4 -0
  17. package/pack/.claude/skills/devrites-lib/reference/standards/afk-hitl.md +27 -9
  18. package/pack/.claude/skills/rite-autocomplete/SKILL.md +20 -9
  19. package/pack/.claude/skills/rite-autocomplete/reference/decision-policy.md +5 -2
  20. package/pack/.claude/skills/rite-autocomplete/reference/loop.md +25 -14
  21. package/pack/.claude/skills/rite-autocomplete/reference/stop-conditions.md +10 -3
  22. package/pack/.claude/skills/rite-build/SKILL.md +3 -1
  23. package/pack/.claude/skills/rite-build/reference/afk-discipline.md +4 -3
  24. package/pack/.claude/skills/rite-build/reference/parallel-batch.md +3 -1
  25. package/pack/.claude/skills/rite-define/reference/gates.md +5 -0
  26. package/pack/.claude/skills/rite-define/reference/plan-template.md +1 -1
  27. package/pack/.claude/skills/rite-resolve/SKILL.md +5 -2
  28. package/pack/generated/claude/skills/devrites-lib/reference/standards/afk-hitl.md +27 -9
  29. package/pack/generated/claude/skills/rite-autocomplete/SKILL.md +20 -9
  30. package/pack/generated/claude/skills/rite-autocomplete/reference/decision-policy.md +5 -2
  31. package/pack/generated/claude/skills/rite-autocomplete/reference/loop.md +25 -14
  32. package/pack/generated/claude/skills/rite-autocomplete/reference/stop-conditions.md +10 -3
  33. package/pack/generated/claude/skills/rite-build/SKILL.md +3 -1
  34. package/pack/generated/claude/skills/rite-build/reference/afk-discipline.md +4 -3
  35. package/pack/generated/claude/skills/rite-build/reference/parallel-batch.md +3 -1
  36. package/pack/generated/claude/skills/rite-define/reference/gates.md +5 -0
  37. package/pack/generated/claude/skills/rite-define/reference/plan-template.md +1 -1
  38. package/pack/generated/claude/skills/rite-resolve/SKILL.md +5 -2
  39. package/pack/generated/codex/skills/devrites-lib/reference/standards/afk-hitl.md +27 -9
  40. package/pack/generated/codex/skills/rite-autocomplete/SKILL.md +20 -9
  41. package/pack/generated/codex/skills/rite-autocomplete/reference/decision-policy.md +5 -2
  42. package/pack/generated/codex/skills/rite-autocomplete/reference/loop.md +25 -14
  43. package/pack/generated/codex/skills/rite-autocomplete/reference/stop-conditions.md +10 -3
  44. package/pack/generated/codex/skills/rite-build/SKILL.md +3 -1
  45. package/pack/generated/codex/skills/rite-build/reference/afk-discipline.md +4 -3
  46. package/pack/generated/codex/skills/rite-build/reference/parallel-batch.md +3 -1
  47. package/pack/generated/codex/skills/rite-define/reference/gates.md +5 -0
  48. package/pack/generated/codex/skills/rite-define/reference/plan-template.md +1 -1
  49. package/pack/generated/codex/skills/rite-resolve/SKILL.md +5 -2
  50. package/pack/generated/omp/skills/devrites-lib/reference/standards/afk-hitl.md +27 -9
  51. package/pack/generated/omp/skills/rite-autocomplete/SKILL.md +20 -9
  52. package/pack/generated/omp/skills/rite-autocomplete/reference/decision-policy.md +5 -2
  53. package/pack/generated/omp/skills/rite-autocomplete/reference/loop.md +25 -14
  54. package/pack/generated/omp/skills/rite-autocomplete/reference/stop-conditions.md +10 -3
  55. package/pack/generated/omp/skills/rite-build/SKILL.md +3 -1
  56. package/pack/generated/omp/skills/rite-build/reference/afk-discipline.md +4 -3
  57. package/pack/generated/omp/skills/rite-build/reference/parallel-batch.md +3 -1
  58. package/pack/generated/omp/skills/rite-define/reference/gates.md +5 -0
  59. package/pack/generated/omp/skills/rite-define/reference/plan-template.md +1 -1
  60. package/pack/generated/omp/skills/rite-resolve/SKILL.md +5 -2
  61. package/pack/generated/pi/skills/devrites-lib/reference/standards/afk-hitl.md +27 -9
  62. package/pack/generated/pi/skills/rite-autocomplete/SKILL.md +20 -9
  63. package/pack/generated/pi/skills/rite-autocomplete/reference/decision-policy.md +5 -2
  64. package/pack/generated/pi/skills/rite-autocomplete/reference/loop.md +25 -14
  65. package/pack/generated/pi/skills/rite-autocomplete/reference/stop-conditions.md +10 -3
  66. package/pack/generated/pi/skills/rite-build/SKILL.md +3 -1
  67. package/pack/generated/pi/skills/rite-build/reference/afk-discipline.md +4 -3
  68. package/pack/generated/pi/skills/rite-build/reference/parallel-batch.md +3 -1
  69. package/pack/generated/pi/skills/rite-define/reference/gates.md +5 -0
  70. package/pack/generated/pi/skills/rite-define/reference/plan-template.md +1 -1
  71. package/pack/generated/pi/skills/rite-resolve/SKILL.md +5 -2
  72. package/package.json +1 -1
@@ -37,13 +37,13 @@ func runPathDisjoint(args []string, stdin io.Reader, stdout, stderr io.Writer) i
37
37
  switch args[i] {
38
38
  case "--root":
39
39
  if i+1 >= len(args) {
40
- fmt.Fprintln(stderr, "usage: check path-disjoint [--root <dir>] [<json-file>|-]")
40
+ fmt.Fprintln(stderr, usagePathDisjoint)
41
41
  return ExitUsage
42
42
  }
43
43
  i++
44
44
  root = args[i]
45
- case "-h", "--help":
46
- fmt.Fprintln(stdout, "usage: check path-disjoint [--root <dir>] [<json-file>|-]")
45
+ case "-h", "-help", "--help":
46
+ fmt.Fprintln(stdout, usagePathDisjoint)
47
47
  return ExitOK
48
48
  default:
49
49
  if args[i] != "-" && strings.HasPrefix(args[i], "-") {
@@ -73,7 +73,6 @@ func runPathDisjoint(args []string, stdin io.Reader, stdout, stderr io.Writer) i
73
73
  }
74
74
 
75
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
76
  root := ""
78
77
  jsonPath := "-"
79
78
  capN := 0
@@ -82,14 +81,14 @@ func cmdSelect(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
82
81
  switch args[i] {
83
82
  case "--root":
84
83
  if i+1 >= len(args) {
85
- fmt.Fprintln(stderr, usage)
84
+ fmt.Fprintln(stderr, usageSelect)
86
85
  return ExitUsage
87
86
  }
88
87
  i++
89
88
  root = args[i]
90
89
  case "--cap":
91
90
  if i+1 >= len(args) {
92
- fmt.Fprintln(stderr, usage)
91
+ fmt.Fprintln(stderr, usageSelect)
93
92
  return ExitUsage
94
93
  }
95
94
  i++
@@ -100,8 +99,8 @@ func cmdSelect(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
100
99
  }
101
100
  capN = n
102
101
  sawCap = true
103
- case "-h", "--help":
104
- fmt.Fprintln(stdout, usage)
102
+ case "-h", "-help", "--help":
103
+ fmt.Fprintln(stdout, usageSelect)
105
104
  return ExitOK
106
105
  default:
107
106
  if args[i] != "-" && strings.HasPrefix(args[i], "-") {
@@ -112,7 +111,7 @@ func cmdSelect(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
112
111
  }
113
112
  }
114
113
  if !sawCap {
115
- fmt.Fprintln(stderr, usage)
114
+ fmt.Fprintln(stderr, usageSelect)
116
115
  return ExitUsage
117
116
  }
118
117
  data, err := readJSONInput(jsonPath, stdin)
@@ -166,7 +165,7 @@ func runParallel(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
166
165
  return runPathDisjoint(rest, stdin, stdout, stderr)
167
166
  case "select":
168
167
  return cmdSelect(rest, stdin, stdout, stderr)
169
- case "-h", "--help", "help":
168
+ case "-h", "-help", "--help", "help":
170
169
  fmt.Fprintln(stdout, parallelUsage())
171
170
  return ExitOK
172
171
  default:
@@ -175,6 +174,20 @@ func runParallel(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
175
174
  }
176
175
  }
177
176
 
177
+ const (
178
+ usageCreate = "usage: parallel create --root --slug --batch --base --json"
179
+ usageRecordGreen = "usage: parallel record-green --root --slug --slice --commit"
180
+ usageAbort = "usage: parallel abort --root --slug"
181
+ usageIntegrate = "usage: parallel integrate --root --slug [--apply-to-control]"
182
+ usageCleanup = "usage: parallel cleanup --root --slug [--force]"
183
+ usageStatus = "usage: parallel status --root --slug"
184
+ usageLeaseWrite = "usage: parallel lease-write --root --slug --json"
185
+ usageLeaseRead = "usage: parallel lease-read --root --slug [--field name]"
186
+ usageLeaseClear = "usage: parallel lease-clear --root --slug"
187
+ usagePathDisjoint = "usage: check path-disjoint [--root <dir>] [<json-file>|-]"
188
+ usageSelect = "usage: parallel select --cap <1-10> [--root <dir>] [<json-file>|-]"
189
+ )
190
+
178
191
  func parallelUsage() string {
179
192
  return strings.TrimSpace(`usage: parallel <subcommand> [options]
180
193
 
@@ -194,9 +207,40 @@ Subcommands:
194
207
  Exit codes: 0 ok, 2 usage, 3 blocked`)
195
208
  }
196
209
 
210
+ // CommandUsage is the usage text for `parallel <name>`. An empty or unknown
211
+ // name returns the family usage.
212
+ func CommandUsage(name string) string {
213
+ switch name {
214
+ case "create":
215
+ return usageCreate
216
+ case "record-green":
217
+ return usageRecordGreen
218
+ case "abort":
219
+ return usageAbort
220
+ case "integrate":
221
+ return usageIntegrate
222
+ case "cleanup":
223
+ return usageCleanup
224
+ case "status":
225
+ return usageStatus
226
+ case "lease-write", "write-lease":
227
+ return usageLeaseWrite
228
+ case "lease-read", "read-lease":
229
+ return usageLeaseRead
230
+ case "lease-clear", "clear-lease":
231
+ return usageLeaseClear
232
+ case "check-disjoint", "path-disjoint":
233
+ return usagePathDisjoint
234
+ case "select":
235
+ return usageSelect
236
+ default:
237
+ return parallelUsage()
238
+ }
239
+ }
240
+
197
241
  type flagSet struct {
198
242
  Root, Slug, Batch, Base, Slice, Commit, JSON, Field, Session string
199
- ApplyToControl, Force bool
243
+ ApplyToControl, Force, Help bool
200
244
  }
201
245
 
202
246
  func parseFlags(args []string, stderr io.Writer) (flagSet, []string, int) {
@@ -271,6 +315,8 @@ func parseFlags(args []string, stderr io.Writer) (flagSet, []string, int) {
271
315
  f.ApplyToControl = true
272
316
  case "--force":
273
317
  f.Force = true
318
+ case "-h", "-help", "--help":
319
+ f.Help = true
274
320
  default:
275
321
  // No parallel subcommand takes positional args; anything
276
322
  // unrecognized (typo'd flag, stray word) is a usage error, not
@@ -283,6 +329,18 @@ func parseFlags(args []string, stderr io.Writer) (flagSet, []string, int) {
283
329
  return f, nil, ExitOK
284
330
  }
285
331
 
332
+ func parseFlagsOrHelp(args []string, stdout, stderr io.Writer, usage string) (flagSet, int, bool) {
333
+ f, _, code := parseFlags(args, stderr)
334
+ if code != ExitOK {
335
+ return f, code, true
336
+ }
337
+ if f.Help {
338
+ fmt.Fprintln(stdout, usage)
339
+ return f, ExitOK, true
340
+ }
341
+ return f, ExitOK, false
342
+ }
343
+
286
344
  func requireRootSlug(f flagSet, stderr io.Writer) int {
287
345
  if f.Root == "" || f.Slug == "" {
288
346
  fmt.Fprintln(stderr, "parallel: --root and --slug are required")
@@ -303,8 +361,8 @@ func readJSONInput(path string, stdin io.Reader) ([]byte, error) {
303
361
  }
304
362
 
305
363
  func cmdCreate(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
306
- f, _, code := parseFlags(args, stderr)
307
- if code != ExitOK {
364
+ f, code, done := parseFlagsOrHelp(args, stdout, stderr, usageCreate)
365
+ if done {
308
366
  return code
309
367
  }
310
368
  if code := requireRootSlug(f, stderr); code != ExitOK {
@@ -314,7 +372,7 @@ func cmdCreate(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
314
372
  return code
315
373
  }
316
374
  if f.Batch == "" || f.Base == "" || f.JSON == "" {
317
- fmt.Fprintln(stderr, "usage: parallel create --root --slug --batch --base --json")
375
+ fmt.Fprintln(stderr, usageCreate)
318
376
  return ExitUsage
319
377
  }
320
378
  data, err := readJSONInput(f.JSON, stdin)
@@ -359,8 +417,8 @@ func rejectUnsupportedFlags(f flagSet, stderr io.Writer, force, applyToControl b
359
417
  }
360
418
 
361
419
  func cmdRecordGreen(args []string, stdout, stderr io.Writer) int {
362
- f, _, code := parseFlags(args, stderr)
363
- if code != ExitOK {
420
+ f, code, done := parseFlagsOrHelp(args, stdout, stderr, usageRecordGreen)
421
+ if done {
364
422
  return code
365
423
  }
366
424
  if code := requireRootSlug(f, stderr); code != ExitOK {
@@ -370,7 +428,7 @@ func cmdRecordGreen(args []string, stdout, stderr io.Writer) int {
370
428
  return code
371
429
  }
372
430
  if f.Slice == "" || f.Commit == "" {
373
- fmt.Fprintln(stderr, "usage: parallel record-green --root --slug --slice --commit")
431
+ fmt.Fprintln(stderr, usageRecordGreen)
374
432
  return ExitUsage
375
433
  }
376
434
  lease, err := RecordGreen(f.Root, f.Slug, f.Slice, f.Commit)
@@ -383,8 +441,8 @@ func cmdRecordGreen(args []string, stdout, stderr io.Writer) int {
383
441
  }
384
442
 
385
443
  func cmdAbort(args []string, stdout, stderr io.Writer) int {
386
- f, _, code := parseFlags(args, stderr)
387
- if code != ExitOK {
444
+ f, code, done := parseFlagsOrHelp(args, stdout, stderr, usageAbort)
445
+ if done {
388
446
  return code
389
447
  }
390
448
  if code := requireRootSlug(f, stderr); code != ExitOK {
@@ -403,8 +461,8 @@ func cmdAbort(args []string, stdout, stderr io.Writer) int {
403
461
  }
404
462
 
405
463
  func cmdIntegrate(args []string, stdout, stderr io.Writer) int {
406
- f, _, code := parseFlags(args, stderr)
407
- if code != ExitOK {
464
+ f, code, done := parseFlagsOrHelp(args, stdout, stderr, usageIntegrate)
465
+ if done {
408
466
  return code
409
467
  }
410
468
  if code := requireRootSlug(f, stderr); code != ExitOK {
@@ -427,8 +485,8 @@ func cmdIntegrate(args []string, stdout, stderr io.Writer) int {
427
485
  }
428
486
 
429
487
  func cmdCleanup(args []string, stdout, stderr io.Writer) int {
430
- f, _, code := parseFlags(args, stderr)
431
- if code != ExitOK {
488
+ f, code, done := parseFlagsOrHelp(args, stdout, stderr, usageCleanup)
489
+ if done {
432
490
  return code
433
491
  }
434
492
  if code := requireRootSlug(f, stderr); code != ExitOK {
@@ -454,8 +512,8 @@ func cmdCleanup(args []string, stdout, stderr io.Writer) int {
454
512
  }
455
513
 
456
514
  func cmdStatus(args []string, stdout, stderr io.Writer) int {
457
- f, _, code := parseFlags(args, stderr)
458
- if code != ExitOK {
515
+ f, code, done := parseFlagsOrHelp(args, stdout, stderr, usageStatus)
516
+ if done {
459
517
  return code
460
518
  }
461
519
  if code := requireRootSlug(f, stderr); code != ExitOK {
@@ -474,15 +532,15 @@ func cmdStatus(args []string, stdout, stderr io.Writer) int {
474
532
  }
475
533
 
476
534
  func cmdLeaseWrite(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
477
- f, _, code := parseFlags(args, stderr)
478
- if code != ExitOK {
535
+ f, code, done := parseFlagsOrHelp(args, stdout, stderr, usageLeaseWrite)
536
+ if done {
479
537
  return code
480
538
  }
481
539
  if code := requireRootSlug(f, stderr); code != ExitOK {
482
540
  return code
483
541
  }
484
542
  if f.JSON == "" {
485
- fmt.Fprintln(stderr, "usage: parallel lease-write --root --slug --json")
543
+ fmt.Fprintln(stderr, usageLeaseWrite)
486
544
  return ExitUsage
487
545
  }
488
546
  data, err := readJSONInput(f.JSON, stdin)
@@ -516,8 +574,8 @@ func cmdLeaseWrite(args []string, stdin io.Reader, stdout, stderr io.Writer) int
516
574
  }
517
575
 
518
576
  func cmdLeaseRead(args []string, stdout, stderr io.Writer) int {
519
- f, _, code := parseFlags(args, stderr)
520
- if code != ExitOK {
577
+ f, code, done := parseFlagsOrHelp(args, stdout, stderr, usageLeaseRead)
578
+ if done {
521
579
  return code
522
580
  }
523
581
  if code := requireRootSlug(f, stderr); code != ExitOK {
@@ -567,8 +625,8 @@ func cmdLeaseRead(args []string, stdout, stderr io.Writer) int {
567
625
  }
568
626
 
569
627
  func cmdLeaseClear(args []string, stdout, stderr io.Writer) int {
570
- f, _, code := parseFlags(args, stderr)
571
- if code != ExitOK {
628
+ f, code, done := parseFlagsOrHelp(args, stdout, stderr, usageLeaseClear)
629
+ if done {
572
630
  return code
573
631
  }
574
632
  if code := requireRootSlug(f, stderr); code != ExitOK {
@@ -0,0 +1,32 @@
1
+ package parallel
2
+
3
+ import (
4
+ "bytes"
5
+ "strings"
6
+ "testing"
7
+ )
8
+
9
+ func TestParallelCreateHelp(t *testing.T) {
10
+ var stdout, stderr bytes.Buffer
11
+ code := Run("parallel", []string{"create", "--help"}, strings.NewReader(""), &stdout, &stderr)
12
+ if code != ExitOK {
13
+ t.Fatalf("code=%d stderr=%q", code, stderr.String())
14
+ }
15
+ if stderr.Len() != 0 {
16
+ t.Fatalf("stderr=%q", stderr.String())
17
+ }
18
+ if got := stdout.String(); !strings.Contains(got, usageCreate) {
19
+ t.Fatalf("stdout=%q, want %q", got, usageCreate)
20
+ }
21
+ }
22
+
23
+ func TestParallelCreateUnknownArgumentStillErrors(t *testing.T) {
24
+ var stdout, stderr bytes.Buffer
25
+ code := Run("parallel", []string{"create", "--nope"}, strings.NewReader(""), &stdout, &stderr)
26
+ if code != ExitUsage {
27
+ t.Fatalf("code=%d, want %d", code, ExitUsage)
28
+ }
29
+ if !strings.Contains(stderr.String(), `unknown argument "--nope"`) {
30
+ t.Fatalf("stderr=%q", stderr.String())
31
+ }
32
+ }
package/engine/main.go CHANGED
@@ -71,6 +71,9 @@ func run(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
71
71
  fmt.Fprint(stderr, usage)
72
72
  return exitUsage
73
73
  }
74
+ if text, ok := commandHelp(args); ok {
75
+ return writeHelp(stdout, text)
76
+ }
74
77
  root, rootExit, err := resolveRootFor(args[0], args[1:])
75
78
  if err != nil {
76
79
  fmt.Fprintf(stderr, "devrites: root selection: %v\n", err)
@@ -113,7 +116,7 @@ func run(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
113
116
 
114
117
  func cmdCheck(root string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
115
118
  if len(args) == 0 {
116
- fmt.Fprintln(stderr, "usage: devrites-engine check <candidate|readiness|seal|path-disjoint|task-graph|skill-trust|indexes> ...")
119
+ fmt.Fprint(stderr, checkUsage)
117
120
  return exitUsage
118
121
  }
119
122
  sub, rest := args[0], args[1:]
@@ -138,7 +141,7 @@ func cmdCheck(root string, args []string, stdin io.Reader, stdout, stderr io.Wri
138
141
 
139
142
  func cmdCandidate(root string, args []string, stdout, stderr io.Writer) int {
140
143
  if len(args) != 1 {
141
- fmt.Fprintln(stderr, "usage: devrites-engine check candidate <slug>")
144
+ fmt.Fprintln(stderr, candidateUsage)
142
145
  return exitUsage
143
146
  }
144
147
  digest, files, err := lib.CandidateIdentity(root, args[0])
@@ -156,7 +159,7 @@ func cmdCandidate(root string, args []string, stdout, stderr io.Writer) int {
156
159
 
157
160
  func cmdState(root string, args []string, stdout, stderr io.Writer) int {
158
161
  if len(args) == 0 {
159
- fmt.Fprintln(stderr, "usage: devrites-engine state <resolve|merge-manifest|close> ...")
162
+ fmt.Fprint(stderr, stateUsage)
160
163
  return exitUsage
161
164
  }
162
165
  switch args[0] {
@@ -178,7 +181,11 @@ func cmdState(root string, args []string, stdout, stderr io.Writer) int {
178
181
  func cmdGate(root string, kind gate.Kind, args []string, stdout, stderr io.Writer) int {
179
182
  emitBinding := kind == gate.Readiness && len(args) == 2 && args[0] == "--emit-binding"
180
183
  if !emitBinding && len(args) != 1 {
181
- fmt.Fprintf(stderr, "usage: devrites-engine check %s <slug>\n", kind)
184
+ if kind == gate.Readiness {
185
+ fmt.Fprintln(stderr, readinessUsage)
186
+ } else {
187
+ fmt.Fprintln(stderr, sealUsage)
188
+ }
182
189
  return exitUsage
183
190
  }
184
191
  if emitBinding {
@@ -219,7 +226,7 @@ func cmdGate(root string, kind gate.Kind, args []string, stdout, stderr io.Write
219
226
 
220
227
  func cmdTaskGraph(root string, args []string, stdout, stderr io.Writer) int {
221
228
  if len(args) != 1 {
222
- fmt.Fprintln(stderr, "usage: devrites-engine check task-graph <slug>")
229
+ fmt.Fprintln(stderr, taskGraphUsage)
223
230
  return exitUsage
224
231
  }
225
232
  return lib.RunTaskGraphCheck(root, args[0], stdout, stderr)
@@ -227,7 +234,7 @@ func cmdTaskGraph(root string, args []string, stdout, stderr io.Writer) int {
227
234
 
228
235
  func cmdSkillTrust(args []string, stdout, stderr io.Writer) int {
229
236
  if len(args) != 1 {
230
- fmt.Fprintln(stderr, "usage: devrites-engine check skill-trust <path>")
237
+ fmt.Fprintln(stderr, skillTrustUsage)
231
238
  return exitUsage
232
239
  }
233
240
  return lib.RunSkillTrustCheck(args[0], stdout, stderr)
@@ -235,7 +242,7 @@ func cmdSkillTrust(args []string, stdout, stderr io.Writer) int {
235
242
 
236
243
  func cmdObserve(root string, args []string, stdout, stderr io.Writer) int {
237
244
  if len(args) == 0 {
238
- fmt.Fprintln(stderr, "usage: devrites-engine observe summary <slug> | observe slice <slug> <SLICE-ID>")
245
+ fmt.Fprint(stderr, observeUsage)
239
246
  return exitUsage
240
247
  }
241
248
  switch args[0] {
@@ -251,7 +258,7 @@ func cmdObserve(root string, args []string, stdout, stderr io.Writer) int {
251
258
  return lib.RunObserveSummary(root, slug, stdout, stderr)
252
259
  case "slice":
253
260
  if len(args) != 3 {
254
- fmt.Fprintln(stderr, "usage: devrites-engine observe slice <slug> <SLICE-ID>")
261
+ fmt.Fprintln(stderr, observeSliceUsage)
255
262
  return exitUsage
256
263
  }
257
264
  return lib.RunObserveSlice(root, args[1], args[2], stdout, stderr)
@@ -25,6 +25,10 @@ func TestRootModeForCoversReadAndWriteSurfaces(t *testing.T) {
25
25
  {name: "observe summary", command: "observe", args: []string{"summary"}, want: rootStrictUsage},
26
26
  {name: "observe slice", command: "observe", args: []string{"slice"}, want: rootStrictUsage},
27
27
  {name: "orient", command: "orient", want: rootStrictUsage},
28
+ {name: "orient help", command: "orient", args: []string{"--help"}, want: rootUnused},
29
+ {name: "check candidate help", command: "check", args: []string{"candidate", "--help"}, want: rootUnused},
30
+ {name: "state help flag", command: "state", args: []string{"--help"}, want: rootUnused},
31
+ {name: "migrate help", command: "migrate", args: []string{"--help"}, want: rootUnused},
28
32
  {name: "check indexes", command: "check", args: []string{"indexes"}, want: rootLenient},
29
33
  {name: "resolve", command: "state", args: []string{"resolve"}, want: rootStrict},
30
34
  {name: "close", command: "state", args: []string{"close"}, want: rootStrict},
@@ -56,14 +56,14 @@ max_minutes: 120 # wall-clock minutes in one host activation
56
56
  max_review_queue: 8 # unresolved review/gate items admitted before fan-out stops
57
57
  # max_tokens: 200000 # optional stricter host-observed token cap
58
58
  # max_cost_usd: 10 # optional stricter host-observed cost cap
59
- notify: "ntfy.sh/my-topic" # shell command; examples: rite-build/reference/afk-discipline.md
59
+ notify: "ntfy.sh/my-topic" # shell command; examples: .claude/skills/rite-build/reference/afk-discipline.md
60
60
  allow_gates: [advisory, validating] # gate severities AFK auto-handles (auto-picks the recommended option)
61
61
  continue_sequence: true # after Seal GO, open the next recorded continuation
62
62
  max_workspaces: 5 # workspaces one armed sequence may open
63
- max_parallel: 10 # cap on eligible path-disjoint Build batches (1 = serial)
63
+ max_parallel: 10 # unattended default cap; `--parallel N` on this invocation wins and may rewrite only this field
64
64
  ```
65
65
 
66
- The file is **read-only config**: never rewritten in place. `max_slices` is the initial
66
+ The file is **read-only config**: never rewritten in place, except `/rite-autocomplete --parallel N` writes or replaces only `max_parallel: N`. `max_slices` is the initial
67
67
  budget; the mutable remaining count is the `state.md` cursor
68
68
  `afk_slices_remaining` (`AFK slices remaining: <n>` in the released bullet
69
69
  form), owned by the controlling root. Recognize either spelling and preserve
@@ -119,11 +119,18 @@ candidate manifest to cover the whole recorded chain — run
119
119
  - Every human-owned, safety, access, exhaustion, and `NO-GO` condition still stops the
120
120
  run exactly as without this field.
121
121
 
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 completed
124
- round (serial slice or parallel integrate)
125
- ([parallel-batch.md § Dynamic selection and re-batching](../../../rite-build/reference/parallel-batch.md#dynamic-selection-and-re-batching));
126
- an explicit `/rite-build --parallel N` cap wins for that invocation.
122
+ `max_parallel` is the unattended default batch cap only when this invocation does
123
+ not contain `--parallel N`; `1` forces the serial cycle only in that default
124
+ case. An exact `--parallel N` on `/rite-autocomplete` or `/rite-build` is the
125
+ batch cap for this run: do not consult leftover sentinel `max_parallel`, and do
126
+ not treat `max_parallel: 1` as serial. `/rite-autocomplete --parallel N` writes
127
+ or replaces only that field so later ticks keep the cap; `/rite-build --parallel N`
128
+ wins for that invocation without rewriting the sentinel. Never write `max_slices`
129
+ from `--max-slices`. **Failing case:** `/rite-autocomplete --parallel 5` honors a
130
+ leftover `max_parallel: 1` and stays serial.
131
+ Unattended runs take the largest eligible set ≤ that cap and recompute after
132
+ every completed round (serial slice or parallel integrate)
133
+ ([parallel-batch.md § Dynamic selection and re-batching](../../../rite-build/reference/parallel-batch.md#dynamic-selection-and-re-batching)).
127
134
 
128
135
  ## Unattended resource envelope
129
136
 
@@ -166,7 +173,18 @@ for the full taxonomy. Summary:
166
173
  | blocking | high | sync | 15m | **no** (always pauses) |
167
174
  | escalating | novel pattern | sync to specialist | 24h | **no** (always pauses) |
168
175
 
169
- `blocking` and `escalating` always pause regardless of `allow_gates`.
176
+ `blocking` and `escalating` always pause for HITL and for AFK `/rite-build`,
177
+ regardless of `allow_gates`.
178
+
179
+ **Autocomplete exception.** While `/rite-autocomplete` is the controlling
180
+ caller, an open `gate: blocking` question that already carries a ranked
181
+ recommended option (`proposed:` or option 1 labelled `(Recommended)`) is not a
182
+ user handoff: the orchestrator auto-picks that option through
183
+ `devrites-engine state resolve` and continues, including internal Spec Drift
184
+ Guard when the answer changes ownership or acceptance. Escalating,
185
+ irreversible-risk, access, and blocking questions with no recommended option
186
+ still pause. Putting `blocking` in `allow_gates` does not replace this resolve
187
+ step: an unanswered blocking question still fails Seal.
170
188
 
171
189
  An open `gate: validating` entry is **merge-blocking by definition**: at `/rite-seal` any
172
190
  `questions.md` entry with `gate: validating` and `status: open` is a NO-GO, regardless of
@@ -8,8 +8,10 @@ user-invocable: true
8
8
  # /rite-autocomplete: full lifecycle, unattended
9
9
 
10
10
  Run every phase after one clarification window. Irreversible-risk,
11
- blocking/escalating,<!-- pack-scan-ignore: negated statement: gates are NOT disabled -->
12
- and NO-GO still pause. Use the Standard native
11
+ escalating,<!-- pack-scan-ignore: negated statement: gates are NOT disabled -->
12
+ access, unanswered blocking with no recommended option, and remaining NO-GO
13
+ still pause. Open blocking questions that already name a ranked recommended
14
+ option auto-resolve; they are not a user handoff. Use the Standard native
13
15
  profile by default and Full for high-risk scope or explicit `--full`; see
14
16
  [`orchestration-profiles.md`](../devrites-lib/reference/orchestration-profiles.md).
15
17
 
@@ -85,25 +87,34 @@ executable controller/harness/bundle bytes or a missing writer, read
85
87
  3. **Arm AFK once.** Apply the loop's
86
88
  [one-write AFK contract](reference/loop.md#arm-afk-once): preserve valid
87
89
  existing bytes or create the sentinel once (`allow_gates: [advisory, validating]`); never
88
- rewrite it after Vet. Preserve an existing sentinel byte-for-byte.
90
+ rewrite it after Vet except to write or replace only `max_parallel: N` when
91
+ this invocation contains `--parallel N`. Preserve an existing sentinel
92
+ byte-for-byte otherwise. **Failing case:** leftover `max_parallel: 1` keeps
93
+ the run serial after `--parallel 5`.
89
94
  **Completion:** a valid read-only AFK sentinel exists.
90
95
  4. **Drive phases.** Follow [the loop](reference/loop.md): `/rite-spec` →
91
96
  `/rite-clarify` → `/rite-temper` → `/rite-define` → `/rite-vet` →
92
97
  `/rite-build` batch loop → `/rite-prove` → `/rite-polish` →
93
98
  `/rite-review` → `/rite-seal`. Read and execute each skill; durable files, not
94
99
  chat, carry state. Apply the mutable post-vet budget before first Build.
95
- Build runs the largest eligible path-disjoint batch (cap: `--parallel N`, else
96
- sentinel `max_parallel`, else 10) and recomputes after every completed round until no
97
- pending slice remains; a one-slice round is serial for that round only, then
100
+ Build runs the largest eligible path-disjoint batch (cap: this invocation's
101
+ `--parallel N`, else sentinel `max_parallel`, else 10) and recomputes after
102
+ every completed round until no pending slice remains; a one-slice round is serial for that round only, then
98
103
  selection runs again (host isolation still forces serial when concurrent writers
99
- cannot be separated).
104
+ cannot be separated). When `--parallel N`
105
+ is present, pass it into `/rite-build` and do not consult leftover sentinel
106
+ `max_parallel`.
100
107
  **Completion:** loop reaches Seal GO or persists a valid stop before any later phase.
101
108
  5. **Apply stops.** At every gate use
102
109
  [stop-conditions.md](reference/stop-conditions.md). A `blocked` label alone is not a stop condition:
103
110
  route agent-owned red results through bounded recovery.
104
111
  Red gates block forward advancement and enter caller-owned recovery. Stop on
105
- hard risk, human-owned blocking/escalating/NO-GO, or the
106
- exact fingerprint's proven exhaustion. Technical exhaustion records terminal
112
+ hard risk, escalating, irreversible-risk, access, blocking with no
113
+ recommended option, remaining NO-GO, or the exact fingerprint's proven
114
+ exhaustion. An open blocking question that already names a ranked recommended
115
+ option (`proposed` or option 1 labelled Recommended) auto-resolves through
116
+ `devrites-engine state resolve` and continues; do not emit `/rite-resolve` as
117
+ a user handoff. Technical exhaustion records terminal
107
118
  `Next step: none`, never a routine phase command. **Completion:** no stop is active, or its cursor and reason are durable.
108
119
  6. **Seal boundary.** Without a ship flag, stop at Seal GO with `/rite-ship` —
109
120
  unless `continue_sequence` is armed and sequence budget remains, in which case
@@ -36,5 +36,8 @@ not pick the option that's merely easiest to implement.
36
36
 
37
37
  ## When a choice is NOT autocomplete's to make
38
38
 
39
- Human-owned blocking/escalating, irreversible-risk, and access stay human.
40
- Recommended temper `expand` auto-applies; do not invent product intent.
39
+ Irreversible-risk, escalating, access, and blocking questions with no ranked
40
+ recommended option stay human. A blocking question that already names option 1
41
+ `(Recommended)` or `proposed:` is autocomplete's to resolve: pick that option
42
+ via `devrites-engine state resolve` and continue. Recommended temper `expand`
43
+ auto-applies; do not invent product intent.
@@ -17,18 +17,21 @@ allow_gates: [advisory, validating]
17
17
  # max_slices / max_agents / max_minutes / max_review_queue omitted: unlimited
18
18
  # continue_sequence: true # opt-in: chain recorded continuations after Seal GO
19
19
  # max_workspaces: 5 # sequence budget when chaining
20
- # max_parallel: 10 # Build batch cap; 1 = serial (default: eligible, cap 10)
20
+ # max_parallel: 10 # default cap when this invocation omits `--parallel N`; `--parallel N` wins and rewrites only this field
21
21
  # max_tokens: <N>
22
22
  # max_cost_usd: <amount>
23
23
  # notify: "<cmd>"
24
24
  ```
25
25
 
26
- Read an existing sentinel first. Preserve it byte-for-byte when valid; stop if
26
+ Read an existing sentinel first. Preserve it byte-for-byte when valid, except
27
+ when this invocation contains `--parallel N`: then write or replace only
28
+ `max_parallel: N` and leave every other key unchanged. Stop if the sentinel is
27
29
  malformed. Do not stop because it names `max_slices` / `max_agents` /
28
30
  `max_minutes` / `max_review_queue` or only `[advisory]`. If absent, write it
29
- once after clarity. Never write `max_slices` from `--max-slices`.
30
- It is read-only: never rewrite it after Vet or reset it on resume. Leftover
31
- `expires_at` is ignored and never rewritten.
31
+ once after clarity, and include `max_parallel: N` when that flag is present.
32
+ Never write `max_slices` from `--max-slices`. It is otherwise read-only: never rewrite it after Vet or reset it on resume. Leftover `expires_at` is ignored
33
+ and never rewritten. **Failing case:** leftover `max_parallel: 1` keeps the run
34
+ serial after `--parallel 5`.
32
35
 
33
36
  ### Derive the mutable post-vet budget
34
37
 
@@ -44,8 +47,13 @@ readiness check first; reject overlap. Do not stop on `max_agents`,
44
47
  gets fresh activation-local counters but retains durable slice/recovery state.
45
48
 
46
49
  When driving `/rite-build`, ignore afk-discipline remaining-0, `--max-slices`,
47
- and parallel AFK headroom. Close validating questions
48
- (recommended pick) instead of queuing them.
50
+ and parallel AFK headroom as stop conditions. Pass this invocation's
51
+ `--parallel N` into `/rite-build` when present; leftover sentinel
52
+ `max_parallel` is then not the cap. Close validating questions and blocking questions
53
+ that already name a ranked recommended option (recommended pick via
54
+ `devrites-engine state resolve`) instead of queuing them or handing
55
+ `/rite-resolve` to the user. Escalating, irreversible-risk, access, and
56
+ blocking with no recommended option still pause.
49
57
 
50
58
  ## Phase arc
51
59
 
@@ -58,7 +66,7 @@ Workspace files carry state; chat does not. Read and execute each phase skill:
58
66
  | 3 | `/rite-temper` | harden, reduce, or expand; irreversible risk still pauses |
59
67
  | 4 | `/rite-define` | approved plan/tasks/traceability |
60
68
  | 5 | `/rite-vet` | every plan; derive mutable budget after READY |
61
- | 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
+ | 6 | `/rite-build` batch loop | largest eligible path-disjoint set (cap: this invocation's `--parallel N`, else sentinel `max_parallel`, else 10; one-slice round when <2 eligible); recompute after each round; charge once per green built slice |
62
70
  | 7 | `/rite-prove` | all slices built; approved proof; recovery on red |
63
71
  | 8 | `/rite-polish` | re-prove after code edits |
64
72
  | 9 | `/rite-review` | in-scope correction then fresh proof |
@@ -122,16 +130,19 @@ agent-owned earlier-phase gap:
122
130
  6. Restore and consume the original cursor when prerequisites are green.
123
131
 
124
132
  <!-- workflow-artifact-adapter: {"module":"devrites-lib/reference/standards/workflow-artifacts.md","entry":"loop tick sees Workflow Artifact trigger/state","action":"invoke classifier once under owner lock; no actor-history migration","return":"same loop cursor; no budget charge for verify/rerun"} -->
125
- Ask only for a human-owned decision or mandatory safety/access action. Three
126
- no-progress corrections of one exact fingerprint exhaust; preserve its
127
- reproduction and dead ends without another Plan/Vet command.
133
+ Ask only for a human-owned decision or mandatory safety/access action. An open
134
+ blocking question with `proposed` or option 1 labelled Recommended is not that
135
+ decision: resolve it and continue. Three no-progress corrections of one exact
136
+ fingerprint exhaust; preserve its reproduction and dead ends without another
137
+ Plan/Vet command.
128
138
 
129
139
  ## Continuous caller obligation
130
140
 
131
141
  No user-facing reply is permitted while durable state contains agent-owned
132
- `NEEDS_REPLAN`, an intermediate Plan/Vet action, or a distinct retained
133
- Critical/Important fingerprint below its cap. Invoke the next internal repair
134
- immediately. A narrow reviewer closing one finding and exposing another
142
+ `NEEDS_REPLAN`, an intermediate Plan/Vet action, a distinct retained
143
+ Critical/Important fingerprint below its cap, or an open blocking question that
144
+ already names a ranked recommended option. Invoke the next internal repair or
145
+ `devrites-engine state resolve` immediately. A narrow reviewer closing one finding and exposing another
135
146
  Critical/Important invariant is progress, not exhaustion.
136
147
 
137
148
  The number of completed repair/Vet cycles is not a stop condition. Context
@@ -59,14 +59,21 @@ exhausted.
59
59
  <!-- workflow-artifact-adapter: {"module":"devrites-lib/reference/standards/workflow-artifacts.md","entry":"classifier returns owner-busy, exhausted, or existing hard gate","action":"stop on exact WAIT_ACTIVE_OWNER, BLOCKED_EXHAUSTED, or BLOCKED_GATE result","return":"unchanged cursor plus fixed route-owned output"} -->
60
60
  ## Other stop classes
61
61
 
62
- - **Gate severity:** blocking or escalating gate. Validating: auto-pick the
63
- recommended option, close the question, continue.
62
+ - **Gate severity:** escalating always stops. Blocking with a ranked recommended
63
+ option (`proposed` or option 1 labelled Recommended): auto-pick via
64
+ `devrites-engine state resolve`, then continue internally (including Spec Drift
65
+ Guard when the answer changes ownership or acceptance). Blocking with no
66
+ recommended option still stops. Validating: auto-pick the recommended option,
67
+ close the question, continue.
64
68
  - **Temper:** `expand` and extra acceptance auto-apply (record + Drift Guard).
65
69
  Irreversible-risk still pauses. `hold-rigor`, `reduce-to-MVP`, and a skip do not pause.
66
70
  - **Clarify:** material Partial/Missing/unowned decision coverage or a
67
71
  low-confidence high-consequence assumption. Continue the initial interview;
68
72
  never arm AFK early.
69
- - **Seal:** NO-GO, with every blocker and fix direction. Never round up to GO.
73
+ - **Seal:** remaining NO-GO after recommended-option questions are closed, with
74
+ every remaining blocker and fix direction. Never round up to GO. An open
75
+ recommended-option blocking question is not a Seal stop: resolve it, recover
76
+ agent-owned findings, then re-enter Seal.
70
77
  - **Reslice:** execute its marked action before deciding continue/stop.
71
78
  - **Slice budget:** do not stop on `--max-slices`. Leftover
72
79
  pre-existing remaining value, explicit flag, sentinel cap, or post-vet pending count