devrites 3.0.2 → 3.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +1 -1
- package/docs/adr/0001-go-engine-as-control-plane.md +43 -0
- package/docs/adr/0002-dual-host-harness.md +34 -0
- package/docs/adr/0003-gate-model-hitl-pause.md +38 -0
- package/docs/adr/0004-state-schema-phases-sections.md +47 -0
- package/docs/adr/0005-hooks-as-engine-subcommands.md +39 -0
- package/docs/adr/0006-clock-seam-and-engine-ci-gates.md +50 -0
- package/docs/adr/0007-canonical-live-workspace-filenames.md +36 -0
- package/docs/adr/0008-sanctioned-engine-network-boundary.md +33 -0
- package/docs/adr/README.md +58 -0
- package/docs/command-map.md +1 -1
- package/docs/engine/state-schema.md +18 -14
- package/docs/flow.md +1 -1
- package/docs/research/go-authoritative-workflow-schema-and-quality-2026-07-20.md +212 -0
- package/engine/hooks_workspace.go +7 -7
- package/engine/internal/gate/gate.go +4 -4
- package/engine/internal/gate/gate_test.go +2 -2
- package/engine/internal/lib/cursor_compat_test.go +29 -0
- package/engine/internal/lib/preamble.go +1 -1
- package/engine/internal/lib/preamble_questions_test.go +10 -0
- package/engine/internal/lib/progress.go +59 -20
- package/engine/internal/lib/resolve.go +15 -6
- package/engine/internal/lib/resolve_remediation_test.go +9 -0
- package/engine/internal/lib/tickafk.go +2 -2
- package/engine/internal/migrate/migrate.go +18 -44
- package/engine/internal/migrate/migrate_test.go +33 -5
- package/engine/internal/state/cmd/workflowmanifest/main.go +54 -0
- package/engine/internal/state/cursor.go +42 -7
- package/engine/internal/state/feature.go +29 -41
- package/engine/internal/state/schema.go +155 -28
- package/engine/internal/state/snapshot.go +49 -3
- package/engine/internal/state/state_test.go +77 -6
- package/engine/internal/state/workflow_manifest.json +310 -0
- package/engine/internal/workflow/commands.go +0 -36
- package/engine/internal/workflow/commands_test.go +0 -17
- package/engine/testdata/golden/TestParityProgress/arg=allbuilt.golden +1 -1
- package/engine/testdata/golden/TestParityProgress/arg=done.golden +1 -1
- package/engine/testdata/golden/TestParityProgress/arg=mid.golden +1 -1
- package/engine/testdata/golden/TestParityProgress/arg=nophase.golden +1 -1
- package/engine/testdata/golden/TestParityProgress/arg=noslice.golden +1 -1
- package/engine/testdata/golden/TestParityProgress/arg=plan.golden +1 -1
- package/engine/testdata/golden/TestParityProgress/arg=seal.golden +1 -1
- package/engine/tests/adr_0004_required_by_phase_test.go +1 -18
- package/engine/tests/budget_test.go +2 -2
- package/engine/tests/lib_parity_test.go +1 -1
- package/engine/tests/meta_test.go +3 -3
- package/engine/tests/migrate_cli_test.go +24 -22
- package/pack/.claude/skills/devrites-lib/reference/workspace-artifact-schema.md +5 -3
- package/pack/generated/claude/skills/devrites-lib/reference/workspace-artifact-schema.md +5 -3
- package/pack/generated/codex/AGENTS.md +1 -1
- package/pack/generated/codex/skills/devrites-lib/reference/workspace-artifact-schema.md +5 -3
- package/package.json +2 -1
- package/scripts/codex-generate.sh +1 -1
- package/scripts/grade-feature.sh +5 -3
- package/scripts/run-outcome-evals.sh +21 -0
- package/scripts/validate-workflow-security.py +3 -8
- package/scripts/validate-workspace-schema.py +9 -73
- package/scripts/validate.sh +10 -0
- package/scripts/workflow_schema.py +69 -0
|
@@ -29,6 +29,56 @@ func TestRequiredSectionsIsPhaseRelativeAndOrdered(t *testing.T) {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
func TestLifecycleRegistryOwnsOrderAndResumeCommands(t *testing.T) {
|
|
33
|
+
phases := LifecyclePhases()
|
|
34
|
+
if len(phases) == 0 || phases[0] != PhaseFrame || phases[len(phases)-1] != PhaseDone {
|
|
35
|
+
t.Fatalf("LifecyclePhases()=%v, want frame...done", phases)
|
|
36
|
+
}
|
|
37
|
+
if got := ResumeVerb(PhasePlan); got != "define" {
|
|
38
|
+
t.Fatalf("ResumeVerb(plan)=%q, want define", got)
|
|
39
|
+
}
|
|
40
|
+
if got := ResumeVerb(PhaseDone); got != "" {
|
|
41
|
+
t.Fatalf("ResumeVerb(done)=%q, want empty", got)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
phases[0] = PhaseDone
|
|
45
|
+
if got := LifecyclePhases()[0]; got != PhaseFrame {
|
|
46
|
+
t.Fatalf("LifecyclePhases exposed mutable registry: first=%q", got)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
func TestLifecycleRegistryInvariants(t *testing.T) {
|
|
51
|
+
phaseNames := map[Phase]bool{}
|
|
52
|
+
aliases := map[string]Phase{}
|
|
53
|
+
for i, definition := range phaseDefinitions {
|
|
54
|
+
if definition.phase == "" || phaseNames[definition.phase] {
|
|
55
|
+
t.Fatalf("phase definition %d has empty or duplicate ID %q", i, definition.phase)
|
|
56
|
+
}
|
|
57
|
+
phaseNames[definition.phase] = true
|
|
58
|
+
if len(definition.workspaceRequired) == 0 {
|
|
59
|
+
t.Fatalf("phase %q has no workspace requirements", definition.phase)
|
|
60
|
+
}
|
|
61
|
+
for _, alias := range definition.aliases {
|
|
62
|
+
if alias == "" || aliases[alias] != "" || KnownPhase(Phase(alias)) {
|
|
63
|
+
t.Fatalf("phase %q has empty or duplicate alias %q", definition.phase, alias)
|
|
64
|
+
}
|
|
65
|
+
aliases[alias] = definition.phase
|
|
66
|
+
}
|
|
67
|
+
for _, section := range definition.required {
|
|
68
|
+
known := false
|
|
69
|
+
for _, canonical := range Sections {
|
|
70
|
+
known = known || section == canonical
|
|
71
|
+
}
|
|
72
|
+
if !known {
|
|
73
|
+
t.Fatalf("phase %q requires unknown section %q", definition.phase, section)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if definition.shippable && !definition.proofRequired {
|
|
77
|
+
t.Fatalf("shippable phase %q does not require proof", definition.phase)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
32
82
|
func TestStatusFixtureBuildIncomplete(t *testing.T) {
|
|
33
83
|
rep, err := Status(fixtureRoot, "auth-tokens")
|
|
34
84
|
if err != nil {
|
|
@@ -88,9 +138,9 @@ func writeWorkSection(t *testing.T, root, slug, name, body string) {
|
|
|
88
138
|
}
|
|
89
139
|
}
|
|
90
140
|
|
|
91
|
-
// A live workspace
|
|
92
|
-
// the state.md ledger and the proof/status
|
|
93
|
-
//
|
|
141
|
+
// A live workspace map need not carry frontmatter: the phase lives in
|
|
142
|
+
// the canonical state.md ledger and the proof/status concepts are satisfied by
|
|
143
|
+
// evidence.md/state.md. The engine must load, list, and report it anyway.
|
|
94
144
|
func TestLoadFeatureFromLedgerAndAliases(t *testing.T) {
|
|
95
145
|
root := filepath.Join(t.TempDir(), ".devrites")
|
|
96
146
|
writeSection(t, root, "live", "state.md", "- Phase: prove\n- Status: running\n")
|
|
@@ -98,7 +148,7 @@ func TestLoadFeatureFromLedgerAndAliases(t *testing.T) {
|
|
|
98
148
|
writeSection(t, root, "live", "plan.md", "# Plan\n\nApproach.\n")
|
|
99
149
|
writeSection(t, root, "live", "decisions.md", "# Decisions\n\nChose X.\n")
|
|
100
150
|
writeSection(t, root, "live", "tasks.md", "# Tasks\n\n- [x] slice 1\n")
|
|
101
|
-
writeSection(t, root, "live", "evidence.md", "# Evidence\n\nTests pass.\n")
|
|
151
|
+
writeSection(t, root, "live", "evidence.md", "# Evidence\n\nTests pass.\n")
|
|
102
152
|
|
|
103
153
|
rep, err := Status(root, "live")
|
|
104
154
|
if err != nil {
|
|
@@ -108,10 +158,10 @@ func TestLoadFeatureFromLedgerAndAliases(t *testing.T) {
|
|
|
108
158
|
t.Errorf("phase = %q, want prove (from the state.md ledger)", rep.Phase)
|
|
109
159
|
}
|
|
110
160
|
if !rep.Present[SectionProof] {
|
|
111
|
-
t.Error("proof section should be present via
|
|
161
|
+
t.Error("proof section should be present via canonical evidence.md")
|
|
112
162
|
}
|
|
113
163
|
if !rep.Present[SectionStatus] {
|
|
114
|
-
t.Error("status section should be present via
|
|
164
|
+
t.Error("status section should be present via canonical state.md")
|
|
115
165
|
}
|
|
116
166
|
if !rep.Complete() {
|
|
117
167
|
t.Errorf("prove-phase feature should be complete, missing: %v", rep.Missing)
|
|
@@ -172,6 +222,27 @@ func TestSnapshotUsesCanonicalNextActionAndWarnsWhenRequiredProofMissing(t *test
|
|
|
172
222
|
}
|
|
173
223
|
}
|
|
174
224
|
|
|
225
|
+
func TestSnapshotReadsCanonicalActiveSliceAndCountsQuestionsByRecord(t *testing.T) {
|
|
226
|
+
workDir := t.TempDir()
|
|
227
|
+
if err := os.WriteFile(filepath.Join(workDir, "state.md"), []byte("| phase | build |\n| active_slice | SLICE-002 |\n"), 0o644); err != nil {
|
|
228
|
+
t.Fatal(err)
|
|
229
|
+
}
|
|
230
|
+
if err := os.WriteFile(filepath.Join(workDir, "tasks.md"), []byte("## SLICE-001 First\n\n## SLICE-002 Second\n"), 0o644); err != nil {
|
|
231
|
+
t.Fatal(err)
|
|
232
|
+
}
|
|
233
|
+
if err := os.WriteFile(filepath.Join(workDir, "questions.md"), []byte("## Q-001\nstatus: open\ngate: blocking\n\n## Q-002\nstatus: answered\ngate: blocking\n"), 0o644); err != nil {
|
|
234
|
+
t.Fatal(err)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
slice := currentSlice(workDir)
|
|
238
|
+
if slice == nil || slice.Name != "SLICE-002" || slice.Index != 2 || slice.Total != 2 {
|
|
239
|
+
t.Fatalf("currentSlice=%+v, want canonical SLICE-002 at 2/2", slice)
|
|
240
|
+
}
|
|
241
|
+
if drift := driftSummary(workDir); drift.Status != "open" || drift.Open != 1 {
|
|
242
|
+
t.Fatalf("driftSummary=%+v, want one open question record", drift)
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
175
246
|
func TestWorkLayoutIsCanonicalAndFeaturesIsAlias(t *testing.T) {
|
|
176
247
|
root := filepath.Join(t.TempDir(), ".devrites")
|
|
177
248
|
writeWorkSection(t, root, "live", "state.md", "- Phase: build\n")
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
{
|
|
2
|
+
"generatedBy": "go generate ./internal/state; DO NOT EDIT",
|
|
3
|
+
"schemaVersion": 1,
|
|
4
|
+
"phases": [
|
|
5
|
+
{
|
|
6
|
+
"id": "frame",
|
|
7
|
+
"resumeVerb": "frame",
|
|
8
|
+
"workspaceRequired": [
|
|
9
|
+
"state.md"
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"id": "spec",
|
|
14
|
+
"resumeVerb": "spec",
|
|
15
|
+
"aliases": [
|
|
16
|
+
"specced",
|
|
17
|
+
"specifying"
|
|
18
|
+
],
|
|
19
|
+
"workspaceRequired": [
|
|
20
|
+
"brief.md",
|
|
21
|
+
"spec.md",
|
|
22
|
+
"state.md",
|
|
23
|
+
"decisions.md",
|
|
24
|
+
"assumptions.md",
|
|
25
|
+
"questions.md"
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "temper",
|
|
30
|
+
"resumeVerb": "temper",
|
|
31
|
+
"aliases": [
|
|
32
|
+
"tempered",
|
|
33
|
+
"tempering"
|
|
34
|
+
],
|
|
35
|
+
"workspaceRequired": [
|
|
36
|
+
"brief.md",
|
|
37
|
+
"spec.md",
|
|
38
|
+
"state.md",
|
|
39
|
+
"decisions.md",
|
|
40
|
+
"assumptions.md",
|
|
41
|
+
"questions.md"
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"id": "define",
|
|
46
|
+
"resumeVerb": "define",
|
|
47
|
+
"aliases": [
|
|
48
|
+
"defined",
|
|
49
|
+
"defining"
|
|
50
|
+
],
|
|
51
|
+
"workspaceRequired": [
|
|
52
|
+
"brief.md",
|
|
53
|
+
"spec.md",
|
|
54
|
+
"architecture.md",
|
|
55
|
+
"plan.md",
|
|
56
|
+
"tasks.md",
|
|
57
|
+
"traceability.md",
|
|
58
|
+
"state.md",
|
|
59
|
+
"decisions.md",
|
|
60
|
+
"assumptions.md",
|
|
61
|
+
"questions.md"
|
|
62
|
+
],
|
|
63
|
+
"blocksOpenQuestions": true
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"id": "plan",
|
|
67
|
+
"resumeVerb": "define",
|
|
68
|
+
"aliases": [
|
|
69
|
+
"planned",
|
|
70
|
+
"planning"
|
|
71
|
+
],
|
|
72
|
+
"workspaceRequired": [
|
|
73
|
+
"brief.md",
|
|
74
|
+
"spec.md",
|
|
75
|
+
"architecture.md",
|
|
76
|
+
"plan.md",
|
|
77
|
+
"tasks.md",
|
|
78
|
+
"traceability.md",
|
|
79
|
+
"state.md",
|
|
80
|
+
"decisions.md",
|
|
81
|
+
"assumptions.md",
|
|
82
|
+
"questions.md"
|
|
83
|
+
],
|
|
84
|
+
"blocksOpenQuestions": true
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"id": "vet",
|
|
88
|
+
"resumeVerb": "vet",
|
|
89
|
+
"aliases": [
|
|
90
|
+
"vetted",
|
|
91
|
+
"vetting"
|
|
92
|
+
],
|
|
93
|
+
"workspaceRequired": [
|
|
94
|
+
"brief.md",
|
|
95
|
+
"spec.md",
|
|
96
|
+
"architecture.md",
|
|
97
|
+
"plan.md",
|
|
98
|
+
"tasks.md",
|
|
99
|
+
"traceability.md",
|
|
100
|
+
"state.md",
|
|
101
|
+
"decisions.md",
|
|
102
|
+
"assumptions.md",
|
|
103
|
+
"questions.md"
|
|
104
|
+
],
|
|
105
|
+
"blocksOpenQuestions": true
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"id": "build",
|
|
109
|
+
"resumeVerb": "build",
|
|
110
|
+
"aliases": [
|
|
111
|
+
"building",
|
|
112
|
+
"wip",
|
|
113
|
+
"in",
|
|
114
|
+
"in-progress"
|
|
115
|
+
],
|
|
116
|
+
"workspaceRequired": [
|
|
117
|
+
"brief.md",
|
|
118
|
+
"spec.md",
|
|
119
|
+
"architecture.md",
|
|
120
|
+
"plan.md",
|
|
121
|
+
"tasks.md",
|
|
122
|
+
"traceability.md",
|
|
123
|
+
"state.md",
|
|
124
|
+
"decisions.md",
|
|
125
|
+
"assumptions.md",
|
|
126
|
+
"questions.md"
|
|
127
|
+
],
|
|
128
|
+
"blocksOpenQuestions": true
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"id": "converge",
|
|
132
|
+
"resumeVerb": "converge",
|
|
133
|
+
"aliases": [
|
|
134
|
+
"converged",
|
|
135
|
+
"converging"
|
|
136
|
+
],
|
|
137
|
+
"workspaceRequired": [
|
|
138
|
+
"brief.md",
|
|
139
|
+
"spec.md",
|
|
140
|
+
"architecture.md",
|
|
141
|
+
"plan.md",
|
|
142
|
+
"tasks.md",
|
|
143
|
+
"traceability.md",
|
|
144
|
+
"state.md",
|
|
145
|
+
"decisions.md",
|
|
146
|
+
"assumptions.md",
|
|
147
|
+
"questions.md"
|
|
148
|
+
],
|
|
149
|
+
"blocksOpenQuestions": true
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
"id": "prove",
|
|
153
|
+
"resumeVerb": "prove",
|
|
154
|
+
"aliases": [
|
|
155
|
+
"proving",
|
|
156
|
+
"proven",
|
|
157
|
+
"testing"
|
|
158
|
+
],
|
|
159
|
+
"workspaceRequired": [
|
|
160
|
+
"brief.md",
|
|
161
|
+
"spec.md",
|
|
162
|
+
"architecture.md",
|
|
163
|
+
"plan.md",
|
|
164
|
+
"tasks.md",
|
|
165
|
+
"traceability.md",
|
|
166
|
+
"state.md",
|
|
167
|
+
"decisions.md",
|
|
168
|
+
"assumptions.md",
|
|
169
|
+
"questions.md",
|
|
170
|
+
"evidence.md",
|
|
171
|
+
"touched-files.md"
|
|
172
|
+
],
|
|
173
|
+
"proofRequired": true,
|
|
174
|
+
"blocksOpenQuestions": true
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
"id": "polish",
|
|
178
|
+
"resumeVerb": "polish",
|
|
179
|
+
"aliases": [
|
|
180
|
+
"polished",
|
|
181
|
+
"polishing"
|
|
182
|
+
],
|
|
183
|
+
"workspaceRequired": [
|
|
184
|
+
"brief.md",
|
|
185
|
+
"spec.md",
|
|
186
|
+
"architecture.md",
|
|
187
|
+
"plan.md",
|
|
188
|
+
"tasks.md",
|
|
189
|
+
"traceability.md",
|
|
190
|
+
"state.md",
|
|
191
|
+
"decisions.md",
|
|
192
|
+
"assumptions.md",
|
|
193
|
+
"questions.md",
|
|
194
|
+
"evidence.md",
|
|
195
|
+
"touched-files.md"
|
|
196
|
+
],
|
|
197
|
+
"proofRequired": true,
|
|
198
|
+
"blocksOpenQuestions": true
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
"id": "review",
|
|
202
|
+
"resumeVerb": "review",
|
|
203
|
+
"aliases": [
|
|
204
|
+
"reviewed",
|
|
205
|
+
"reviewing"
|
|
206
|
+
],
|
|
207
|
+
"workspaceRequired": [
|
|
208
|
+
"brief.md",
|
|
209
|
+
"spec.md",
|
|
210
|
+
"architecture.md",
|
|
211
|
+
"plan.md",
|
|
212
|
+
"tasks.md",
|
|
213
|
+
"traceability.md",
|
|
214
|
+
"state.md",
|
|
215
|
+
"decisions.md",
|
|
216
|
+
"assumptions.md",
|
|
217
|
+
"questions.md",
|
|
218
|
+
"evidence.md",
|
|
219
|
+
"touched-files.md"
|
|
220
|
+
],
|
|
221
|
+
"proofRequired": true,
|
|
222
|
+
"blocksOpenQuestions": true
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
"id": "seal",
|
|
226
|
+
"resumeVerb": "seal",
|
|
227
|
+
"aliases": [
|
|
228
|
+
"sealed",
|
|
229
|
+
"sealing"
|
|
230
|
+
],
|
|
231
|
+
"workspaceRequired": [
|
|
232
|
+
"brief.md",
|
|
233
|
+
"spec.md",
|
|
234
|
+
"architecture.md",
|
|
235
|
+
"plan.md",
|
|
236
|
+
"tasks.md",
|
|
237
|
+
"traceability.md",
|
|
238
|
+
"state.md",
|
|
239
|
+
"decisions.md",
|
|
240
|
+
"assumptions.md",
|
|
241
|
+
"questions.md",
|
|
242
|
+
"evidence.md",
|
|
243
|
+
"touched-files.md"
|
|
244
|
+
],
|
|
245
|
+
"proofRequired": true,
|
|
246
|
+
"blocksOpenQuestions": true,
|
|
247
|
+
"shippable": true
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
"id": "ship",
|
|
251
|
+
"resumeVerb": "ship",
|
|
252
|
+
"aliases": [
|
|
253
|
+
"shipped",
|
|
254
|
+
"shipping"
|
|
255
|
+
],
|
|
256
|
+
"workspaceRequired": [
|
|
257
|
+
"brief.md",
|
|
258
|
+
"spec.md",
|
|
259
|
+
"architecture.md",
|
|
260
|
+
"plan.md",
|
|
261
|
+
"tasks.md",
|
|
262
|
+
"traceability.md",
|
|
263
|
+
"state.md",
|
|
264
|
+
"decisions.md",
|
|
265
|
+
"assumptions.md",
|
|
266
|
+
"questions.md",
|
|
267
|
+
"evidence.md",
|
|
268
|
+
"touched-files.md"
|
|
269
|
+
],
|
|
270
|
+
"proofRequired": true,
|
|
271
|
+
"blocksOpenQuestions": true,
|
|
272
|
+
"shippable": true
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
"id": "done",
|
|
276
|
+
"aliases": [
|
|
277
|
+
"closed",
|
|
278
|
+
"complete",
|
|
279
|
+
"completed"
|
|
280
|
+
],
|
|
281
|
+
"workspaceRequired": [
|
|
282
|
+
"brief.md",
|
|
283
|
+
"spec.md",
|
|
284
|
+
"architecture.md",
|
|
285
|
+
"plan.md",
|
|
286
|
+
"tasks.md",
|
|
287
|
+
"traceability.md",
|
|
288
|
+
"state.md",
|
|
289
|
+
"decisions.md",
|
|
290
|
+
"assumptions.md",
|
|
291
|
+
"questions.md",
|
|
292
|
+
"evidence.md",
|
|
293
|
+
"touched-files.md"
|
|
294
|
+
],
|
|
295
|
+
"proofRequired": true,
|
|
296
|
+
"blocksOpenQuestions": true,
|
|
297
|
+
"shippable": true
|
|
298
|
+
}
|
|
299
|
+
],
|
|
300
|
+
"cursorKeyAliases": [
|
|
301
|
+
{
|
|
302
|
+
"alias": "nextstep",
|
|
303
|
+
"canonical": "next_action"
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
"alias": "qid",
|
|
307
|
+
"canonical": "question_id"
|
|
308
|
+
}
|
|
309
|
+
]
|
|
310
|
+
}
|
|
@@ -12,22 +12,6 @@ type Command struct {
|
|
|
12
12
|
Codex string
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
var phaseToVerb = map[string]string{
|
|
16
|
-
"frame": "frame",
|
|
17
|
-
"spec": "spec",
|
|
18
|
-
"temper": "temper",
|
|
19
|
-
"define": "define",
|
|
20
|
-
"plan": "define", // plan is the artifact state produced by rite-define.
|
|
21
|
-
"vet": "vet",
|
|
22
|
-
"build": "build",
|
|
23
|
-
"converge": "converge",
|
|
24
|
-
"prove": "prove",
|
|
25
|
-
"polish": "polish",
|
|
26
|
-
"review": "review",
|
|
27
|
-
"seal": "seal",
|
|
28
|
-
"ship": "ship",
|
|
29
|
-
}
|
|
30
|
-
|
|
31
15
|
// ForVerb returns the Claude and Codex forms for a public rite verb. The empty
|
|
32
16
|
// verb returns an empty command so callers can omit suggestions safely.
|
|
33
17
|
func ForVerb(verb string) Command {
|
|
@@ -52,12 +36,6 @@ func ForAction(action string) Command {
|
|
|
52
36
|
return Command{}
|
|
53
37
|
}
|
|
54
38
|
|
|
55
|
-
// ForPhase returns the next public command for a workflow phase.
|
|
56
|
-
func ForPhase(phase string) Command {
|
|
57
|
-
verb := phaseToVerb[strings.TrimSpace(strings.ToLower(phase))]
|
|
58
|
-
return ForVerb(verb)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
39
|
// Both renders a command in host-neutral prose for engine stderr/stdout messages.
|
|
62
40
|
func (c Command) Both() string {
|
|
63
41
|
if c.Verb == "" {
|
|
@@ -65,17 +43,3 @@ func (c Command) Both() string {
|
|
|
65
43
|
}
|
|
66
44
|
return c.Claude + " (Claude) / " + c.Codex + " (Codex)"
|
|
67
45
|
}
|
|
68
|
-
|
|
69
|
-
// ClaudeOrCodex returns the command for a known harness. Empty/unknown harnesses
|
|
70
|
-
// deliberately get the host-neutral rendering so CLI output remains safe when the
|
|
71
|
-
// engine is run outside a hook.
|
|
72
|
-
func (c Command) ClaudeOrCodex(harness string) string {
|
|
73
|
-
switch strings.ToLower(strings.TrimSpace(harness)) {
|
|
74
|
-
case "claude", "claude-code", "claudecode":
|
|
75
|
-
return c.Claude
|
|
76
|
-
case "codex":
|
|
77
|
-
return c.Codex
|
|
78
|
-
default:
|
|
79
|
-
return c.Both()
|
|
80
|
-
}
|
|
81
|
-
}
|
|
@@ -16,20 +16,3 @@ func TestForActionExtractsCanonicalInvocation(t *testing.T) {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
-
|
|
20
|
-
func TestForPhaseCoversCurrentLifecycle(t *testing.T) {
|
|
21
|
-
for _, phase := range []string{
|
|
22
|
-
"frame", "spec", "temper", "define", "vet", "build", "converge",
|
|
23
|
-
"prove", "polish", "review", "seal", "ship",
|
|
24
|
-
} {
|
|
25
|
-
if got := ForPhase(phase).Verb; got != phase {
|
|
26
|
-
t.Fatalf("ForPhase(%q).Verb=%q, want %q", phase, got, phase)
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
if got := ForPhase("plan").Verb; got != "define" {
|
|
30
|
-
t.Fatalf("ForPhase(plan).Verb=%q, want define compatibility route", got)
|
|
31
|
-
}
|
|
32
|
-
if got := ForPhase("done").Verb; got != "" {
|
|
33
|
-
t.Fatalf("ForPhase(done).Verb=%q, want empty", got)
|
|
34
|
-
}
|
|
35
|
-
}
|
|
@@ -12,25 +12,8 @@ import (
|
|
|
12
12
|
"github.com/devrites/devrites/internal/state"
|
|
13
13
|
)
|
|
14
14
|
|
|
15
|
-
// phaseArc is the canonical order the lifecycle advances through (ADR-0004).
|
|
16
|
-
var phaseArc = []state.Phase{
|
|
17
|
-
state.PhaseFrame,
|
|
18
|
-
state.PhaseSpec,
|
|
19
|
-
state.PhaseTemper,
|
|
20
|
-
state.PhaseDefine,
|
|
21
|
-
state.PhasePlan,
|
|
22
|
-
state.PhaseVet,
|
|
23
|
-
state.PhaseBuild,
|
|
24
|
-
state.PhaseConverge,
|
|
25
|
-
state.PhaseProve,
|
|
26
|
-
state.PhasePolish,
|
|
27
|
-
state.PhaseReview,
|
|
28
|
-
state.PhaseSeal,
|
|
29
|
-
state.PhaseShip,
|
|
30
|
-
state.PhaseDone,
|
|
31
|
-
}
|
|
32
|
-
|
|
33
15
|
func TestADR0004RequiredSectionsAreAdditiveDownTheArc(t *testing.T) {
|
|
16
|
+
phaseArc := state.LifecyclePhases()
|
|
34
17
|
for _, p := range phaseArc {
|
|
35
18
|
if !state.KnownPhase(p) {
|
|
36
19
|
t.Fatalf("phase %q in the arc is not a known phase", p)
|
|
@@ -7,8 +7,8 @@ import (
|
|
|
7
7
|
|
|
8
8
|
// TestStatusLiveWorkspace is the P2 acceptance check: `devrites-engine status <slug>`
|
|
9
9
|
// must report a canonical work/<slug> feature the live pack created without a
|
|
10
|
-
//
|
|
11
|
-
// evidence.md/state.md
|
|
10
|
+
// workspace-map frontmatter — phase from the canonical state.md ledger and proof/status
|
|
11
|
+
// completeness from canonical evidence.md/state.md files. Before schema unification this returned
|
|
12
12
|
// "feature not found".
|
|
13
13
|
func TestStatusLiveWorkspace(t *testing.T) {
|
|
14
14
|
work := t.TempDir()
|
|
@@ -795,7 +795,7 @@ func TestParityPreamble(t *testing.T) {
|
|
|
795
795
|
//
|
|
796
796
|
// Golden: the header rule width, the slice meter (round-half math, ✅ ALL BUILT
|
|
797
797
|
// vs the last-built tail), and the flow ribbon (conditional temper/vet, the
|
|
798
|
-
// plan
|
|
798
|
+
// explicit plan and terminal done cursor rules) are checked against golden snapshots.
|
|
799
799
|
|
|
800
800
|
func TestParityProgress(t *testing.T) {
|
|
801
801
|
work := t.TempDir()
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
package main_test
|
|
2
2
|
|
|
3
|
-
// Cross-cutting assertions
|
|
4
|
-
//
|
|
3
|
+
// Cross-cutting assertions: network I/O stays inside the one sanctioned package,
|
|
4
|
+
// and the inline fail-open guard no-ops when the binary is absent.
|
|
5
5
|
|
|
6
6
|
import (
|
|
7
7
|
"bytes"
|
|
@@ -11,7 +11,7 @@ import (
|
|
|
11
11
|
)
|
|
12
12
|
|
|
13
13
|
// TestFirstPartyMakesNoNetworkCalls asserts that no first-party package imports a
|
|
14
|
-
// network client EXCEPT internal/iohooks — the
|
|
14
|
+
// network client EXCEPT internal/iohooks — the one sanctioned network surface,
|
|
15
15
|
// where the source-citation cache does conditional-HEAD revalidation. Confining
|
|
16
16
|
// network to that single, auditable package keeps the rest of the engine a
|
|
17
17
|
// network-free control plane that makes zero model calls (PRD: "zero API"). The
|