devrites 7.3.4 → 7.3.6

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 (75) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +1 -1
  3. package/docs/adr/0039-autocomplete-structural-admission.md +87 -0
  4. package/docs/adr/0040-early-preparation-admission.md +76 -0
  5. package/docs/adr/README.md +2 -0
  6. package/docs/cli.md +103 -5
  7. package/docs/engine/commands.md +88 -1
  8. package/engine/internal/lib/claims.go +65 -8
  9. package/engine/internal/lib/context.go +50 -2
  10. package/engine/internal/lib/continuation.go +8 -1
  11. package/engine/internal/lib/dispatch.go +147 -46
  12. package/engine/internal/lib/frontier.go +139 -59
  13. package/engine/internal/lib/frontier_controller.go +667 -0
  14. package/engine/internal/lib/frontier_preparation.go +210 -0
  15. package/engine/internal/lib/frontier_roadmap.go +168 -0
  16. package/engine/internal/lib/next.go +14 -2
  17. package/engine/internal/lib/triggers.go +9 -0
  18. package/engine/internal/parallel/repository.go +10 -0
  19. package/pack/.claude/skills/devrites-lib/SKILL.md +2 -0
  20. package/pack/.claude/skills/devrites-lib/reference/parallel-workspaces.md +108 -115
  21. package/pack/.claude/skills/devrites-lib/reference/standards/afk-hitl.md +18 -17
  22. package/pack/.claude/skills/devrites-lib/reference/workspace-artifact-schema.md +41 -38
  23. package/pack/.claude/skills/rite-autocomplete/SKILL.md +35 -38
  24. package/pack/.claude/skills/rite-autocomplete/reference/loop.md +66 -59
  25. package/pack/.claude/skills/rite-autocomplete/reference/stop-conditions.md +28 -28
  26. package/pack/.claude/skills/rite-build/reference/parallel-batch.md +17 -15
  27. package/pack/generated/claude/skills/devrites-lib/SKILL.md +2 -0
  28. package/pack/generated/claude/skills/devrites-lib/reference/parallel-workspaces.md +108 -115
  29. package/pack/generated/claude/skills/devrites-lib/reference/standards/afk-hitl.md +18 -17
  30. package/pack/generated/claude/skills/devrites-lib/reference/workspace-artifact-schema.md +41 -38
  31. package/pack/generated/claude/skills/rite-autocomplete/SKILL.md +35 -38
  32. package/pack/generated/claude/skills/rite-autocomplete/reference/loop.md +66 -59
  33. package/pack/generated/claude/skills/rite-autocomplete/reference/stop-conditions.md +28 -28
  34. package/pack/generated/claude/skills/rite-build/reference/parallel-batch.md +17 -15
  35. package/pack/generated/codex/skills/devrites-lib/SKILL.md +2 -0
  36. package/pack/generated/codex/skills/devrites-lib/reference/parallel-workspaces.md +108 -115
  37. package/pack/generated/codex/skills/devrites-lib/reference/standards/afk-hitl.md +18 -17
  38. package/pack/generated/codex/skills/devrites-lib/reference/workspace-artifact-schema.md +41 -38
  39. package/pack/generated/codex/skills/rite-autocomplete/SKILL.md +35 -38
  40. package/pack/generated/codex/skills/rite-autocomplete/reference/loop.md +66 -59
  41. package/pack/generated/codex/skills/rite-autocomplete/reference/stop-conditions.md +28 -28
  42. package/pack/generated/codex/skills/rite-build/reference/parallel-batch.md +17 -15
  43. package/pack/generated/devin/skills/devrites-lib/SKILL.md +2 -0
  44. package/pack/generated/devin/skills/devrites-lib/reference/parallel-workspaces.md +108 -115
  45. package/pack/generated/devin/skills/devrites-lib/reference/standards/afk-hitl.md +18 -17
  46. package/pack/generated/devin/skills/devrites-lib/reference/workspace-artifact-schema.md +41 -38
  47. package/pack/generated/devin/skills/rite-autocomplete/SKILL.md +35 -38
  48. package/pack/generated/devin/skills/rite-autocomplete/reference/loop.md +66 -59
  49. package/pack/generated/devin/skills/rite-autocomplete/reference/stop-conditions.md +28 -28
  50. package/pack/generated/devin/skills/rite-build/reference/parallel-batch.md +17 -15
  51. package/pack/generated/dsh/skills/devrites-lib/SKILL.md +2 -0
  52. package/pack/generated/dsh/skills/devrites-lib/reference/parallel-workspaces.md +108 -115
  53. package/pack/generated/dsh/skills/devrites-lib/reference/standards/afk-hitl.md +18 -17
  54. package/pack/generated/dsh/skills/devrites-lib/reference/workspace-artifact-schema.md +41 -38
  55. package/pack/generated/dsh/skills/rite-autocomplete/SKILL.md +35 -38
  56. package/pack/generated/dsh/skills/rite-autocomplete/reference/loop.md +66 -59
  57. package/pack/generated/dsh/skills/rite-autocomplete/reference/stop-conditions.md +28 -28
  58. package/pack/generated/dsh/skills/rite-build/reference/parallel-batch.md +17 -15
  59. package/pack/generated/omp/skills/devrites-lib/SKILL.md +2 -0
  60. package/pack/generated/omp/skills/devrites-lib/reference/parallel-workspaces.md +108 -115
  61. package/pack/generated/omp/skills/devrites-lib/reference/standards/afk-hitl.md +18 -17
  62. package/pack/generated/omp/skills/devrites-lib/reference/workspace-artifact-schema.md +41 -38
  63. package/pack/generated/omp/skills/rite-autocomplete/SKILL.md +35 -38
  64. package/pack/generated/omp/skills/rite-autocomplete/reference/loop.md +66 -59
  65. package/pack/generated/omp/skills/rite-autocomplete/reference/stop-conditions.md +28 -28
  66. package/pack/generated/omp/skills/rite-build/reference/parallel-batch.md +17 -15
  67. package/pack/generated/pi/skills/devrites-lib/SKILL.md +2 -0
  68. package/pack/generated/pi/skills/devrites-lib/reference/parallel-workspaces.md +108 -115
  69. package/pack/generated/pi/skills/devrites-lib/reference/standards/afk-hitl.md +18 -17
  70. package/pack/generated/pi/skills/devrites-lib/reference/workspace-artifact-schema.md +41 -38
  71. package/pack/generated/pi/skills/rite-autocomplete/SKILL.md +35 -38
  72. package/pack/generated/pi/skills/rite-autocomplete/reference/loop.md +66 -59
  73. package/pack/generated/pi/skills/rite-autocomplete/reference/stop-conditions.md +28 -28
  74. package/pack/generated/pi/skills/rite-build/reference/parallel-batch.md +17 -15
  75. package/package.json +1 -1
@@ -5,7 +5,6 @@ import (
5
5
  "io"
6
6
  "os"
7
7
  "path/filepath"
8
- "sort"
9
8
  "strconv"
10
9
  "strings"
11
10
 
@@ -241,6 +240,10 @@ func frontierQuote(s string) string { return "'" + strings.ReplaceAll(s, "'", "'
241
240
 
242
241
  // FrontierLines reports structural obligations, never semantic authorization or Ship approval.
243
242
  func FrontierLines(root, slug string) ([]string, error) {
243
+ mode, err := autocompleteMode(root)
244
+ if err != nil {
245
+ return nil, err
246
+ }
244
247
  if slug == "" {
245
248
  var err error
246
249
  slug, err = devritespaths.ActiveSlug(root)
@@ -248,11 +251,13 @@ func FrontierLines(root, slug string) ([]string, error) {
248
251
  return nil, err
249
252
  }
250
253
  }
254
+ workspaceOverride := ""
251
255
  if slug != "" {
252
256
  if !devritespaths.ValidSlug(slug) {
253
257
  return nil, fmt.Errorf("invalid source slug")
254
258
  }
255
- if _, err := devritespaths.WorkspaceOverrideChecked(root, slug); err != nil {
259
+ workspaceOverride, err = devritespaths.WorkspaceOverrideChecked(root, slug)
260
+ if err != nil {
256
261
  return nil, err
257
262
  }
258
263
  }
@@ -288,44 +293,21 @@ func FrontierLines(root, slug string) ([]string, error) {
288
293
  }
289
294
  chain, chainErr := frontierChain(nodes, slug)
290
295
  if chainErr != nil {
291
- return []string{"frontier: blocked", "frontier-source: " + slug, fmt.Sprintf("frontier-workspace: %s location=%s action=repair reason=%s", slug, source.location, chainErr)}, nil
292
- }
293
- anchor := chain[len(chain)-1]
294
- related := map[string]bool{anchor: true}
295
- for range maxSequenceChain {
296
- changed := false
297
- for name, n := range nodes {
298
- if related[n.parent] && !related[name] {
299
- related[name] = true
300
- changed = true
301
- }
302
- }
303
- if !changed {
304
- break
305
- }
306
- }
296
+ location := "work"
297
+ if source.location == "archive" {
298
+ location = "archive"
299
+ }
300
+ artifact := filepath.Join(root, location, slug, "state.md")
301
+ return []string{
302
+ "frontier: blocked", "frontier-source: " + slug,
303
+ fmt.Sprintf("frontier-workspace: %s location=%s action=repair reason=%s", slug, source.location, chainErr),
304
+ "frontier-review-sequence: reason=native authority-normalization required: " + chainErr.Error() + " artifacts=" + artifact,
305
+ }, nil
306
+ }
307
+ ordered, related := frontierRelated(nodes, chain)
307
308
  if len(related) == 1 && source.parent == "" && !source.hasPosition && !source.hasRemaining && frontierSpecTarget(source) == "" {
308
309
  return []string{"frontier: none"}, nil
309
310
  }
310
- ordered := make([]string, 0, len(related))
311
- for name := range related {
312
- ordered = append(ordered, name)
313
- }
314
- depth := make(map[string]int, len(ordered))
315
- for _, name := range ordered {
316
- chain, _ := frontierChain(nodes, name)
317
- depth[name] = len(chain)
318
- }
319
- sort.Slice(ordered, func(i, j int) bool {
320
- a, b := nodes[ordered[i]], nodes[ordered[j]]
321
- if depth[a.slug] != depth[b.slug] {
322
- return depth[a.slug] < depth[b.slug]
323
- }
324
- if a.position != b.position {
325
- return a.position < b.position
326
- }
327
- return a.slug < b.slug
328
- })
329
311
  controller := ""
330
312
  for _, name := range ordered {
331
313
  if nodes[name].location == "live" {
@@ -350,6 +332,9 @@ func FrontierLines(root, slug string) ([]string, error) {
350
332
  if err != nil {
351
333
  return nil, err
352
334
  }
335
+ if workspaceOverride != "" {
336
+ allowFuture = false
337
+ }
353
338
  if lease != nil && lease.Capacity > 0 && capN > lease.Capacity {
354
339
  capN = lease.Capacity
355
340
  }
@@ -364,12 +349,43 @@ func FrontierLines(root, slug string) ([]string, error) {
364
349
  if source.hasRemaining && source.remaining == 0 {
365
350
  allowFuture = false
366
351
  }
367
- maps, artifacts, mapProblem := frontierRoadmap(nodes, ordered)
352
+ maps, artifacts, mapProblem, invalidCanonical := frontierRoadmap(nodes, ordered)
353
+ if invalidCanonical {
354
+ return []string{"frontier: blocked", "frontier-source: " + slug, "frontier-review-sequence: reason=native authority-normalization required: invalid canonical authority: " + mapProblem + " artifacts=" + strings.Join(artifacts, ",")}, nil
355
+ }
356
+ if len(maps) > 0 && mapProblem == "" {
357
+ lines = append(lines, "frontier-authority: "+strings.Join(artifacts, ","))
358
+ }
359
+ records := frontierRecords{}
360
+ if mode {
361
+ records, err = frontierEvidence(root, slug)
362
+ if err != nil {
363
+ return nil, err
364
+ }
365
+ }
366
+ sequenceDeferred := mode && records.blocked["sequence"] != ""
368
367
  if mapProblem != "" {
369
- lines = append(lines, "frontier-review-sequence: "+strings.Join(artifacts, ",")+" reason=native authority-normalization required: "+mapProblem)
370
- blocked = true
368
+ if sequenceDeferred {
369
+ lines = append(lines, "frontier-blocked: sequence — "+records.blocked["sequence"]+"; scheduling deferred, not normalized")
370
+ } else {
371
+ lines = append(lines, "frontier-review-sequence: reason=native authority-normalization required: "+mapProblem+" artifacts="+strings.Join(artifacts, ","))
372
+ blocked = true
373
+ }
371
374
  }
375
+ deferred := map[string]string{}
372
376
  prepared := map[string]bool{}
377
+ var preparatory map[string]bool
378
+ var preparationAccounts map[string]*dispatchFile
379
+ if mode && capN > 1 {
380
+ preparatory = make(map[string]bool, len(ordered))
381
+ preparationAccounts = make(map[string]*dispatchFile, len(ordered))
382
+ for _, name := range ordered {
383
+ preparatory[name], preparationAccounts[name], err = frontierPreparatoryOnly(root, nodes[name], nodes, maps, mapProblem)
384
+ if err != nil {
385
+ return nil, err
386
+ }
387
+ }
388
+ }
373
389
  for _, name := range ordered {
374
390
  n := nodes[name]
375
391
  action, reason := "complete", "archived predecessor; no writer admission"
@@ -382,25 +398,38 @@ func FrontierLines(root, slug string) ([]string, error) {
382
398
  } else if gateReason != "" {
383
399
  action, reason, held, blocked = "wait", gateReason, true, true
384
400
  } else if n.location == "live" {
385
- action, reason, err = frontierLocal(root, controller, n, &candidates)
401
+ action, reason, err = frontierLocal(root, controller, n, &candidates, preparationAccounts[name])
386
402
  if err != nil {
387
403
  action, reason, n.selectionHeld = "repair", err.Error(), true
388
404
  }
405
+ if mode && action == "prepare" && deferred[name] != "" {
406
+ action, reason, n.selectionHeld = "wait", "frontier-blocked: "+name+" — "+deferred[name], true
407
+ }
408
+ inheritedPreparation := preparatory[name] && preparatory[n.parent] && (n.phase == "frame" || preparationPhase(n.phase)) && action == "prepare"
409
+ if inheritedPreparation {
410
+ action, reason, n.selectionHeld = "wait", frontierInheritedPreparationWait, true
411
+ }
389
412
  if !n.selectionHeld {
390
413
  live = append(live, name)
391
414
  }
392
- held = action == "blocked" || err != nil
415
+ held = inheritedPreparation || action == "blocked" || err != nil || mode && action == "wait" && deferred[name] != ""
393
416
  switch action {
394
417
  case "inspect", "prepare", "repair":
395
418
  actionable = true
396
419
  case "wait":
397
420
  waiting = true
421
+ case "no-cursor":
422
+ blocked = true
423
+ action = "wait"
398
424
  case "blocked":
399
425
  blocked = true
400
426
  action = "wait"
401
427
  }
402
428
  }
403
429
  lines = append(lines, fmt.Sprintf("frontier-workspace: %s location=%s action=%s reason=%s", name, n.location, action, reason))
430
+ if preparatory[name] {
431
+ continue
432
+ }
404
433
  if invalid != nil || held || !allowFuture || n.hasRemaining && n.remaining == 0 || n.status == "awaiting_human" || n.status == "blocked" || n.question != "" {
405
434
  continue
406
435
  }
@@ -409,18 +438,18 @@ func FrontierLines(root, slug string) ([]string, error) {
409
438
  target = maps[n.position+1]
410
439
  }
411
440
  if mapProblem != "" {
412
- if target != "" || n.hasRemaining && n.remaining > 0 {
441
+ if !sequenceDeferred && (target != "" || n.hasRemaining && n.remaining > 0) {
413
442
  actionable = true
414
443
  }
415
444
  continue
416
445
  }
417
446
  if target != "" && frontierConflictingSuccessor(nodes, n, target) {
418
- lines = append(lines, "frontier-review-sequence: "+strings.Join(artifacts, ",")+" source="+name+" reason=conflicting successor identities; native authority-normalization required")
447
+ lines = append(lines, "frontier-review-sequence: reason=conflicting successor identities; native authority-normalization required source="+name+" artifacts="+strings.Join(artifacts, ","))
419
448
  actionable, blocked = true, true
420
449
  continue
421
450
  }
422
451
  if target != "" && n.hasPosition && maps[n.position+1] != "" && maps[n.position+1] != target {
423
- lines = append(lines, "frontier-review-sequence: "+strings.Join(artifacts, ",")+" source="+name+" reason=next Spec conflicts with recorded successor; native authority-normalization required")
452
+ lines = append(lines, "frontier-review-sequence: reason=next Spec conflicts with recorded successor; native authority-normalization required source="+name+" artifacts="+strings.Join(artifacts, ","))
424
453
  actionable, blocked = true, true
425
454
  continue
426
455
  }
@@ -429,16 +458,47 @@ func FrontierLines(root, slug string) ([]string, error) {
429
458
  actionable, blocked = true, true
430
459
  continue
431
460
  }
461
+ if mode && target != "" {
462
+ sourceRecords := records
463
+ if name != slug {
464
+ sourceRecords, err = frontierEvidence(root, name)
465
+ if err != nil {
466
+ return nil, err
467
+ }
468
+ }
469
+ if reason := sourceRecords.blocked[target]; reason != "" {
470
+ deferred[target] = reason
471
+ lines = append(lines, "frontier-blocked: "+target+" source="+name+" — "+reason+"; preparation deferred, not approved")
472
+ continue
473
+ }
474
+ }
432
475
  if target != "" && nodes[target] == nil && !prepared[target] {
433
476
  prepared[target] = true
434
477
  // A table identifies a preparation obligation, not approval to implement it.
435
478
  lines = append(lines, fmt.Sprintf("frontier-workspace: %s location=missing action=prepare reason=recorded next workspace; native scope and independence review required", target), fmt.Sprintf("frontier-prepare: /rite-spec %s source=%s reason=read recorded sequence authority before preparation; no workspace created", frontierQuote(target), name))
436
479
  actionable = true
437
480
  } else if target == "" && n.hasRemaining && n.remaining > 0 {
438
- lines = append(lines, "frontier-review-sequence: "+strings.Join(artifacts, ",")+" source="+name+" reason=remaining work unresolved; native authority-normalization required")
481
+ lines = append(lines, "frontier-review-sequence: reason=remaining work unresolved; native authority-normalization required source="+name+" artifacts="+strings.Join(artifacts, ","))
439
482
  actionable, blocked = true, true
440
483
  }
441
484
  }
485
+ if mode {
486
+ filtered := candidates[:0]
487
+ for _, candidate := range candidates {
488
+ workspace := candidate.Workspace
489
+ if workspace == "" {
490
+ workspace = controller
491
+ }
492
+ if workspace != slug {
493
+ if reason := frontierSerialReason(records, slug, workspace, candidate.ID); reason != "" {
494
+ lines = append(lines, "frontier-serial: "+workspace+"/"+candidate.ID+" — "+reason+"; ordered handoff retained")
495
+ continue
496
+ }
497
+ }
498
+ filtered = append(filtered, candidate)
499
+ }
500
+ candidates = filtered
501
+ }
442
502
  if controller != "" && controllerProblem == "" && len(live) > 0 {
443
503
  lines = append(lines, fmt.Sprintf("frontier-select-command: devrites-engine parallel select --root %s --slug %s --from-plan --workspaces %s --cap %d", frontierQuote(filepath.Dir(root)), frontierQuote(controller), frontierQuote(strings.Join(live, ",")), capN))
444
504
  if len(candidates) > 0 {
@@ -533,7 +593,7 @@ func frontierQuestionHold(n *frontierWorkspace) (string, error) {
533
593
  return "", nil
534
594
  }
535
595
 
536
- func frontierLocal(root, controller string, n *frontierWorkspace, candidates *[]parallel.SlicePaths) (string, string, error) {
596
+ func frontierLocal(root, controller string, n *frontierWorkspace, candidates *[]parallel.SlicePaths, dispatch *dispatchFile) (string, string, error) {
537
597
  n.selectionHeld = true
538
598
  if frontierTerminal(n) {
539
599
  return "complete", "terminal workspace", nil
@@ -544,15 +604,20 @@ func frontierLocal(root, controller string, n *frontierWorkspace, candidates *[]
544
604
  if n.status != "running" && n.status != "ready" {
545
605
  return "blocked", "status lacks an actionable native cursor; normalize state without inventing authority", nil
546
606
  }
547
- raw, err := readBoundedRegularFile(filepath.Join(n.dir, "dispatch.json"), workspaceArtifactLimit)
548
- if err != nil && !os.IsNotExist(err) {
549
- return "", "", err
550
- }
551
- if err == nil {
552
- dispatch, err := decodeDispatch(raw)
553
- if err != nil {
607
+ if dispatch == nil {
608
+ raw, err := readBoundedRegularFile(filepath.Join(n.dir, "dispatch.json"), workspaceArtifactLimit)
609
+ if err != nil && !os.IsNotExist(err) {
554
610
  return "", "", err
555
611
  }
612
+ if err == nil {
613
+ dispatch, err = decodeDispatch(raw)
614
+ if err != nil {
615
+ return "", "", err
616
+ }
617
+ }
618
+ }
619
+ if dispatch != nil {
620
+ var err error
556
621
  retained, pending := false, false
557
622
  var built map[string]bool
558
623
  for _, wave := range dispatch.Waves {
@@ -638,12 +703,22 @@ func frontierLocal(root, controller string, n *frontierWorkspace, candidates *[]
638
703
  n.selectionHeld = true
639
704
  return "wait", "Ship requires existing native authority; controller invocation is not approval", nil
640
705
  }
706
+ if n.next == "" {
707
+ // Missing local work is not a hold on independently recorded preparation.
708
+ n.selectionHeld = true
709
+ return "no-cursor", "no actionable native internal cursor; review state without inventing authority", nil
710
+ }
641
711
  return "blocked", "no actionable native internal cursor; review state without inventing authority", nil
642
712
  }
643
713
 
644
- // Only exact position/slug or position/workspace tables are structural authority.
645
- // Different maps require native normalization, even if prose claims one supersedes another.
646
- func frontierRoadmap(nodes map[string]*frontierWorkspace, ordered []string) (map[int]string, []string, string) {
714
+ // Current canonical authority supersedes historical tables without rewriting them.
715
+ // Without it, differing exact legacy maps require native normalization.
716
+ func frontierRoadmap(nodes map[string]*frontierWorkspace, ordered []string) (map[int]string, []string, string, bool) {
717
+ if mapping, artifacts, err := frontierCanonicalRoadmap(nodes, ordered); err != nil {
718
+ return nil, artifacts, err.Error(), true
719
+ } else if mapping != nil {
720
+ return mapping, artifacts, "", false
721
+ }
647
722
  result := map[int]string{}
648
723
  var artifacts []string
649
724
  problem := ""
@@ -743,7 +818,7 @@ func frontierRoadmap(nodes map[string]*frontierWorkspace, ordered []string) (map
743
818
  artifacts = append(artifacts, filepath.Join(nodes[slug].dir, "state.md"))
744
819
  }
745
820
  }
746
- return result, artifacts, problem
821
+ return result, artifacts, problem, false
747
822
  }
748
823
 
749
824
  func frontierCells(line string) []string {
@@ -782,6 +857,12 @@ func frontierBoundSpec(root, slug, action string) bool {
782
857
  if err != nil {
783
858
  return false
784
859
  }
860
+ ordered, _ := frontierRelated(nodes, chain)
861
+ mapping, _, problem, invalidCanonical := frontierRoadmap(nodes, ordered)
862
+ if invalidCanonical {
863
+ return false
864
+ }
865
+ // Existing checked children remain resumable under ambiguous historical maps.
785
866
  if child := nodes[target]; child != nil {
786
867
  if child.parent != slug {
787
868
  return false
@@ -791,7 +872,6 @@ func frontierBoundSpec(root, slug, action string) bool {
791
872
  }
792
873
  return true
793
874
  }
794
- mapping, _, problem := frontierRoadmap(nodes, chain)
795
875
  if problem != "" || n.hasPosition && mapping[n.position+1] != "" && mapping[n.position+1] != target {
796
876
  return false
797
877
  }