devrites 4.1.0 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +1 -1
  3. package/docs/engine/commands.md +69 -6
  4. package/docs/orchestration.md +6 -3
  5. package/engine/go.mod +1 -1
  6. package/engine/internal/gate/gate.go +154 -46
  7. package/engine/internal/gate/gate_test.go +290 -2
  8. package/engine/internal/gate/readiness_binding.go +66 -100
  9. package/engine/internal/gate/readiness_binding_test.go +126 -3
  10. package/engine/internal/lib/cli_observe.go +66 -0
  11. package/engine/internal/lib/observe_summary.go +79 -0
  12. package/engine/internal/lib/resolve.go +6 -6
  13. package/engine/internal/lib/skilltrust.go +196 -0
  14. package/engine/internal/lib/taskgraph.go +162 -0
  15. package/engine/internal/lib/taskgraph_test.go +150 -0
  16. package/engine/internal/lib/workspace_read.go +29 -0
  17. package/engine/internal/parallel/cli.go +467 -0
  18. package/engine/internal/parallel/git.go +181 -0
  19. package/engine/internal/parallel/lease.go +244 -0
  20. package/engine/internal/parallel/ops.go +416 -0
  21. package/engine/internal/parallel/parallel_test.go +242 -0
  22. package/engine/internal/parallel/pathdisjoint.go +129 -0
  23. package/engine/internal/parallel/pathdisjoint_test.go +97 -0
  24. package/engine/internal/state/cmd/workflowmanifest/main.go +32 -3
  25. package/engine/internal/state/feature.go +1 -100
  26. package/engine/internal/state/observation.go +584 -0
  27. package/engine/internal/state/observation_open_other.go +9 -0
  28. package/engine/internal/state/observation_open_unix.go +12 -0
  29. package/engine/internal/state/observation_test.go +960 -0
  30. package/engine/internal/state/observation_unix_test.go +68 -0
  31. package/engine/internal/state/schema.go +60 -160
  32. package/engine/internal/state/state_test.go +489 -101
  33. package/engine/internal/state/status.go +97 -53
  34. package/engine/main.go +72 -3
  35. package/engine/root_routing_test.go +1 -1
  36. package/engine/tests/adr_0004_required_by_phase_test.go +16 -13
  37. package/engine/tests/adr_0011_define_plan_test.go +14 -12
  38. package/engine/tests/gate_test.go +403 -28
  39. package/engine/tests/workspace_observation_migration_test.go +1065 -0
  40. package/pack/.claude/skills/devrites-debug-recovery/SKILL.md +1 -10
  41. package/pack/.claude/skills/devrites-lib/reference/standards/acceptance-preserving-reslice.md +30 -0
  42. package/pack/.claude/skills/devrites-lib/reference/standards/afk-hitl.md +2 -9
  43. package/pack/.claude/skills/devrites-lib/reference/standards/one-shot-actions.md +1 -6
  44. package/pack/.claude/skills/devrites-lib/reference/standards/skill-authoring.md +22 -0
  45. package/pack/.claude/skills/devrites-lib/reference/standards/workflow-artifacts.md +411 -102
  46. package/pack/.claude/skills/rite-autocomplete/SKILL.md +98 -146
  47. package/pack/.claude/skills/rite-autocomplete/reference/decision-policy.md +11 -5
  48. package/pack/.claude/skills/rite-autocomplete/reference/loop.md +96 -134
  49. package/pack/.claude/skills/rite-autocomplete/reference/stop-conditions.md +92 -131
  50. package/pack/.claude/skills/rite-build/SKILL.md +46 -81
  51. package/pack/.claude/skills/rite-build/reference/afk-discipline.md +19 -25
  52. package/pack/.claude/skills/rite-build/reference/checkpoint-protocol.md +29 -59
  53. package/pack/.claude/skills/rite-build/reference/one-slice-cycle.md +8 -3
  54. package/pack/.claude/skills/rite-build/reference/output.md +2 -0
  55. package/pack/.claude/skills/rite-build/reference/parallel-batch.md +64 -0
  56. package/pack/.claude/skills/rite-build/reference/phase-contract.md +9 -22
  57. package/pack/.claude/skills/rite-build/reference/wright-dispatch.md +7 -3
  58. package/pack/.claude/skills/rite-clarify/reference/anti-patterns.md +24 -0
  59. package/pack/.claude/skills/rite-customize/SKILL.md +3 -2
  60. package/pack/.claude/skills/rite-doctor/SKILL.md +5 -1
  61. package/pack/.claude/skills/rite-plan/SKILL.md +17 -17
  62. package/pack/.claude/skills/rite-plan/reference/anti-patterns.md +11 -1
  63. package/pack/.claude/skills/rite-plan/reference/dependency-graph.md +3 -0
  64. package/pack/.claude/skills/rite-plan/reference/replan-and-repair.md +10 -3
  65. package/pack/.claude/skills/rite-prove/SKILL.md +95 -153
  66. package/pack/.claude/skills/rite-vet/SKILL.md +121 -174
  67. package/pack/.claude/skills/rite-vet/reference/anti-patterns.md +14 -2
  68. package/pack/.claude/skills/rite-vet/reference/artifacts.md +10 -6
  69. package/pack/.claude/skills/rite-vet/reference/depth.md +10 -3
  70. package/pack/.claude/skills/rite-vet/reference/review-axes.md +12 -4
  71. package/pack/generated/claude/skills/devrites-debug-recovery/SKILL.md +1 -10
  72. package/pack/generated/claude/skills/devrites-lib/reference/standards/acceptance-preserving-reslice.md +30 -0
  73. package/pack/generated/claude/skills/devrites-lib/reference/standards/afk-hitl.md +2 -9
  74. package/pack/generated/claude/skills/devrites-lib/reference/standards/one-shot-actions.md +1 -6
  75. package/pack/generated/claude/skills/devrites-lib/reference/standards/skill-authoring.md +22 -0
  76. package/pack/generated/claude/skills/devrites-lib/reference/standards/workflow-artifacts.md +411 -102
  77. package/pack/generated/claude/skills/rite-autocomplete/SKILL.md +98 -146
  78. package/pack/generated/claude/skills/rite-autocomplete/reference/decision-policy.md +11 -5
  79. package/pack/generated/claude/skills/rite-autocomplete/reference/loop.md +96 -134
  80. package/pack/generated/claude/skills/rite-autocomplete/reference/stop-conditions.md +92 -131
  81. package/pack/generated/claude/skills/rite-build/SKILL.md +46 -81
  82. package/pack/generated/claude/skills/rite-build/reference/afk-discipline.md +19 -25
  83. package/pack/generated/claude/skills/rite-build/reference/checkpoint-protocol.md +29 -59
  84. package/pack/generated/claude/skills/rite-build/reference/one-slice-cycle.md +8 -3
  85. package/pack/generated/claude/skills/rite-build/reference/output.md +2 -0
  86. package/pack/generated/claude/skills/rite-build/reference/parallel-batch.md +64 -0
  87. package/pack/generated/claude/skills/rite-build/reference/phase-contract.md +9 -22
  88. package/pack/generated/claude/skills/rite-build/reference/wright-dispatch.md +7 -3
  89. package/pack/generated/claude/skills/rite-clarify/reference/anti-patterns.md +24 -0
  90. package/pack/generated/claude/skills/rite-customize/SKILL.md +3 -2
  91. package/pack/generated/claude/skills/rite-doctor/SKILL.md +5 -1
  92. package/pack/generated/claude/skills/rite-plan/SKILL.md +17 -17
  93. package/pack/generated/claude/skills/rite-plan/reference/anti-patterns.md +11 -1
  94. package/pack/generated/claude/skills/rite-plan/reference/dependency-graph.md +3 -0
  95. package/pack/generated/claude/skills/rite-plan/reference/replan-and-repair.md +10 -3
  96. package/pack/generated/claude/skills/rite-prove/SKILL.md +95 -153
  97. package/pack/generated/claude/skills/rite-vet/SKILL.md +121 -174
  98. package/pack/generated/claude/skills/rite-vet/reference/anti-patterns.md +14 -2
  99. package/pack/generated/claude/skills/rite-vet/reference/artifacts.md +10 -6
  100. package/pack/generated/claude/skills/rite-vet/reference/depth.md +10 -3
  101. package/pack/generated/claude/skills/rite-vet/reference/review-axes.md +12 -4
  102. package/pack/generated/codex/skills/devrites-debug-recovery/SKILL.md +1 -10
  103. package/pack/generated/codex/skills/devrites-lib/reference/standards/acceptance-preserving-reslice.md +30 -0
  104. package/pack/generated/codex/skills/devrites-lib/reference/standards/afk-hitl.md +2 -9
  105. package/pack/generated/codex/skills/devrites-lib/reference/standards/one-shot-actions.md +1 -6
  106. package/pack/generated/codex/skills/devrites-lib/reference/standards/skill-authoring.md +22 -0
  107. package/pack/generated/codex/skills/devrites-lib/reference/standards/workflow-artifacts.md +411 -102
  108. package/pack/generated/codex/skills/rite-autocomplete/SKILL.md +98 -146
  109. package/pack/generated/codex/skills/rite-autocomplete/reference/decision-policy.md +11 -5
  110. package/pack/generated/codex/skills/rite-autocomplete/reference/loop.md +96 -134
  111. package/pack/generated/codex/skills/rite-autocomplete/reference/stop-conditions.md +92 -131
  112. package/pack/generated/codex/skills/rite-build/SKILL.md +46 -81
  113. package/pack/generated/codex/skills/rite-build/reference/afk-discipline.md +19 -25
  114. package/pack/generated/codex/skills/rite-build/reference/checkpoint-protocol.md +29 -59
  115. package/pack/generated/codex/skills/rite-build/reference/one-slice-cycle.md +8 -3
  116. package/pack/generated/codex/skills/rite-build/reference/output.md +2 -0
  117. package/pack/generated/codex/skills/rite-build/reference/parallel-batch.md +64 -0
  118. package/pack/generated/codex/skills/rite-build/reference/phase-contract.md +9 -22
  119. package/pack/generated/codex/skills/rite-build/reference/wright-dispatch.md +7 -3
  120. package/pack/generated/codex/skills/rite-clarify/reference/anti-patterns.md +24 -0
  121. package/pack/generated/codex/skills/rite-customize/SKILL.md +3 -2
  122. package/pack/generated/codex/skills/rite-doctor/SKILL.md +5 -1
  123. package/pack/generated/codex/skills/rite-plan/SKILL.md +17 -17
  124. package/pack/generated/codex/skills/rite-plan/reference/anti-patterns.md +11 -1
  125. package/pack/generated/codex/skills/rite-plan/reference/dependency-graph.md +3 -0
  126. package/pack/generated/codex/skills/rite-plan/reference/replan-and-repair.md +10 -3
  127. package/pack/generated/codex/skills/rite-prove/SKILL.md +95 -153
  128. package/pack/generated/codex/skills/rite-vet/SKILL.md +121 -174
  129. package/pack/generated/codex/skills/rite-vet/reference/anti-patterns.md +14 -2
  130. package/pack/generated/codex/skills/rite-vet/reference/artifacts.md +10 -6
  131. package/pack/generated/codex/skills/rite-vet/reference/depth.md +10 -3
  132. package/pack/generated/codex/skills/rite-vet/reference/review-axes.md +12 -4
  133. package/package.json +1 -1
@@ -6,78 +6,119 @@ import (
6
6
  )
7
7
 
8
8
  // Report is the computed completeness status of a feature at its current phase.
9
- // It embeds the loaded Feature (Slug, Phase, Present) and adds the phase-relative
10
- // required set and the missing sections.
11
9
  type Report struct {
12
- *Feature
13
- Required map[Section]bool
14
- Missing []Section // legacy required-but-empty sections, in Sections order
15
- RequiredFiles map[string]bool
16
- MissingFiles []string // required-but-empty workspace files, in lifecycle order
10
+ Slug string
11
+ Phase Phase
12
+ Status string
13
+ NextAction string
14
+ PrinciplesPresent bool
15
+ Required map[Section]bool
16
+ Missing []Section // required-but-empty sections, in policy order
17
+ RequiredFiles map[string]bool
18
+ MissingFiles []string // required-but-empty workspace files, in lifecycle order
19
+ present map[Section]bool
20
+ diagnostics []ArtifactDiagnostic
17
21
  }
18
22
 
19
- // Status computes the status report for feature <slug> under root by reading
20
- // the files directly. It reports completeness relative to the feature's current
21
- // phase only.
23
+ // Status computes phase-relative completeness from one retained workspace observation.
22
24
  func Status(root, slug string) (*Report, error) {
23
- f, err := LoadFeature(root, slug)
25
+ return statusWithCallback(root, slug, nil)
26
+ }
27
+
28
+ func statusWithCallback(root, slug string, callback observationCallback) (*Report, error) {
29
+ observation, err := observeWorkspace(root, slug, callback)
30
+ if err != nil {
31
+ return nil, err
32
+ }
33
+ phase, err := observation.DeclaredPhase()
24
34
  if err != nil {
25
35
  return nil, fmt.Errorf("compute status: %w", err)
26
36
  }
27
- return NewReport(f), nil
37
+ policy, _ := PolicyFor(phase)
38
+ return newObservationReport(observation, phase, policy), nil
28
39
  }
29
40
 
30
- // NewReport computes the phase-relative required set and missing sections for a
31
- // loaded Feature.
32
- func NewReport(f *Feature) *Report {
33
- required := make(map[Section]bool)
34
- for _, s := range RequiredSections(f.Phase) {
35
- required[s] = true
41
+ func observationCursorFields(observation *WorkspaceObservation) (status, nextAction string, principlesPresent bool) {
42
+ if fact, ok := observation.Fact(".devrites/principles.md"); ok && fact.State() == ArtifactPresent {
43
+ principlesPresent = true
44
+ }
45
+ fact, ok := observation.Fact(LedgerFile)
46
+ if !ok || fact.State() != ArtifactPresent {
47
+ return "", "", principlesPresent
36
48
  }
37
- requiredFiles := make(map[string]bool)
38
- for _, name := range RequiredWorkspaceFiles(f.Phase) {
39
- requiredFiles[name] = true
49
+ lines := strings.Split(string(fact.Bytes()), "\n")
50
+ if value, ok := CursorField(lines, CursorStatus); ok {
51
+ status = value
52
+ }
53
+ if value, ok := CursorField(lines, CursorNextAction); ok {
54
+ nextAction = value
55
+ }
56
+ return status, nextAction, principlesPresent
57
+ }
58
+
59
+ func newObservationReport(observation *WorkspaceObservation, phase Phase, policy PhasePolicy) *Report {
60
+ present := observationSectionPresence(observation)
61
+ required := requiredSections(policy)
62
+ requiredFiles := requiredArtifactSet(policy)
63
+ missingArtifacts, diagnostics := observation.Missing(policy.RequiredArtifacts)
64
+ missingFiles := make([]string, len(missingArtifacts))
65
+ for i, artifact := range missingArtifacts {
66
+ missingFiles[i] = string(artifact)
40
67
  }
41
- missingFiles := MissingWorkspaceFiles(f, f.Phase)
68
+ status, nextAction, principlesPresent := observationCursorFields(observation)
42
69
  return &Report{
43
- Feature: f,
44
- Required: required,
45
- Missing: MissingFor(f, f.Phase),
46
- RequiredFiles: requiredFiles,
47
- MissingFiles: missingFiles,
70
+ Slug: observation.Slug(),
71
+ Phase: phase,
72
+ Status: status,
73
+ NextAction: nextAction,
74
+ PrinciplesPresent: principlesPresent,
75
+ Required: required,
76
+ Missing: missingObservationSections(present, policy.RequiredSections),
77
+ RequiredFiles: requiredFiles,
78
+ MissingFiles: missingFiles,
79
+ present: present,
80
+ diagnostics: diagnostics,
48
81
  }
49
82
  }
50
83
 
51
- // MissingFor returns the sections required to complete phase p that the feature
52
- // does not yet have real content for, in canonical Sections order. Gates use it
53
- // to check completeness against a phase other than the feature's current one
54
- // (e.g. seal always checks the full seal-phase set), so its result is
55
- // independent of f.Phase.
56
- func MissingFor(f *Feature, p Phase) []Section {
57
- required := make(map[Section]bool)
58
- for _, s := range RequiredSections(p) {
59
- required[s] = true
84
+ func observationSectionPresence(observation *WorkspaceObservation) map[Section]bool {
85
+ present := make(map[Section]bool, len(Sections))
86
+ for _, section := range Sections {
87
+ for _, name := range sectionFiles[section] {
88
+ fact, ok := observation.Fact(ArtifactPath(name))
89
+ if ok && fact.State() == ArtifactPresent {
90
+ present[section] = true
91
+ break
92
+ }
93
+ }
60
94
  }
95
+ return present
96
+ }
97
+
98
+ func missingObservationSections(present map[Section]bool, required []Section) []Section {
61
99
  var missing []Section
62
- for _, s := range Sections {
63
- if required[s] && !f.Present[s] {
64
- missing = append(missing, s)
100
+ for _, section := range required {
101
+ if !present[section] {
102
+ missing = append(missing, section)
65
103
  }
66
104
  }
67
105
  return missing
68
106
  }
69
107
 
70
- // MissingWorkspaceFiles returns the concrete lifecycle artifacts required for
71
- // p that do not contain real content. It is the authoritative runtime
72
- // completeness view; Section completeness remains only a compact legacy view.
73
- func MissingWorkspaceFiles(f *Feature, p Phase) []string {
74
- var missing []string
75
- for _, name := range RequiredWorkspaceFiles(p) {
76
- if !f.PresentFiles[name] {
77
- missing = append(missing, name)
78
- }
108
+ func requiredSections(policy PhasePolicy) map[Section]bool {
109
+ required := make(map[Section]bool, len(policy.RequiredSections))
110
+ for _, section := range policy.RequiredSections {
111
+ required[section] = true
79
112
  }
80
- return missing
113
+ return required
114
+ }
115
+
116
+ func requiredArtifactSet(policy PhasePolicy) map[string]bool {
117
+ required := make(map[string]bool, len(policy.RequiredArtifacts))
118
+ for _, artifact := range policy.RequiredArtifacts {
119
+ required[string(artifact)] = true
120
+ }
121
+ return required
81
122
  }
82
123
 
83
124
  // Complete reports whether every concrete canonical-workspace file required by
@@ -92,19 +133,22 @@ func (r *Report) Render() string {
92
133
  var b strings.Builder
93
134
  fmt.Fprintf(&b, "feature: %s\n", r.Slug)
94
135
  fmt.Fprintf(&b, "phase: %s\n", r.Phase)
95
- for _, s := range Sections {
136
+ for _, section := range Sections {
96
137
  state := "empty"
97
- if r.Present[s] {
138
+ if r.present[section] {
98
139
  state = "present"
99
140
  }
100
141
  mark := ""
101
- if r.Required[s] {
142
+ if r.Required[section] {
102
143
  mark = "required"
103
144
  }
104
- line := fmt.Sprintf(" %-10s %-8s %s", s, state, mark)
145
+ line := fmt.Sprintf(" %-10s %-8s %s", section, state, mark)
105
146
  b.WriteString(strings.TrimRight(line, " "))
106
147
  b.WriteByte('\n')
107
148
  }
149
+ for _, diagnostic := range r.diagnostics {
150
+ fmt.Fprintf(&b, "artifact: %s: %s (%s)\n", diagnostic.Path, diagnostic.State, diagnostic.Code)
151
+ }
108
152
  if r.Complete() {
109
153
  b.WriteString("result: complete\n")
110
154
  } else {
package/engine/main.go CHANGED
@@ -10,6 +10,7 @@ import (
10
10
  "github.com/devrites/devrites/internal/gate"
11
11
  "github.com/devrites/devrites/internal/install"
12
12
  "github.com/devrites/devrites/internal/lib"
13
+ "github.com/devrites/devrites/internal/parallel"
13
14
  "github.com/devrites/devrites/internal/state"
14
15
  "github.com/devrites/devrites/internal/version"
15
16
  )
@@ -24,6 +25,12 @@ Usage:
24
25
  devrites-engine check readiness <slug> Check required files and the stable Build-input binding
25
26
  devrites-engine check readiness --emit-binding <slug> Emit the stable Build-input binding for Vet
26
27
  devrites-engine check seal <slug> Recheck the Build-input binding, final files, and evidence freshness
28
+ devrites-engine check path-disjoint [--root <dir>] [<json-file>|-]
29
+ Verify slice path sets are pairwise disjoint
30
+ devrites-engine check task-graph <slug> Validate tasks.md slice dependency graph
31
+ devrites-engine check skill-trust <path> Scan one skill/agent Markdown for trust violations
32
+ devrites-engine observe summary <slug> Emit sanitized JSON workspace summary
33
+ devrites-engine parallel <subcommand> Deterministic parallel worktree lease/create/integrate/cleanup
27
34
  devrites-engine state resolve <qid> "<ans>" Resolve an open question and update state atomically
28
35
  devrites-engine state close <slug> Archive a shipped feature and clear ACTIVE
29
36
  devrites-engine secret-scan [--staged] [--stdin] [slug] Scan exact staged blobs, stdin, or touched files; HIGH blocks
@@ -71,7 +78,11 @@ func run(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
71
78
  case "uninstall":
72
79
  return install.Run(args[1:], stdout, stderr, install.ModeUninstall)
73
80
  case "check":
74
- return cmdCheck(args[1:], stdout, stderr)
81
+ return cmdCheck(root, args[1:], stdin, stdout, stderr)
82
+ case "parallel":
83
+ return parallel.Run("parallel", args[1:], stdin, stdout, stderr)
84
+ case "observe":
85
+ return cmdObserve(root, args[1:], stdout, stderr)
75
86
  case "state":
76
87
  return cmdState(root, args[1:], stdout, stderr)
77
88
  case "secret-scan":
@@ -88,9 +99,9 @@ func run(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
88
99
  }
89
100
  }
90
101
 
91
- func cmdCheck(args []string, stdout, stderr io.Writer) int {
102
+ func cmdCheck(root string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
92
103
  if len(args) == 0 {
93
- fmt.Fprintln(stderr, "usage: devrites-engine check <candidate|readiness|seal> ...")
104
+ fmt.Fprintln(stderr, "usage: devrites-engine check <candidate|readiness|seal|path-disjoint|task-graph|skill-trust> ...")
94
105
  return exitUsage
95
106
  }
96
107
  sub, rest := args[0], args[1:]
@@ -99,6 +110,12 @@ func cmdCheck(args []string, stdout, stderr io.Writer) int {
99
110
  return cmdCandidate(rest, stdout, stderr)
100
111
  case "readiness", "seal":
101
112
  return cmdGate(gate.Kind(sub), rest, stdout, stderr)
113
+ case "path-disjoint":
114
+ return parallel.Run("path-disjoint", rest, stdin, stdout, stderr)
115
+ case "task-graph":
116
+ return cmdTaskGraph(root, rest, stdout, stderr)
117
+ case "skill-trust":
118
+ return cmdSkillTrust(rest, stdout, stderr)
102
119
  default:
103
120
  fmt.Fprintf(stderr, "devrites: unknown check %q\n", sub)
104
121
  return exitUsage
@@ -185,3 +202,55 @@ func cmdGate(kind gate.Kind, args []string, stdout, stderr io.Writer) int {
185
202
  fmt.Fprint(stdout, result.Render())
186
203
  return exitOK
187
204
  }
205
+
206
+ func cmdTaskGraph(root string, args []string, stdout, stderr io.Writer) int {
207
+ if len(args) != 1 {
208
+ fmt.Fprintln(stderr, "usage: devrites-engine check task-graph <slug>")
209
+ return exitUsage
210
+ }
211
+ if root == "" {
212
+ var err error
213
+ root, err = state.ResolveRoot(os.Getenv("DEVRITES_ROOT"))
214
+ if err != nil {
215
+ fmt.Fprintf(stderr, "devrites: %v\n", err)
216
+ return exitUsage
217
+ }
218
+ }
219
+ return lib.RunTaskGraphCheck(root, args[0], stdout, stderr)
220
+ }
221
+
222
+ func cmdSkillTrust(args []string, stdout, stderr io.Writer) int {
223
+ if len(args) != 1 {
224
+ fmt.Fprintln(stderr, "usage: devrites-engine check skill-trust <path>")
225
+ return exitUsage
226
+ }
227
+ return lib.RunSkillTrustCheck(args[0], stdout, stderr)
228
+ }
229
+
230
+ func cmdObserve(root string, args []string, stdout, stderr io.Writer) int {
231
+ if len(args) == 0 {
232
+ fmt.Fprintln(stderr, "usage: devrites-engine observe summary <slug>")
233
+ return exitUsage
234
+ }
235
+ if args[0] != "summary" {
236
+ fmt.Fprintf(stderr, "devrites: unknown observe command %q\n", args[0])
237
+ return exitUsage
238
+ }
239
+ if root == "" {
240
+ var err error
241
+ root, err = state.ResolveRoot(os.Getenv("DEVRITES_ROOT"))
242
+ if err != nil {
243
+ fmt.Fprintf(stderr, "devrites: %v\n", err)
244
+ return exitUsage
245
+ }
246
+ }
247
+ slug, code, err := lib.ActiveSlug(root, args[1:])
248
+ if err != nil {
249
+ fmt.Fprintf(stderr, "observe: %v\n", err)
250
+ if code == 0 {
251
+ code = exitUsage
252
+ }
253
+ return code
254
+ }
255
+ return lib.RunObserveSummary(root, slug, stdout, stderr)
256
+ }
@@ -126,7 +126,7 @@ func TestNestedCommandFamilyUsageListsOnlyRetainedCommands(t *testing.T) {
126
126
  want string
127
127
  removed []string
128
128
  }{
129
- {args: []string{"check"}, want: "check <candidate|readiness|seal>", removed: []string{"spec"}},
129
+ {args: []string{"check"}, want: "check <candidate|readiness|seal|path-disjoint|task-graph|skill-trust>", removed: []string{"spec"}},
130
130
  {args: []string{"state"}, want: "state <resolve|close>", removed: []string{"clarify", "tick-afk", "recovery"}},
131
131
  } {
132
132
  t.Run(test.args[0], func(t *testing.T) {
@@ -13,31 +13,34 @@ import (
13
13
  )
14
14
 
15
15
  func TestADR0004RequiredSectionsAreAdditiveDownTheArc(t *testing.T) {
16
- phaseArc := state.LifecyclePhases()
17
- for _, p := range phaseArc {
18
- if !state.KnownPhase(p) {
19
- t.Fatalf("phase %q in the arc is not a known phase", p)
16
+ policies := state.PhasePolicies()
17
+ for _, policy := range policies {
18
+ lookedUp, ok := state.PolicyFor(policy.Target)
19
+ if !ok || lookedUp.Target != policy.Target {
20
+ t.Fatalf("phase %q in the arc is not a known phase", policy.Target)
20
21
  }
21
22
  }
22
23
 
23
24
  // Each phase's required set must contain everything the previous phase
24
25
  // required: completeness only accumulates, never regresses.
25
- for i := 1; i < len(phaseArc); i++ {
26
- prev := asSet(state.RequiredSections(phaseArc[i-1]))
27
- cur := asSet(state.RequiredSections(phaseArc[i]))
28
- for s := range prev {
29
- if !cur[s] {
26
+ for i := 1; i < len(policies); i++ {
27
+ previous := asSet(policies[i-1].RequiredSections)
28
+ current := asSet(policies[i].RequiredSections)
29
+ for section := range previous {
30
+ if !current[section] {
30
31
  t.Errorf("phase %q dropped section %q that %q required: arc must be additive",
31
- phaseArc[i], s, phaseArc[i-1])
32
+ policies[i].Target, section, policies[i-1].Target)
32
33
  }
33
34
  }
34
35
  }
35
36
 
36
37
  // Anchors: framing requires nothing; seal requires the full section set.
37
- if got := state.RequiredSections(state.PhaseFrame); len(got) != 0 {
38
- t.Errorf("frame requires %v, want none", got)
38
+ frame, _ := state.PolicyFor(state.PhaseFrame)
39
+ if len(frame.RequiredSections) != 0 {
40
+ t.Errorf("frame requires %v, want none", frame.RequiredSections)
39
41
  }
40
- if got, want := len(state.RequiredSections(state.PhaseSeal)), len(state.Sections); got != want {
42
+ seal, _ := state.PolicyFor(state.PhaseSeal)
43
+ if got, want := len(seal.RequiredSections), len(state.Sections); got != want {
41
44
  t.Errorf("seal requires %d sections, want the full set of %d", got, want)
42
45
  }
43
46
  }
@@ -7,24 +7,26 @@ import (
7
7
  )
8
8
 
9
9
  func TestADR0011DefineAndPlanHaveDistinctTransitionRights(t *testing.T) {
10
- if got := state.ResumeVerb(state.PhaseDefine); got != "define" {
11
- t.Fatalf("define resumes %q, want define", got)
10
+ define, ok := state.PolicyFor(state.PhaseDefine)
11
+ if !ok || define.ResumeVerb != "define" {
12
+ t.Fatalf("define policy=(%+v,%v), want resume verb define", define, ok)
12
13
  }
13
- if got := state.ResumeVerb(state.PhasePlan); got != "vet" {
14
- t.Fatalf("plan resumes %q, want vet", got)
14
+ plan, ok := state.PolicyFor(state.PhasePlan)
15
+ if !ok || plan.ResumeVerb != "vet" {
16
+ t.Fatalf("plan policy=(%+v,%v), want resume verb vet", plan, ok)
15
17
  }
16
- if got, ok := state.PhaseForName("planning"); ok || got != "" {
17
- t.Fatalf("planning alias=(%q,%v), want speculative alias rejected", got, ok)
18
+ if policy, ok := state.PolicyFor(state.Phase("planning")); ok {
19
+ t.Fatalf("planning alias=(%+v,true), want speculative alias rejected", policy)
18
20
  }
19
21
 
20
22
  rights := map[string]state.Phase{}
21
- for _, phase := range state.WorkflowPhases() {
22
- if phase.TransitionRight == "" {
23
- t.Fatalf("phase %q has no transition right", phase.ID)
23
+ for _, policy := range state.PhasePolicies() {
24
+ if policy.TransitionRight == "" {
25
+ t.Fatalf("phase %q has no transition right", policy.Target)
24
26
  }
25
- if prior := rights[phase.TransitionRight]; prior != "" {
26
- t.Fatalf("phases %q and %q share transition right %q", prior, phase.ID, phase.TransitionRight)
27
+ if prior := rights[policy.TransitionRight]; prior != "" {
28
+ t.Fatalf("phases %q and %q share transition right %q", prior, policy.Target, policy.TransitionRight)
27
29
  }
28
- rights[phase.TransitionRight] = phase.ID
30
+ rights[policy.TransitionRight] = policy.Target
29
31
  }
30
32
  }