devrites 3.0.1 → 3.0.3
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 +12 -0
- package/README.md +1 -1
- package/docs/adr/0004-state-schema-phases-sections.md +44 -0
- package/docs/command-map.md +1 -1
- package/docs/engine/state-schema.md +15 -12
- package/docs/flow.md +1 -1
- package/docs/research/go-authoritative-workflow-schema-and-quality-2026-07-20.md +212 -0
- package/engine/hooks_events_test.go +15 -0
- package/engine/hooks_workspace.go +8 -31
- package/engine/internal/gate/gate.go +6 -11
- package/engine/internal/gate/gate_test.go +18 -2
- package/engine/internal/lib/buildreadiness.go +10 -16
- package/engine/internal/lib/cursor_compat_test.go +120 -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 +69 -29
- package/engine/internal/lib/resolve.go +21 -17
- package/engine/internal/lib/resolve_remediation_test.go +9 -0
- package/engine/internal/lib/tickafk.go +11 -28
- package/engine/internal/migrate/migrate.go +13 -32
- package/engine/internal/migrate/migrate_test.go +11 -3
- package/engine/internal/state/cmd/workflowmanifest/main.go +54 -0
- package/engine/internal/state/cursor.go +119 -0
- package/engine/internal/state/cursor_test.go +58 -0
- package/engine/internal/state/feature.go +25 -48
- package/engine/internal/state/schema.go +151 -24
- package/engine/internal/state/snapshot.go +60 -3
- package/engine/internal/state/state_test.go +119 -3
- package/engine/internal/state/workflow_manifest.json +310 -0
- package/engine/internal/workflow/commands.go +10 -29
- package/engine/internal/workflow/commands_test.go +18 -0
- 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 -12
- package/engine/tests/gate_test.go +22 -0
- package/engine/tests/lib_parity_test.go +1 -1
- package/engine/tests/migrate_cli_test.go +3 -3
- 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 +1 -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-workspace-schema.py +9 -73
- package/scripts/validate.sh +10 -0
- package/scripts/workflow_schema.py +69 -0
|
@@ -9,9 +9,11 @@ workspace map; `proof.md` may stand in for `evidence.md`.
|
|
|
9
9
|
|
|
10
10
|
| Phase | Required artifacts |
|
|
11
11
|
| --- | --- |
|
|
12
|
+
| frame | `state.md` |
|
|
12
13
|
| spec | `README.md`/`index.md`/`feature.md`, `brief.md`, `spec.md`, `state.md`, `decisions.md`, `assumptions.md`, `questions.md` |
|
|
13
|
-
|
|
|
14
|
-
|
|
|
14
|
+
| temper | spec artifacts |
|
|
15
|
+
| define/plan/vet/build/converge | spec artifacts plus `architecture.md`, `plan.md`, `tasks.md`, `traceability.md` |
|
|
16
|
+
| prove/polish/review/seal/ship/done | plan artifacts plus `evidence.md`/`proof.md`, `touched-files.md` |
|
|
15
17
|
| conditional | `flows.md` when diagrams clarify; `design-brief.md` and `browser-evidence.md` for UI; `drift.md` for drift; `handoff.md` only when requested; `references.md` + `references/` when references exist |
|
|
16
18
|
|
|
17
19
|
## What each file owns
|
|
@@ -33,7 +35,7 @@ workspace map; `proof.md` may stand in for `evidence.md`.
|
|
|
33
35
|
| `evidence.md` / `proof.md` | `EVID-###` command/action, result, timestamp if available, related AC/slice IDs, limitation | 280 lines |
|
|
34
36
|
| `browser-evidence.md` | UI route/viewports/screenshots/console/network/interactions and Visual Verdict | 220 lines |
|
|
35
37
|
| `drift.md` | `DRIFT-###` spec/plan drift and resolution | 160 lines |
|
|
36
|
-
| `touched-files.md` | implementation files, slice ownership, reason, and a concern-ordered `## Review trail` of `path:line` stops for human review |
|
|
38
|
+
| `touched-files.md` | implementation files, slice ownership, reason, and a concern-ordered `## Review trail` of `path:line` stops for human review | 160 lines |
|
|
37
39
|
| `design-brief.md` | UI design direction, states, interaction model | 160 lines |
|
|
38
40
|
| `handoff.md` | cold-resume guide: current objective, last completed slice, next action, blockers, read-next links | 120 lines |
|
|
39
41
|
|
|
@@ -18,6 +18,6 @@ This project has DevRites installed for both Claude Code and Codex.
|
|
|
18
18
|
## Workflow contract
|
|
19
19
|
|
|
20
20
|
- Keep all feature state in `.devrites/work/<slug>/` and preserve `.devrites/ACTIVE`.
|
|
21
|
-
- Follow the DevRites lifecycle: spec -> define -> build -> prove -> polish -> review -> seal -> ship.
|
|
21
|
+
- Follow the DevRites lifecycle: frame -> spec -> temper -> define -> plan -> vet -> build -> converge -> prove -> polish -> review -> seal -> ship -> done.
|
|
22
22
|
- Claims of completion need recorded evidence in the feature workspace, not confidence alone.
|
|
23
23
|
<!-- END DEVRITES CODEX -->
|
|
@@ -9,9 +9,11 @@ workspace map; `proof.md` may stand in for `evidence.md`.
|
|
|
9
9
|
|
|
10
10
|
| Phase | Required artifacts |
|
|
11
11
|
| --- | --- |
|
|
12
|
+
| frame | `state.md` |
|
|
12
13
|
| spec | `README.md`/`index.md`/`feature.md`, `brief.md`, `spec.md`, `state.md`, `decisions.md`, `assumptions.md`, `questions.md` |
|
|
13
|
-
|
|
|
14
|
-
|
|
|
14
|
+
| temper | spec artifacts |
|
|
15
|
+
| define/plan/vet/build/converge | spec artifacts plus `architecture.md`, `plan.md`, `tasks.md`, `traceability.md` |
|
|
16
|
+
| prove/polish/review/seal/ship/done | plan artifacts plus `evidence.md`/`proof.md`, `touched-files.md` |
|
|
15
17
|
| conditional | `flows.md` when diagrams clarify; `design-brief.md` and `browser-evidence.md` for UI; `drift.md` for drift; `handoff.md` only when requested; `references.md` + `references/` when references exist |
|
|
16
18
|
|
|
17
19
|
## What each file owns
|
|
@@ -33,7 +35,7 @@ workspace map; `proof.md` may stand in for `evidence.md`.
|
|
|
33
35
|
| `evidence.md` / `proof.md` | `EVID-###` command/action, result, timestamp if available, related AC/slice IDs, limitation | 280 lines |
|
|
34
36
|
| `browser-evidence.md` | UI route/viewports/screenshots/console/network/interactions and Visual Verdict | 220 lines |
|
|
35
37
|
| `drift.md` | `DRIFT-###` spec/plan drift and resolution | 160 lines |
|
|
36
|
-
| `touched-files.md` | implementation files, slice ownership, reason, and a concern-ordered `## Review trail` of `path:line` stops for human review |
|
|
38
|
+
| `touched-files.md` | implementation files, slice ownership, reason, and a concern-ordered `## Review trail` of `path:line` stops for human review | 160 lines |
|
|
37
39
|
| `design-brief.md` | UI design direction, states, interaction model | 160 lines |
|
|
38
40
|
| `handoff.md` | cold-resume guide: current objective, last completed slice, next action, blockers, read-next links | 120 lines |
|
|
39
41
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "devrites",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.3",
|
|
4
4
|
"description": "DevRites — disciplined senior-engineer workflow skills pack for Claude Code and Codex",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"homepage": "https://github.com/ViktorsBaikers/DevRites#readme",
|
|
@@ -132,7 +132,7 @@ This project has DevRites installed for both Claude Code and Codex.
|
|
|
132
132
|
## Workflow contract
|
|
133
133
|
|
|
134
134
|
- Keep all feature state in `.devrites/work/<slug>/` and preserve `.devrites/ACTIVE`.
|
|
135
|
-
- Follow the DevRites lifecycle: spec -> define -> build -> prove -> polish -> review -> seal -> ship.
|
|
135
|
+
- Follow the DevRites lifecycle: frame -> spec -> temper -> define -> plan -> vet -> build -> converge -> prove -> polish -> review -> seal -> ship -> done.
|
|
136
136
|
- Claims of completion need recorded evidence in the feature workspace, not confidence alone.
|
|
137
137
|
<!-- END DEVRITES CODEX -->
|
|
138
138
|
EOF
|
package/scripts/grade-feature.sh
CHANGED
|
@@ -77,9 +77,11 @@ fi
|
|
|
77
77
|
|
|
78
78
|
# 7 — state phase / status
|
|
79
79
|
if [ -f "$st" ]; then
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
ph="$(python3 "$ROOT/scripts/workflow_schema.py" field "$st" phase 2>/dev/null || true)"
|
|
81
|
+
stt="$(python3 "$ROOT/scripts/workflow_schema.py" field "$st" status 2>/dev/null || true)"
|
|
82
|
+
if ! python3 "$ROOT/scripts/workflow_schema.py" phase-property "$ph" shippable; then
|
|
83
|
+
problems+=("state.md Phase='${ph}' (expected a shippable phase)")
|
|
84
|
+
fi
|
|
83
85
|
case "$stt" in awaiting_human|blocked) problems+=("state.md Status='${stt}' (not shippable)") ;; esac
|
|
84
86
|
else
|
|
85
87
|
problems+=("state.md missing")
|
|
@@ -23,6 +23,27 @@ else
|
|
|
23
23
|
echo " FAIL — a known-shippable workspace should grade GO"; fail=1
|
|
24
24
|
fi
|
|
25
25
|
|
|
26
|
+
echo
|
|
27
|
+
echo "== grade synthetic/canonical-cursor (expect GO) =="
|
|
28
|
+
canonical="$tmp/canonical-cursor"
|
|
29
|
+
mkdir -p "$canonical"
|
|
30
|
+
cp -R "$good/." "$canonical/"
|
|
31
|
+
cat > "$canonical/state.md" <<'MD'
|
|
32
|
+
# State
|
|
33
|
+
|
|
34
|
+
## Cursor
|
|
35
|
+
| Key | Value |
|
|
36
|
+
| --- | --- |
|
|
37
|
+
| phase | done |
|
|
38
|
+
| status | done |
|
|
39
|
+
| next_action | archived |
|
|
40
|
+
MD
|
|
41
|
+
if bash "$GRADER" "$canonical"; then
|
|
42
|
+
echo " PASS — canonical cursor graded GO"
|
|
43
|
+
else
|
|
44
|
+
echo " FAIL — canonical cursor should grade GO"; fail=1
|
|
45
|
+
fi
|
|
46
|
+
|
|
26
47
|
echo
|
|
27
48
|
echo "== grade golden/blocked-feature (expect NO-GO) =="
|
|
28
49
|
if bash "$GRADER" "$bad"; then
|
|
@@ -12,6 +12,8 @@ import re
|
|
|
12
12
|
import sys
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
|
|
15
|
+
from workflow_schema import PHASES, cursor_field
|
|
16
|
+
|
|
15
17
|
|
|
16
18
|
ID_PATTERNS = {
|
|
17
19
|
"AC": re.compile(r"\bAC-\d{3}\b"),
|
|
@@ -112,68 +114,6 @@ SLICE_REQUIRED_FIELDS = (
|
|
|
112
114
|
"Done condition",
|
|
113
115
|
)
|
|
114
116
|
|
|
115
|
-
PHASE_REQUIRED = {
|
|
116
|
-
"frame": ["state.md"],
|
|
117
|
-
"spec": ["brief.md", "spec.md", "state.md", "decisions.md", "assumptions.md", "questions.md"],
|
|
118
|
-
"temper": ["brief.md", "spec.md", "state.md", "decisions.md", "assumptions.md", "questions.md"],
|
|
119
|
-
"plan": [
|
|
120
|
-
"brief.md",
|
|
121
|
-
"spec.md",
|
|
122
|
-
"architecture.md",
|
|
123
|
-
"plan.md",
|
|
124
|
-
"tasks.md",
|
|
125
|
-
"traceability.md",
|
|
126
|
-
"state.md",
|
|
127
|
-
"decisions.md",
|
|
128
|
-
"assumptions.md",
|
|
129
|
-
"questions.md",
|
|
130
|
-
],
|
|
131
|
-
"vet": [
|
|
132
|
-
"brief.md",
|
|
133
|
-
"spec.md",
|
|
134
|
-
"architecture.md",
|
|
135
|
-
"plan.md",
|
|
136
|
-
"tasks.md",
|
|
137
|
-
"traceability.md",
|
|
138
|
-
"state.md",
|
|
139
|
-
"decisions.md",
|
|
140
|
-
"assumptions.md",
|
|
141
|
-
"questions.md",
|
|
142
|
-
],
|
|
143
|
-
"build": [
|
|
144
|
-
"brief.md",
|
|
145
|
-
"spec.md",
|
|
146
|
-
"architecture.md",
|
|
147
|
-
"plan.md",
|
|
148
|
-
"tasks.md",
|
|
149
|
-
"traceability.md",
|
|
150
|
-
"state.md",
|
|
151
|
-
"decisions.md",
|
|
152
|
-
"assumptions.md",
|
|
153
|
-
"questions.md",
|
|
154
|
-
],
|
|
155
|
-
"prove": [
|
|
156
|
-
"brief.md",
|
|
157
|
-
"spec.md",
|
|
158
|
-
"architecture.md",
|
|
159
|
-
"plan.md",
|
|
160
|
-
"tasks.md",
|
|
161
|
-
"traceability.md",
|
|
162
|
-
"state.md",
|
|
163
|
-
"decisions.md",
|
|
164
|
-
"assumptions.md",
|
|
165
|
-
"questions.md",
|
|
166
|
-
],
|
|
167
|
-
"polish": [],
|
|
168
|
-
"review": [],
|
|
169
|
-
"seal": [],
|
|
170
|
-
"ship": [],
|
|
171
|
-
"done": [],
|
|
172
|
-
}
|
|
173
|
-
for phase in ("polish", "review", "seal", "ship", "done"):
|
|
174
|
-
PHASE_REQUIRED[phase] = PHASE_REQUIRED["prove"]
|
|
175
|
-
|
|
176
|
-
|
|
177
117
|
def read(path: Path) -> str:
|
|
178
118
|
return path.read_text(encoding="utf-8")
|
|
179
119
|
|
|
@@ -198,12 +138,9 @@ def has_budget_override(text: str) -> bool:
|
|
|
198
138
|
def phase_for(workspace: Path) -> str:
|
|
199
139
|
for name in ("state.md", "status.md", "README.md", "index.md", "feature.md"):
|
|
200
140
|
p = workspace / name
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
m = re.search(r"(?im)^\s*-?\s*phase\s*:\s*([a-z-]+)", text)
|
|
205
|
-
if m:
|
|
206
|
-
return m.group(1).lower()
|
|
141
|
+
value = cursor_field(p, "phase")
|
|
142
|
+
if value:
|
|
143
|
+
return value.split()[0].lower()
|
|
207
144
|
return "spec"
|
|
208
145
|
|
|
209
146
|
|
|
@@ -374,9 +311,8 @@ def validate_workspace(workspace: Path) -> list[str]:
|
|
|
374
311
|
if not workspace_index_present(workspace):
|
|
375
312
|
errors.append(f"{workspace}: missing README.md/index.md/feature.md workspace map")
|
|
376
313
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
required = [*required, "evidence.md", "touched-files.md"]
|
|
314
|
+
metadata = PHASES.get(phase, PHASES["spec"])
|
|
315
|
+
required = list(metadata["workspaceRequired"])
|
|
380
316
|
for name in required:
|
|
381
317
|
if not existing_or_alias(workspace, name):
|
|
382
318
|
errors.append(f"{workspace}: phase {phase} requires {name}")
|
|
@@ -456,7 +392,7 @@ def validate_workspace(workspace: Path) -> list[str]:
|
|
|
456
392
|
for ac in sorted(ac_ids):
|
|
457
393
|
if ac not in rows:
|
|
458
394
|
errors.append(f"{workspace / 'traceability.md'}: {ac} absent from coverage matrix")
|
|
459
|
-
if
|
|
395
|
+
if bool(metadata.get("proofRequired")):
|
|
460
396
|
for eid in sorted(evid_ids):
|
|
461
397
|
if eid not in trace:
|
|
462
398
|
errors.append(f"{workspace / 'traceability.md'}: evidence ID {eid} from evidence/browser proof is not mapped")
|
|
@@ -467,7 +403,7 @@ def validate_workspace(workspace: Path) -> list[str]:
|
|
|
467
403
|
elif slice_id not in evidence:
|
|
468
404
|
errors.append(f"{evidence_path}: completed {slice_id} is not referenced by evidence")
|
|
469
405
|
|
|
470
|
-
if
|
|
406
|
+
if bool(metadata.get("blocksOpenQuestions")):
|
|
471
407
|
qfile = workspace / "questions.md"
|
|
472
408
|
if qfile.exists():
|
|
473
409
|
for qid in blocking_questions(read(qfile)):
|
package/scripts/validate.sh
CHANGED
|
@@ -219,6 +219,16 @@ fi
|
|
|
219
219
|
|
|
220
220
|
# ---- 11b. generated workspace schema fixtures ----------------------------
|
|
221
221
|
section "workspace artifact schema"
|
|
222
|
+
if command -v go >/dev/null 2>&1; then
|
|
223
|
+
if (cd "$ROOT/engine" && go run ./internal/state/cmd/workflowmanifest -check -out internal/state/workflow_manifest.json) >/tmp/dr_workflow_manifest 2>&1; then
|
|
224
|
+
good "workflow manifest is fresh"
|
|
225
|
+
else
|
|
226
|
+
cat /tmp/dr_workflow_manifest
|
|
227
|
+
bad "workflow manifest drifted from the typed state registry"
|
|
228
|
+
fi
|
|
229
|
+
else
|
|
230
|
+
echo "skip: go not found; workflow manifest freshness not checked"
|
|
231
|
+
fi
|
|
222
232
|
if command -v python3 >/dev/null 2>&1; then
|
|
223
233
|
if python3 "$ROOT/scripts/validate-workspace-schema.py" "$ROOT/tests/fixtures/workspace-schema" >/tmp/dr_workspace_schema 2>&1; then
|
|
224
234
|
cat /tmp/dr_workspace_schema
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Read generated workflow metadata and canonical/legacy cursor fields."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import re
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
MANIFEST = Path(__file__).resolve().parents[1] / "engine" / "internal" / "state" / "workflow_manifest.json"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
DOCUMENT = json.loads(MANIFEST.read_text(encoding="utf-8"))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
PHASES = {str(phase["id"]): phase for phase in DOCUMENT["phases"]}
|
|
19
|
+
CURSOR_KEY_ALIASES = {
|
|
20
|
+
str(entry["alias"]): str(entry["canonical"])
|
|
21
|
+
for entry in DOCUMENT["cursorKeyAliases"]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def normalize_cursor_key(key: str) -> str:
|
|
26
|
+
normalized = "".join(char for char in key.lower() if char.isalnum())
|
|
27
|
+
canonical = CURSOR_KEY_ALIASES.get(normalized, normalized)
|
|
28
|
+
return "".join(char for char in canonical.lower() if char.isalnum())
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def cursor_field_text(text: str, key: str) -> str | None:
|
|
32
|
+
wanted = normalize_cursor_key(key)
|
|
33
|
+
for line in text.splitlines():
|
|
34
|
+
stripped = line.strip()
|
|
35
|
+
if stripped.startswith("|") and stripped.endswith("|"):
|
|
36
|
+
cells = [cell.strip() for cell in stripped[1:-1].split("|")]
|
|
37
|
+
if len(cells) >= 2 and normalize_cursor_key(cells[0]) == wanted:
|
|
38
|
+
return cells[1]
|
|
39
|
+
legacy = re.match(r"^\s*[-*+]?\s*([^:]+):\s*(.*?)\s*$", line)
|
|
40
|
+
if legacy and normalize_cursor_key(legacy.group(1)) == wanted:
|
|
41
|
+
return re.sub(r"\s*(?:#|\|).*?$", "", legacy.group(2)).rstrip()
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def cursor_field(path: Path, key: str) -> str | None:
|
|
46
|
+
if not path.is_file():
|
|
47
|
+
return None
|
|
48
|
+
return cursor_field_text(path.read_text(encoding="utf-8"), key)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def phase_property(phase: str, name: str) -> bool:
|
|
52
|
+
return bool(PHASES.get(phase.lower(), {}).get(name, False))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def main(argv: list[str]) -> int:
|
|
56
|
+
if len(argv) == 4 and argv[1] == "field":
|
|
57
|
+
value = cursor_field(Path(argv[2]), argv[3])
|
|
58
|
+
if value is None:
|
|
59
|
+
return 1
|
|
60
|
+
print(value)
|
|
61
|
+
return 0
|
|
62
|
+
if len(argv) == 4 and argv[1] == "phase-property":
|
|
63
|
+
return 0 if phase_property(argv[2], argv[3]) else 1
|
|
64
|
+
print("usage: workflow_schema.py field <state.md> <key> | phase-property <phase> <property>", file=sys.stderr)
|
|
65
|
+
return 2
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if __name__ == "__main__":
|
|
69
|
+
raise SystemExit(main(sys.argv))
|