Skip to content

Commit f12e528

Browse files
committed
chore(conventions): overhaul workflow standards, add extend-criteria skill
- Rename test functions to test_<short_title> (drop _should_ pattern) - UUID-only first line in test docstrings (no description after UUID) - Add mandatory Source: field to acceptance criteria format - Add extend-criteria skill for gap/defect handling mid-flight - Replace bare 'task' with 'uv run task' across all skill/agent files - Update to flat tests/ layout (no unit/ integration/ subdirs) - Split compound acceptance criterion into two single-outcome criteria - Remove Test strategy from PO scope (developer decision) - Add [~] and [-] TODO.md status markers - Move mv command ownership to developer Step 2 - Add --doctest-modules note to implementation skill - Align pytest.skip prohibition: allowed with written justification - Remove stale auto-publish-docs.md from in-progress - Add display-version.md as completed reference feature - Update README with uv sync --all-extras and uv run task commands
1 parent 0c46a92 commit f12e528

File tree

13 files changed

+434
-246
lines changed

13 files changed

+434
-246
lines changed

.opencode/agents/developer.md

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,16 @@ You build everything: architecture, tests, code, and releases. You own technical
3434
Every session: load `skill session-workflow` first. Read TODO.md to find current step and feature.
3535

3636
### Step 2 — BOOTSTRAP + ARCHITECTURE
37-
When a new feature lands in `docs/features/in-progress/`:
38-
39-
1. Read the feature doc. Understand all acceptance criteria and their UUIDs.
40-
2. Add an `## Architecture` section to the feature doc:
37+
When a new feature is ready in `docs/features/backlog/`:
38+
39+
1. Move the feature doc to in-progress:
40+
```bash
41+
mv docs/features/backlog/<feature-name>.md docs/features/in-progress/<feature-name>.md
42+
git add -A
43+
git commit -m "chore(workflow): start <feature-name>"
44+
```
45+
2. Read the feature doc. Understand all acceptance criteria and their UUIDs.
46+
3. Add an `## Architecture` section to the feature doc:
4147
- Module structure (which files you will create/modify)
4248
- Key decisions — write an ADR for any non-obvious choice:
4349
```
@@ -47,10 +53,10 @@ When a new feature lands in `docs/features/in-progress/`:
4753
Alternatives considered: <what you rejected and why>
4854
```
4955
- Build changes that need PO approval: new runtime deps, new packages, changed entry points
50-
3. If build changes need PO approval, ask before proceeding. Tooling changes (coverage, lint rules, test config) are your autonomy.
51-
4. Update `pyproject.toml` and project structure as needed.
52-
5. Run `task test` — must still pass.
53-
6. Commit: `feat(bootstrap): configure build for <feature-name>`
56+
4. If build changes need PO approval, ask before proceeding. Tooling changes (coverage, lint rules, test config) are your autonomy.
57+
5. Update `pyproject.toml` and project structure as needed.
58+
6. Run `uv run task test` — must still pass.
59+
7. Commit: `feat(bootstrap): configure build for <feature-name>`
5460
5561
### Step 3 — TEST FIRST
5662
Load `skill tdd`. Write failing tests mapped 1:1 to each UUID acceptance criterion.
@@ -59,7 +65,8 @@ Commit: `test(<feature-name>): add failing tests for all acceptance criteria`
5965
### Step 4 — IMPLEMENT
6066
Load `skill implementation`. Make tests green one at a time.
6167
Commit after each test goes green: `feat(<feature-name>): implement <component>`
62-
Self-verify: run `task test` and `timeout 10s task run` after each commit.
68+
Self-verify after each commit: run all four commands in the Self-Verification block below.
69+
If you discover a missing behavior during implementation, load `skill extend-criteria`.
6370
6471
### After reviewer approves (Step 5)
6572
Load `skill pr-management` and `skill git-release` as needed.
@@ -100,10 +107,10 @@ When making a non-obvious architecture decision, write a brief ADR in the featur
100107
101108
Before declaring any step complete and before requesting reviewer verification, run:
102109
```bash
103-
task lint # must exit 0
104-
task static-check # must exit 0, 0 errors
105-
task test # must exit 0, all tests pass
106-
timeout 10s task run # must exit 0 or 124; exit 124 = timeout (infinite loop) = fix it
110+
uv run task lint # must exit 0
111+
uv run task static-check # must exit 0, 0 errors
112+
uv run task test # must exit 0, all tests pass
113+
timeout 10s uv run task run # must exit non-124; exit 124 = timeout (infinite loop) = fix it
107114
```
108115

109116
Do not hand off broken work to the reviewer.
@@ -112,9 +119,8 @@ Do not hand off broken work to the reviewer.
112119

113120
```
114121
<package>/ # production code (named after the project)
115-
tests/
116-
unit/ # @pytest.mark.unit — isolated, one function/class
117-
integration/ # @pytest.mark.integration — multiple components
122+
tests/ # flat layout — no unit/ or integration/ subdirectories
123+
<name>_test.py # marker (@pytest.mark.unit/integration) determines category
118124
pyproject.toml # version, deps, tasks, test config
119125
```
120126

@@ -127,6 +133,7 @@ pyproject.toml # version, deps, tasks, test config
127133
- `session-workflow` — read/update TODO.md at session boundaries
128134
- `tdd` — write failing tests with UUID traceability (Step 3)
129135
- `implementation` — Red-Green-Refactor cycle (Step 4)
136+
- `extend-criteria` — add gap criteria discovered during implementation or review
130137
- `code-quality` — ruff, pyright, coverage standards
131138
- `pr-management` — create PRs with conventional commits
132139
- `git-release` — calver versioning and themed release naming

.opencode/agents/product-owner.md

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,28 +53,43 @@ Every criterion must have a UUID (generate with `python -c "import uuid; print(u
5353

5454
```markdown
5555
- `<uuid>`: <Short description>.
56+
Source: <stakeholder | po | developer | reviewer | bug>
57+
5658
Given: <precondition>
5759
When: <action>
5860
Then: <expected outcome>
59-
Test strategy: unit | integration
6061
```
6162

6263
All UUIDs must be unique. Every story must have at least one criterion. Every criterion must be independently testable.
6364

65+
**Source field** (mandatory): records who originated this criterion.
66+
- `stakeholder` — an external stakeholder gave this requirement to the PO
67+
- `po` — the PO originated this criterion independently
68+
- `developer` — a gap found during Step 4 implementation
69+
- `reviewer` — a gap found during Step 5 verification
70+
- `bug` — a post-merge regression; the feature doc was reopened
71+
72+
When adding criteria discovered after initial scope, load `skill extend-criteria`.
73+
6474
## Feature Document Structure
6575

76+
Filename: `<verb>-<object>.md` — imperative verb first, kebab-case, 2–4 words.
77+
Examples: `display-version.md`, `authenticate-user.md`, `export-metrics-csv.md`
78+
Title matches: `# Feature: <Verb> <Object>` in Title Case.
79+
6680
```markdown
67-
# Feature: <Name>
81+
# Feature: <Verb> <Object>
6882

6983
## User Stories
7084
- As a <role>, I want <goal> so that <benefit>
7185

7286
## Acceptance Criteria
7387
- `<uuid>`: <Short description>.
88+
Source: <stakeholder | po>
89+
7490
Given: ...
7591
When: ...
7692
Then: ...
77-
Test strategy: unit | integration
7893

7994
## Notes
8095
<constraints, risks, out-of-scope items>

.opencode/agents/reviewer.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,10 @@ Load `skill verify`. Run all commands, check all criteria, produce a written rep
5555
Run these in order. If any fails, stop and report — do not continue to the next:
5656

5757
```bash
58-
task lint # must exit 0
59-
task static-check # must exit 0, 0 errors
60-
task test # must exit 0, 0 failures, coverage >= 100%
61-
timeout 10s task run # must exit 0 or 124; exit 124 = timeout (infinite loop) = FAIL
58+
uv run task lint # must exit 0
59+
uv run task static-check # must exit 0, 0 errors
60+
uv run task test # must exit 0, 0 failures, coverage >= 100%
61+
timeout 10s uv run task run # must exit non-124; exit 124 = timeout (infinite loop) = FAIL
6262
```
6363

6464
## Code Review Checklist
@@ -95,7 +95,7 @@ After all commands pass, review source code for:
9595
9. No getters/setters (tell, don't ask)
9696

9797
**Tests**
98-
- [ ] Every test has UUID docstring with Given/When/Then
98+
- [ ] Every test has UUID-only first line docstring, blank line, then Given/When/Then
9999
- [ ] Tests assert behavior, not structure
100100
- [ ] Every acceptance criterion has a mapped test
101101
- [ ] No test verifies isinstance, type(), or internal attributes
@@ -111,10 +111,10 @@ After all commands pass, review source code for:
111111
## Step 5 Verification Report
112112
113113
### Commands
114-
- task lint: PASS | FAIL — <output if fail>
115-
- task static-check: PASS | FAIL — <errors if fail>
116-
- task test: PASS | FAIL — <failures/coverage if fail>
117-
- timeout 10s task run: PASS | FAIL | TIMEOUT — <error or "process did not exit within 10s" if fail>
114+
- uv run task lint: PASS | FAIL — <output if fail>
115+
- uv run task static-check: PASS | FAIL | NOT RUN — <errors if fail, or "stopped after previous failure">
116+
- uv run task test: PASS | FAIL | NOT RUN — <failures/coverage if fail, or "stopped after previous failure">
117+
- timeout 10s uv run task run: PASS | FAIL | TIMEOUT | NOT RUN — <error or "process did not exit within 10s" if fail, or "stopped after previous failure">
118118
119119
### Code Review
120120
- PASS | FAIL: <finding with file:line reference>
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
---
2+
name: extend-criteria
3+
description: Add acceptance criteria discovered after scope is written — gaps found during implementation or review, and post-merge defects
4+
version: "1.0"
5+
author: any
6+
audience: developer, reviewer, product-owner
7+
workflow: feature-lifecycle
8+
---
9+
10+
# Extend Criteria
11+
12+
This skill is loaded when any agent discovers a missing behavior that is not covered by the existing acceptance criteria. It provides the decision rule, UUID assignment, and commit protocol for adding new criteria mid-flight or post-merge.
13+
14+
## When to Use
15+
16+
- **Developer (Step 4)**: implementation reveals an untested behavior
17+
- **Reviewer (Step 5)**: code review reveals an observable behavior with no acceptance criterion
18+
- **Post-merge**: a defect is found in production and a regression criterion must be added
19+
20+
Do not use this skill to scope new features. New observable behaviors that go beyond the current feature's user stories must be escalated to the PO.
21+
22+
## Decision Rule: Is This a Gap or a New Feature?
23+
24+
Ask: "Does this behavior fall within the intent of the current user stories?"
25+
26+
| Situation | Action |
27+
|---|---|
28+
| Edge case or error path within approved scope | Add criterion with `Source: developer` or `Source: reviewer` |
29+
| New observable behavior users did not ask for | Escalate to PO; do not add criterion unilaterally |
30+
| Post-merge regression (the feature was accepted and broke later) | Reopen feature doc; add criterion with `Source: bug` |
31+
| Behavior already present but criterion was never written | Add criterion with appropriate `Source:` |
32+
33+
When in doubt, ask the PO before adding.
34+
35+
## Criterion Format
36+
37+
All criteria use this format (mandatory `Source:` field):
38+
39+
```markdown
40+
- `<uuid>`: <Short description ending with a period>.
41+
Source: <source>
42+
43+
Given: <precondition>
44+
When: <action>
45+
Then: <single observable outcome>
46+
```
47+
48+
**Source values** (choose exactly one):
49+
- `stakeholder` — an external stakeholder gave this requirement to the PO
50+
- `po` — the PO originated this criterion independently
51+
- `developer` — a gap found during Step 4 implementation
52+
- `reviewer` — a gap found during Step 5 verification
53+
- `bug` — a post-merge regression; the feature doc was reopened
54+
55+
**Rules**:
56+
- UUID must be unique across the entire project
57+
- Generate: `python -c "import uuid; print(uuid.uuid4())"`
58+
- `Then` must be a single observable, measurable outcome — no "and"
59+
- Do not add `Source:` retroactively to criteria that predate this field
60+
61+
## Procedure by Role
62+
63+
### Developer (Step 4)
64+
65+
1. Determine whether this is a gap within scope or a new feature (use the decision table above)
66+
2. If it is within scope:
67+
a. Add the criterion to the feature doc with `Source: developer`
68+
b. Write the failing test for it (load `skill tdd`)
69+
c. Make it green (continue Red-Green-Refactor)
70+
d. Commit: `test(<feature-name>): add gap criterion <uuid>`
71+
3. If it is out of scope: write a note in TODO.md under `## Next`, flag it for the PO after Step 5
72+
73+
### Reviewer (Step 5)
74+
75+
1. Determine whether this is a gap within scope or a new feature
76+
2. If it is within scope:
77+
- Add the criterion to the feature doc with `Source: reviewer`
78+
- Record in the REJECTED report: "Added criterion `<uuid>` — developer must implement before resubmitting"
79+
3. If it is out of scope:
80+
- Do not add the criterion
81+
- Note it in the report as a future backlog item
82+
83+
### Post-merge Defect
84+
85+
1. Move the feature doc back to in-progress:
86+
```bash
87+
mv docs/features/completed/<name>.md docs/features/in-progress/<name>.md
88+
git add -A
89+
git commit -m "chore(workflow): reopen <name> for bug fix"
90+
```
91+
2. Add the new criterion with `Source: bug`
92+
3. Return to Step 3 (write failing test) then Step 4 (implement) then Step 5 (verify) then Step 6 (accept)
93+
4. Update TODO.md to reflect the reopened feature at the correct step
94+
95+
## Checklist
96+
97+
Before committing a new criterion:
98+
- [ ] UUID is unique (search: `grep -r "<uuid>" docs/features/` and `grep -r "<uuid>" tests/`)
99+
- [ ] `Source:` value is one of the five valid values
100+
- [ ] `Then` is a single, observable outcome (no "and")
101+
- [ ] Blank line between `Source:` line and `Given:`
102+
- [ ] A corresponding test will be written (or already exists)

.opencode/skills/implementation/SKILL.md

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ def register_user(email: EmailAddress, repo: UserRepository) -> "User":
102102
## RED — Confirm the Test Fails
103103

104104
```bash
105-
pytest tests/unit/<file>_test.py::test_<name> -v
105+
uv run pytest tests/<file>_test.py::test_<name> -v
106106
```
107107

108108
Expected: `FAILED` or `ERROR`. If it passes before you've written code, the test is wrong — fix it.
@@ -116,8 +116,8 @@ Write the least code that makes the test pass. Apply during GREEN:
116116
Do NOT apply during GREEN: DRY, SOLID, Object Calisthenics — those come in refactor.
117117

118118
```bash
119-
pytest tests/unit/<file>_test.py::test_<name> -v # must be PASSED
120-
task test # must all still pass
119+
uv run pytest tests/<file>_test.py::test_<name> -v # must be PASSED
120+
uv run task test # must all still pass
121121
```
122122

123123
## REFACTOR — Apply Principles (in priority order)
@@ -137,10 +137,12 @@ task test # must all still pass
137137
4. **Type hints**: add/fix type annotations on all public functions and classes
138138
5. **Docstrings**: Google-style on all public functions and classes
139139

140+
> **Note**: `uv run task test` runs `--doctest-modules`, which executes code examples embedded in source docstrings. Keep `Examples:` blocks in Google-style docstrings valid and executable. If an example should not be run, mark it with `# doctest: +SKIP`.
141+
140142
```bash
141-
task test # must still pass
142-
task lint # must exit 0
143-
task static-check # must exit 0
143+
uv run task test # must still pass
144+
uv run task lint # must exit 0
145+
uv run task static-check # must exit 0
144146
```
145147

146148
## COMMIT
@@ -157,10 +159,10 @@ Then move to the next failing test.
157159
After all tests are green, before telling the reviewer you are ready:
158160

159161
```bash
160-
task lint # exit 0
161-
task static-check # exit 0, 0 errors
162-
task test # exit 0, all pass, coverage 100%
163-
timeout 10s task run # exit 0 or non-124; exit 124 = hung process = fix it
162+
uv run task lint # exit 0
163+
uv run task static-check # exit 0, 0 errors
164+
uv run task test # exit 0, all pass, coverage 100%
165+
timeout 10s uv run task run # exit non-124; exit 124 = hung process = fix it
164166
```
165167

166168
All four must pass. Do not hand off broken work.

0 commit comments

Comments
 (0)