Skip to content

Commit 2b3dca3

Browse files
authored
chore(workflow): refine agents, skills, commands, and ADR structure for v5 (#70)
* chore(workflow): refine agents, skills, commands, and ADR structure for v5 - Consolidate ADR files into single adr.md (drop adr-NNN-<title>.md pattern) - Add test-coverage and test-build tasks; fix test-fast/test flags - Clarify design priority chain with full complexity ladder - Remove self-selection language from agent instructions - Add refactor and design-patterns skills to reviewer agent - Add BASELINED guard to feature-selection skill - Fix scope skill: drop silent pre-mortem requirement from Session 1 - Align function/class line-count wording to code-lines-only * chore(workflow): document model v6 — 2-stage discovery, PO-owned feature moves, unified session protocol - Split docs/discovery.md into three append-only files: docs/discovery_journal.md (raw Q&A), docs/discovery.md (synthesis), docs/architecture.md (architectural decisions) - Remove docs/features/discovery.md (superseded) - Replace Phase 1/Phase 2 model with 2-stage model: Stage 1 Discovery (unified iterative sessions) + Stage 2 Specification (stories + criteria) - PO is sole owner of all .feature file moves; SE and reviewer never move or edit .feature files; explicit escalation when no in-progress feature - Add bug handling protocol: PO adds @bug @id, SE writes both @id test and @given Hypothesis property test - Add real-time split rule: >2 concerns or >8 Examples splits immediately within the same session - Add session status markers to discovery_journal.md: IN-PROGRESS/COMPLETE - Update all agent files, skills, workflow docs, and research docs to match
1 parent d1b2d40 commit 2b3dca3

File tree

19 files changed

+572
-546
lines changed

19 files changed

+572
-546
lines changed

.opencode/agents/product-owner.md

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,16 @@ Load `skill session-workflow` first — it reads TODO.md, orients you to the cur
2525

2626
| Step | Action |
2727
|---|---|
28-
| **Step 1 — SCOPE** | Load `skill scope` — contains the full 4-phase discovery and criteria protocol |
28+
| **Step 1 — SCOPE** | Load `skill scope` — contains Stage 1 (Discovery sessions) and Stage 2 (Stories + Criteria) |
2929
| **Step 5 — ACCEPT** | See acceptance protocol below |
3030

3131
## Ownership Rules
3232

33-
- You are the **sole owner** of `.feature` files and `docs/features/discovery.md`
33+
- You are the **sole owner** of `.feature` files, `docs/discovery_journal.md`, and `docs/discovery.md`
3434
- No other agent may edit these files
35+
- **You are the sole owner of all `.feature` file moves**: backlog → in-progress (before Step 2) and in-progress → completed (after Step 5 acceptance). No other agent moves `.feature` files.
3536
- Software-engineer escalates spec gaps to you; you decide whether to extend criteria
36-
- **You pick** the next feature from backlog — the software-engineer never self-selects
37-
- **NEVER move a feature to `in-progress/` unless its discovery section has `Status: BASELINED`** — if not baselined, complete Step 1 (Phase 2 + 3 + 4) first
37+
- **NEVER move a feature to `in-progress/` unless its `.feature` file has `Status: BASELINED`** — if not baselined, complete Step 1 (Stage 1 Discovery + Stage 2 Specification) first
3838

3939
## Step 5 — Accept
4040

@@ -56,8 +56,17 @@ When a gap is reported (by software-engineer or reviewer):
5656
| Behavior contradicts an existing Example | Write a new Example with new `@id`. |
5757
| Post-merge defect | Move the `.feature` file back to `in-progress/`, add new Example with `@id`, resume at Step 3. |
5858

59+
## Bug Handling
60+
61+
When a defect is reported against any feature:
62+
63+
1. Add a `@bug @id:<new-8-char-hex>` Example to the relevant `Rule:` block in the `.feature` file.
64+
2. Write the Example using the standard `Given/When/Then` format describing the correct behavior.
65+
3. Update TODO.md to note the new `@id` for the SE to implement.
66+
4. SE implements the `@id` test in `tests/features/` **and** a `@given` Hypothesis property test in `tests/unit/`. Both are required.
67+
5968
## Available Skills
6069

6170
- `session-workflow` — session start/end protocol
6271
- `feature-selection` — when TODO.md is idle: score and select next backlog feature using WSJF
63-
- `scope` — Step 1: 3-session discovery (Phase 1 + 2), stories (Phase 3), and criteria (Phase 4)
72+
- `scope` — Step 1: Stage 1 (Discovery sessions with stakeholder) and Stage 2 (Stories + Criteria, PO alone)

.opencode/agents/reviewer.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@ Load `skill session-workflow` first. Then load `skill verify` for Step 4 verific
4242
- **Never suggest `noqa`, `type: ignore`, or `pytest.skip` as a fix.** These are bypasses, not solutions.
4343
- **Report specific locations.** "`physics/engine.py:47`: unreachable return" not "there is dead code."
4444
- **Every PASS/FAIL cell must have evidence.** Empty evidence = UNCHECKED = REJECTED.
45+
- **Never move `.feature` files.** The PO is the sole owner of all feature file moves. After producing an APPROVED report, update TODO.md and stop — the PO accepts and moves the file.
46+
47+
## After APPROVED
48+
49+
When your report verdict is APPROVED:
50+
1. Write the report as described in `skill verify`.
51+
2. Update TODO.md `## Next` line: `Run @product-owner — accept feature <name> at Step 5.`
52+
3. Stop. Do not touch `.feature` files. The PO reviews the feature themselves and moves it to `completed/`.
4553

4654
## Gap Reporting
4755

@@ -58,4 +66,6 @@ You never edit `.feature` files or add Examples yourself.
5866
## Available Skills
5967

6068
- `session-workflow` — session start/end protocol
69+
- `refactor` — Code refactoring heuristics
70+
- `design-patterns` — Reference for code smell and design patterns
6171
- `verify` — Step 4: full verification protocol with all tables, gates, and report template

.opencode/agents/software-engineer.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,14 @@ Load `skill session-workflow` first — it reads TODO.md, orients you to the cur
4545

4646
- You own all technical decisions: module structure, patterns, internal APIs, test tooling, linting config
4747
- **PO approves**: new runtime dependencies, changed entry points, scope changes
48-
- You are **never** the one to pick the next feature — only the PO picks from backlog
48+
- **You never move `.feature` files.** The PO is the sole owner of all feature file moves (backlog → in-progress → completed). If you find no `.feature` file in `docs/features/in-progress/`, **STOP** — do not self-select a feature. Write the gap in TODO.md and escalate to PO.
49+
50+
## No In-Progress Feature
51+
52+
If `docs/features/in-progress/` contains only `.gitkeep` (no `.feature` file):
53+
1. Do not pick a feature from backlog yourself.
54+
2. Update TODO.md: `Next: Run @product-owner — load skill feature-selection and pick the next BASELINED feature from backlog.`
55+
3. Stop. The PO must move the chosen feature into `in-progress/` before you can begin Step 2.
4956

5057
## Spec Gaps
5158

@@ -61,4 +68,4 @@ If during implementation you discover behavior not covered by existing acceptanc
6168
- `design-patterns` — on-demand when smell detected during architecture or refactor
6269
- `pr-management` — Step 5: PRs with conventional commits
6370
- `git-release` — Step 5: calver versioning and themed release naming
64-
- `create-skill` — meta: create new skills when needed
71+
- `create-skill` — meta: create new skills when needed

.opencode/skills/feature-selection/SKILL.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ Read each `.feature` file in `docs/features/backlog/`. Check its discovery secti
3838
- Non-BASELINED features are not eligible — they need Step 1 (scope) first
3939
- If no BASELINED features exist: inform the stakeholder; run `@product-owner` with `skill scope` to baseline the most promising backlog item first
4040

41+
**IMPORTANT**
42+
43+
**NEVER move a feature to `in-progress/` unless its discovery section has `Status: BASELINED`**
44+
4145
### 3. Score Each Candidate
4246

4347
For each BASELINED feature, fill this table:
@@ -96,7 +100,7 @@ Run @<agent-name> — <first concrete action for this feature>
96100
```
97101

98102
- If the feature has no `Rule:` blocks yet → Step 1 (SCOPE): `Run @product-owner — load skill scope and write stories`
99-
- If the feature has `Rule:` blocks but no `@id` Examples → Step 1 Phase 4 (Criteria): `Run @product-owner — load skill scope and write acceptance criteria`
103+
- If the feature has `Rule:` blocks but no `@id` Examples → Step 1 Stage 2 Step B (Criteria): `Run @product-owner — load skill scope and write acceptance criteria`
100104
- If the feature has `@id` Examples → Step 2 (ARCH): `Run @software-engineer — load skill implementation and write architecture stubs`
101105

102106
### 6. Commit

.opencode/skills/implementation/SKILL.md

Lines changed: 42 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,20 @@ Steps 2 (Architecture) and 3 (TDD Loop) combined into a single skill. The softwa
1515

1616
During implementation, correctness priorities are (in order):
1717

18-
1. **Design correctness** — YAGNI > KISS > DRY > SOLID > Object Calisthenics > appropriate design patterns
18+
1. **Design correctness** — YAGNI > KISS > DRY > SOLID > Object Calisthenics > appropriate design patterns > complex code > complicated code > failing code > no code
1919
2. **One @id green** — the specific test under work passes, plus `test-fast` still passes
2020
3. **Commit** — when a meaningful increment is green
2121
4. **Quality tooling**`lint`, `static-check`, full `test` with coverage run at end-of-feature handoff
2222

23-
Design correctness is far more important than lint/pyright/coverage compliance. Never run lint, static-check, or coverage during the TDD loop — those are handoff-only checks.
23+
Design correctness is far more important than lint/pyright/coverage compliance. Never run lint (ruff check, ruff format), static-check (pyright), or coverage during the TDD loop — those are handoff-only checks.
2424

2525
---
2626

2727
## Step 2 — Architecture
2828

2929
### Prerequisites (stop if any fail — escalate to PO)
3030

31-
1. `docs/features/in-progress/` contains only `.gitkeep` (no `.feature` files). If another `.feature` file exists, **STOP**another feature is already in progress.
31+
1. `docs/features/in-progress/` contains exactly one `.feature` file (not just `.gitkeep`). If none exists, **STOP**update TODO.md `Next:` to `Run @product-owner — move the chosen feature to in-progress/` and stop. Never self-select or move a feature yourself.
3232
2. The feature file's discovery section has `Status: BASELINED`. If not, escalate to PO — Step 1 is incomplete.
3333
3. The feature file contains `Rule:` blocks with `Example:` blocks and `@id` tags. If not, escalate to PO — criteria have not been written.
3434
4. Package name confirmed: read `pyproject.toml` → locate `[tool.setuptools]` → confirm directory exists on disk.
@@ -37,26 +37,20 @@ Design correctness is far more important than lint/pyright/coverage compliance.
3737

3838
1. Read `pyproject.toml` → locate `[tool.setuptools]` → record `packages = ["<name>"]`
3939
2. Confirm directory exists: `ls <name>/`
40-
3. All new source files go under `<name>/` — never under a template placeholder.
40+
3. All new source files go under `<name>/`
4141

42-
### Move Feature File
43-
44-
```bash
45-
mv docs/features/backlog/<name>.feature docs/features/in-progress/<name>.feature
46-
```
47-
48-
Update `TODO.md` Source path from `backlog/` to `in-progress/`.
42+
**Note on feature file moves**: The PO moves `.feature` files between folders. The software-engineer never moves or edits `.feature` files. Update TODO.md `Source:` path to reflect `in-progress/` once the PO has moved the file.
4943

5044
### Read Phase (all before writing anything)
5145

52-
1. Read `docs/features/discovery.md` (project-level)
46+
1. Read `docs/discovery.md` (project-level synthesis changelog) and optionally `docs/discovery_journal.md` (Q&A history for context)
5347
2. Read **ALL** `.feature` files in `docs/features/backlog/` (discovery + entities sections)
5448
3. Read in-progress `.feature` file (full: Rules + Examples + @id)
5549
4. Read **ALL** existing `.py` files in `<package>/` — understand what already exists before adding anything
5650

5751
### Domain Analysis
5852

59-
From Entities table + Rules (Business) in `.feature` file:
53+
From the Domain Model table in `docs/discovery.md` + Rules (Business) in the `.feature` file:
6054
- **Nouns** → named classes, value objects, aggregates
6155
- **Verbs** → method names with typed signatures
6256
- **Datasets** → named types (not bare dict/list)
@@ -116,19 +110,20 @@ class UserRepository(Protocol):
116110

117111
Place stubs where responsibility dictates — do not pre-create `ports/` or `adapters/` folders unless a concrete external dependency was identified in scope. Structure follows domain analysis, not a template.
118112

119-
### Write ADR Files (significant decisions only)
113+
### Record Architectural Decisions
120114

121-
For each significant architectural decision, create `docs/architecture/adr-NNN-<title>.md`:
115+
Append a new dated block to `docs/architecture.md` for each significant decision:
122116

123117
```markdown
124-
# ADR-NNN: <title>
118+
## YYYY-MM-DD — <feature-name>: <short title>
125119

126-
**Decision:** <what was decided>
127-
**Reason:** <why, one sentence>
128-
**Alternatives considered:** <what was rejected and why>
120+
Decision: <what was decided>
121+
Reason: <why, one sentence>
122+
Alternatives considered: <what was rejected and why>
123+
Feature: <feature-name>
129124
```
130125

131-
Only write an ADR if the decision is non-obvious or has meaningful trade-offs. Routine YAGNI choices do not need an ADR.
126+
Only write a block for non-obvious decisions with meaningful trade-offs. Routine YAGNI choices do not need a record.
132127

133128
### Architecture Smell Check (hard gate)
134129

@@ -153,25 +148,21 @@ Commit: `feat(<feature-name>): add architecture stubs`
153148

154149
### Prerequisites
155150

151+
- [ ] Exactly one .feature `in_progress`. If not present, Load `skill feature-selection`
156152
- [ ] Architecture stubs present in `<package>/` (committed by Step 2)
157-
- [ ] Read all `docs/architecture/adr-NNN-*.md` files — understand the architectural decisions before writing any test
158-
- [ ] Test stub files exist in `tests/features/<feature-name>/` — one file per `Rule:` block, all `@id` functions present with `@pytest.mark.skip`; if missing, write them now before entering RED
153+
- [ ] Read `docs/architecture.md` — understand all architectural decisions before writing any test
154+
- [ ] Test stub files exist in `tests/features/<feature-name>/<rule_slug>_test.py` — one file per `Rule:` block, all `@id` stub functions present with `@pytest.mark.skip`; if missing, write them now before entering RED
159155

160156
### Write Test Stubs (if not present)
161157

162-
For each `Rule:` block in the in-progress `.feature` file, create `tests/features/<feature-name>/<rule-slug>_test.py` if it does not already exist. Write one function per `@id` Example, all skipped:
158+
For each `Rule:` block in the in-progress `.feature` file, create `tests/features/<feature-name>/<rule_slug>_test.py` if it does not already exist. Write one function per `@id` Example, all skipped:
163159

164160
```python
165161
@pytest.mark.skip(reason="not yet implemented")
166-
def test_<rule_slug>_<8char_hex>() -> None:
162+
def test_<feature_slug>_<@id>() -> None:
167163
"""
168-
Given: ...
169-
When: ...
170-
Then: ...
164+
<@id steps raw text including new lines>
171165
"""
172-
# Given
173-
# When
174-
# Then
175166
```
176167

177168
Run `uv run task gen-todo` after writing stubs to sync `@id` rows into `TODO.md`.
@@ -192,17 +183,17 @@ For each pending `@id`:
192183
```
193184
INNER LOOP
194185
├── RED
195-
│ ├── Confirm stub for this @id exists in tests/features/<feature-name>/ with @pytest.mark.skip
186+
│ ├── Confirm stub for this @id exists in tests/features/<feature-name>/<rule_slug>.feature with @pytest.mark.skip
196187
│ ├── Read existing stubs in `<package>/` — base the test on the current data model and signatures
197188
│ ├── Write test body (Given/When/Then → Arrange/Act/Assert); remove @pytest.mark.skip
198-
│ ├── Update stub signatures as needed — edit the `.py` file directly
189+
│ ├── Update <package> stub signatures as needed — edit the `.py` file directly
199190
│ ├── uv run task test-fast
200191
│ └── EXIT: this @id FAILS
201192
│ (if it passes: test is wrong — fix it first)
202193
203194
├── GREEN
204195
│ ├── Write minimum code — YAGNI + KISS only
205-
│ │ (no DRY, SOLID, OC here — those belong in REFACTOR)
196+
│ │ (no DRY, SOLID, OC, Docstring, type hint here — those belong in REFACTOR)
206197
│ ├── uv run task test-fast
207198
│ └── EXIT: this @id passes AND all prior tests pass
208199
│ (fix implementation only; do not advance to next @id)
@@ -221,7 +212,7 @@ Commit when a meaningful increment is green
221212
```bash
222213
uv run task lint
223214
uv run task static-check
224-
uv run task test # coverage must be 100%
215+
uv run task test-coverage # coverage must be 100%
225216
timeout 10s uv run task run
226217
```
227218

@@ -231,7 +222,7 @@ All must pass before Self-Declaration.
231222

232223
### Self-Declaration (once, after all quality gates pass)
233224

234-
Write into `TODO.md` under a `## Self-Declaration` block:
225+
Answer honestly the `## Self-Declaration` report:
235226

236227
```markdown
237228
## Self-Declaration
@@ -256,6 +247,7 @@ As a software-engineer I declare:
256247
* OC-7: ≤20 lines per function, ≤50 per class — AGREE/DISAGREE | longest: file:line
257248
* OC-8: ≤2 instance variables per class (behavioural classes only; dataclasses, Pydantic models, value objects, and TypedDicts are exempt) — AGREE/DISAGREE | file:line
258249
* OC-9: no getters/setters — AGREE/DISAGREE | file:line
250+
* Patterns: I have no good reason to refactor parts of the code using OOP or Design Patterns — AGREE/DISAGREE | file:line
259251
* Patterns: no creational smell — AGREE/DISAGREE | file:line
260252
* Patterns: no structural smell — AGREE/DISAGREE | file:line
261253
* Patterns: no behavioral smell — AGREE/DISAGREE | file:line
@@ -268,7 +260,7 @@ A `DISAGREE` answer is not automatic rejection — state the reason inline and f
268260

269261
Signal completion to the reviewer. Provide:
270262
- Feature file path
271-
- Self-Declaration from TODO.md
263+
- Self-Declaration report
272264
- Summary of what was implemented
273265

274266
---
@@ -278,40 +270,35 @@ Signal completion to the reviewer. Provide:
278270
### Test File Layout
279271

280272
```
281-
tests/features/<feature-name>/<rule-slug>_test.py
273+
tests/features/<feature-name>/<rule_slug>_test.py
282274
```
283275

284276
- `<feature-name>` = the `.feature` file stem
285-
- `<rule-slug>` = the `Rule:` title slugified
277+
- `<rule_slug>` = the `Rule:` title slugified
286278

287279
### Function Naming
288280

289281
```python
290-
def test_<rule_slug>_<8char_hex>() -> None:
282+
def test_<feature_slug>_<@id>() -> None:
291283
```
292284

293-
- `rule_slug` = the `Rule:` title with spaces/hyphens replaced by underscores, lowercase
294-
- `8char_hex` = the `@id` from the `Example:` block
285+
- `feature_slug` = the `.feature` file stem with spaces/hyphens replaced by underscores, lowercase
286+
- `@id` = the `@id` from the `Example:` block
295287

296288
### Docstring Format (mandatory)
297289

298290
New tests start as skipped stubs. Remove `@pytest.mark.skip` when implementing in the RED phase.
299291

300292
```python
301293
@pytest.mark.skip(reason="not yet implemented")
302-
def test_wall_bounce_a3f2b1c4() -> None:
294+
def test_<feature_slug>_<@id>() -> None:
303295
"""
304-
Given: A ball moving upward reaches y=0
305-
When: The physics engine processes the next frame
306-
Then: The ball velocity y-component becomes positive
296+
<@id steps raw text including new lines>
307297
"""
308-
# Given
309-
# When
310-
# Then
311298
```
312299

313300
**Rules**:
314-
- Docstring contains `Given:/When:/Then:` on separate indented lines
301+
- Docstring contains `Gherkin steps` as raw text on separate indented lines
315302
- No extra metadata in docstring — traceability comes from function name `@id` suffix
316303

317304
### Markers
@@ -320,6 +307,7 @@ def test_wall_bounce_a3f2b1c4() -> None:
320307
- `@pytest.mark.deprecated` — auto-skipped by conftest; used for superseded Examples
321308

322309
```python
310+
@pytest.mark.deprecated
323311
def test_wall_bounce_a3f2b1c4() -> None:
324312
...
325313

@@ -350,11 +338,11 @@ def test_wall_bounce_c4d5e6f7(x: float) -> None:
350338
**Rules**:
351339
- `@pytest.mark.slow` is mandatory on every `@given`-decorated test
352340
- `@example(...)` is optional but encouraged
353-
- Never use Hypothesis for: I/O, side effects, network calls, database writes
341+
- Do not use Hypothesis for: I/O, side effects, network calls, database writes
354342

355343
### Semantic Alignment Rule
356344

357-
The test's Given/When/Then must operate at the **same abstraction level** as the AC's Given/When/Then.
345+
The test's Given/When/Then must operate at the **same abstraction level** as the AC's Steps.
358346

359347
| AC says | Test must do |
360348
|---|---|
@@ -369,7 +357,7 @@ If testing through the real entry point is infeasible, escalate to PO to adjust
369357
- No `isinstance()`, `type()`, or internal attribute (`_x`) checks in assertions
370358
- One assertion concept per test (multiple `assert` ok if they verify the same thing)
371359
- No `pytest.mark.xfail` without written justification
372-
- `pytest.mark.skip` is only valid on stubs (`reason="not yet implemented"`) — remove it when implementing
360+
- `pytest.mark.skip(reason="not yet implemented")` is only valid on stubs — remove it when implementing
373361
- Test data embedded directly in the test, not loaded from external files
374362

375363
### Test Tool Decision
@@ -396,7 +384,7 @@ Extra tests in `tests/unit/` are allowed freely (coverage, edge cases, etc.) —
396384

397385
## Signature Design
398386

399-
Signatures are written during Step 2 (Architecture) and refined during Step 3 (RED). They live directly in the package `.py` files — never in the `.feature` file.
387+
<package> signatures are written during Step 2 (Architecture) and refined during Step 3 (RED). They live directly in the package `.py` files — never in the `.feature` file.
400388

401389
Key rules:
402390
- Bodies are always `...` in the architecture stub
@@ -420,4 +408,4 @@ class EmailAddress:
420408
class UserRepository(Protocol):
421409
def save(self, user: "User") -> None: ...
422410
def find_by_email(self, email: EmailAddress) -> "User | None": ...
423-
```
411+
```

0 commit comments

Comments
 (0)