Skip to content

Commit 5252e97

Browse files
committed
feat(workflow): restructure features to single .feature files with Rule: blocks
- Replace folder-per-feature with one .feature file per feature - User stories are now Rule: blocks; ACs are Example: blocks under each Rule - Discovery content embedded in feature description free text - Test layout: tests/features/<feature-name>/<rule-slug>_test.py - Function naming: test_<rule_slug>_<id_hex>() - Rewrite gen_test_stubs.py to parse Rule: blocks (one test file per Rule) - Update gen_todo.py to find .feature files directly in in-progress/ - Update all skills: scope, tdd, implementation, verify, session-workflow - Add mandatory Self-Declaration block in TODO.md at SELF-DECLARE phase - Enforce Hypothesis @given + @pytest.mark.slow on all tests/unit/ tests - Migrate completed/display-version to new single-file format - Clarify OC-8: fix must produce a new named class, no workarounds
1 parent e1db40e commit 5252e97

File tree

13 files changed

+544
-380
lines changed

13 files changed

+544
-380
lines changed

.opencode/skills/implementation/SKILL.md

Lines changed: 56 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -57,36 +57,36 @@ Never write production code before picking a specific failing test. Never refact
5757
If `packages` is missing or the directory does not exist, stop and resolve with the stakeholder before writing any code.
5858

5959
**Prerequisites — verify before starting:**
60-
1. `docs/features/in-progress/` contains only `.gitkeep` (no feature folders). If another feature folder exists, **STOP** — another feature is already in progress.
61-
2. The feature's `discovery.md` has `Status: BASELINED`. If not, escalate to the PO — Step 1 is incomplete.
62-
3. At least one `.feature` file in the feature folder contains `Example:` blocks with `@id` tags. If not, escalate to PO — criteria have not been written.
60+
1. `docs/features/in-progress/` contains only `.gitkeep` (no `.feature` files). If another `.feature` file exists, **STOP** — another feature is already in progress.
61+
2. The feature file's discovery section has `Status: BASELINED`. If not, escalate to the PO — Step 1 is incomplete.
62+
3. The feature file contains `Rule:` blocks with `Example:` blocks and `@id` tags. If not, escalate to PO — criteria have not been written.
6363

6464
**Steps:**
6565

66-
1. Move the feature folder from `backlog/` to `in-progress/`:
66+
1. Move the feature file from `backlog/` to `in-progress/`:
6767
```bash
68-
mv docs/features/backlog/<name>/ docs/features/in-progress/<name>/
68+
mv docs/features/backlog/<name>.feature docs/features/in-progress/<name>.feature
6969
```
7070
2. Update `TODO.md` Source path from `backlog/` to `in-progress/`.
71-
3. Read both `docs/features/discovery.md` (project-level) and the feature's `discovery.md`
71+
3. Read both `docs/features/discovery.md` (project-level) and the feature file's discovery section
7272
4. Run a silent pre-mortem: YAGNI, KISS, DRY, SOLID, Object Calisthenics, design patterns
73-
5. Add the Architecture section to `docs/features/in-progress/<name>/discovery.md`:
73+
5. Add the Architecture section to `docs/features/in-progress/<name>.feature` (append to the feature description, before the first `Rule:`):
7474

75-
```markdown
76-
## Architecture
75+
```gherkin
76+
Architecture:
7777
78-
### Module Structure
79-
- `<package>/domain/entity.py` — data classes and value objects
80-
- `<package>/domain/service.py` — business logic
78+
### Module Structure
79+
- `<package>/domain/entity.py` — data classes and value objects
80+
- `<package>/domain/service.py` — business logic
8181
82-
### Key Decisions
83-
ADR-001: <title>
84-
Decision: <what>
85-
Reason: <why in one sentence>
86-
Alternatives considered: <what was rejected and why>
82+
### Key Decisions
83+
ADR-001: <title>
84+
Decision: <what>
85+
Reason: <why in one sentence>
86+
Alternatives considered: <what was rejected and why>
8787
88-
### Build Changes (needs PO approval: yes/no)
89-
- New runtime dependency: <name> — reason: <why>
88+
### Build Changes (needs PO approval: yes/no)
89+
- New runtime dependency: <name> — reason: <why>
9090
```
9191

9292
6. **Architecture contradiction check**: Compare each ADR against each AC. If any architectural decision contradicts or circumvents an acceptance criterion, flag it and resolve with the PO before writing any production code.
@@ -140,14 +140,18 @@ Update `## Cycle State` Phase: `GREEN`
140140
1. **DRY**: extract duplication
141141
2. **SOLID**: split classes that have grown beyond one responsibility
142142
3. **Object Calisthenics** (enforce all 9 rules):
143-
1. One level of indentation per method — extract inner blocks to helpers
143+
1. One level of indentation per method — extract inner blocks to named helpers
144144
2. No `else` after `return` — return early, flatten the happy path
145145
3. Wrap all primitives — `EmailAddress(str)` not raw `str` for domain concepts
146146
4. First-class collections — wrap `list[User]` in a `UserList` class
147147
5. One dot per line — `user.address` then `address.city`, never `user.address.city`
148148
6. No abbreviations — `calculate` not `calc`, `manager` not `mgr`
149149
7. Small entities — functions ≤ 20 lines, classes ≤ 50 lines
150-
8. ≤ 2 instance variables — extract to value objects or split the class
150+
8. ≤ 2 instance variables — if a class has 3+ `self.x` in `__init__`, group related
151+
fields into a new named value object (Rule 3) or collection class (Rule 4). The fix
152+
must produce a **new named class** — hardcoding constants, inlining literals,
153+
using class-level variables, or moving fields to a parent class are all invalid
154+
workarounds and remain FAIL.
151155
9. No getters/setters — use commands (`activate()`) and queries (`is_active()`)
152156
4. **Type hints**: add/fix type annotations on all public functions and classes
153157
5. **Docstrings**: Google-style on all public functions and classes
@@ -185,6 +189,7 @@ After refactor, before moving to self-declaration:
185189
| Bare `int`/`str` as domain concept | Wrap in value object | Verify no raw primitives in signatures |
186190
| > 4 positional parameters | Group into dataclass | Verify parameter count |
187191
| `list[X]` as domain collection | Wrap in collection class | Verify no bare lists |
192+
| Class with 3+ `self.x` in `__init__` | Group related fields into a new named value object (OC-3) or collection class (OC-4) — **not** a dict, tuple, class variable, constant, or parent class | Count `self.` assignments again; each fix must produce a new named class |
188193

189194
```bash
190195
uv run task test-fast # must still pass — the ONLY check during refactor
@@ -196,52 +201,49 @@ Update `## Cycle State` Phase: `REFACTOR`
196201

197202
### Design Self-Declaration
198203

199-
After refactor is complete and `test-fast` passes, complete this checklist before requesting the reviewer check. Include the filled-in checklist in your reviewer check request — this is the structured audit target the reviewer will verify against the actual code.
200-
201-
*For each item: check the box and cite `file:line` evidence, or explain why the rule does not apply to the code changed in this cycle.*
202-
203-
#### YAGNI
204-
- [ ] No abstractions added beyond what the current acceptance criteria require
205-
- [ ] No speculative parameters, flags, or extension points for hypothetical future use
204+
After refactor is complete and `test-fast` passes, write the self-declaration **into `TODO.md`** under a `## Self-Declaration` block (replacing any prior one), then request the reviewer check. The reviewer will read `TODO.md` directly — do not paste the checklist into a separate message.
206205

207-
#### KISS
208-
- [ ] Every function can be described in one sentence without "and"
209-
- [ ] No unnecessary indirection, wrapper layers, or complexity
206+
**Write this block into `TODO.md` now, filling in every item before requesting review:**
210207

211-
#### DRY
212-
- [ ] No logic duplicated across functions or classes
213-
- [ ] Shared concepts extracted into a single reusable location
214-
215-
#### SOLID
216-
- [ ] **S** — each class/function has exactly one reason to change (`file:line`)
217-
- [ ] **O** — new behavior added via extension, not by editing existing class bodies
218-
- [ ] **L** — subtypes fully substitutable; no subtype narrows a contract or raises where base does not
219-
- [ ] **I** — no Protocol/ABC forces unused method implementations
220-
- [ ] **D** — domain classes import from abstractions (Protocols), not from I/O or framework layers directly
208+
```markdown
209+
## Self-Declaration (@id:<hex>)
210+
- [ ] YAGNI-1: No abstractions beyond current AC — `file:line`
211+
- [ ] YAGNI-2: No speculative parameters or flags for hypothetical future use — `file:line`
212+
- [ ] KISS-1: Every function has one job, describable in one sentence without "and" — `file:line`
213+
- [ ] KISS-2: No unnecessary indirection, wrapper layers, or complexity — `file:line`
214+
- [ ] DRY-1: No logic block duplicated across two or more locations — `file:line`
215+
- [ ] DRY-2: Every shared concept extracted to exactly one place — `file:line`
216+
- [ ] SOLID-S: Each class/function has one reason to change — `file:line`
217+
- [ ] SOLID-O: New behavior added by extension, no existing class body edited — `file:line` or N/A
218+
- [ ] SOLID-L: Every subtype fully substitutable; no narrowed contract or surprise raise — `file:line` or N/A
219+
- [ ] SOLID-I: No Protocol/ABC forces an implementor to leave a method as `...` or raise — `file:line` or N/A
220+
- [ ] SOLID-D: Domain classes depend on Protocols, not on I/O or framework imports directly — `file:line`
221+
- [ ] OC-1: Max one indent level per method; inner blocks extracted to named helpers — deepest: `file:line`
222+
- [ ] OC-2: No `else` after `return`; all branches return early and the happy path is flat — `file:line` or N/A
223+
- [ ] OC-3: No bare `int`/`str`/`float` as domain concepts in public signatures; each wrapped in a named type — `file:line` or N/A
224+
- [ ] OC-4: No bare `list[X]`/`set[X]` as domain values; each wrapped in a named collection class — `file:line` or N/A
225+
- [ ] OC-5: No `a.b.c()` chains; each dot navigation step assigned to a named local — `file:line` or N/A
226+
- [ ] OC-6: No abbreviations anywhere; every name is a full word readable without context — `file:line` or N/A
227+
- [ ] OC-7: Every function ≤ 20 lines, every class ≤ 50 lines — longest: `file:line`
228+
- [ ] OC-8: Every class has ≤ 2 `self.x` in `__init__`; if > 2 before this cycle, name the new value object extracted and cite `file:line` per class
229+
- [ ] OC-9: No `get_x()`/`set_x()` pairs; state changes via commands, queries return values — `file:line` or N/A
230+
- [ ] Semantic: test Given/When/Then operates at the same abstraction level as the AC — `file:line`
231+
```
221232

222-
#### Object Calisthenics
223-
- [ ] Rule 1 — one indent level per method (`file:line` of deepest nesting)
224-
- [ ] Rule 2 — no `else` after `return`; early returns only
225-
- [ ] Rule 3 — primitives wrapped: no bare `int`/`str` as domain concepts in public signatures
226-
- [ ] Rule 4 — collections wrapped: no bare `list[X]` as domain values
227-
- [ ] Rule 5 — one dot per line: no `a.b.c()` chains
228-
- [ ] Rule 6 — no abbreviations in names
229-
- [ ] Rule 7 — functions ≤ 20 lines, classes ≤ 50 lines (cite longest: `file:line`)
230-
- [ ] Rule 8 — ≤ 2 instance variables per class (cite any with 2: `file:line`)
231-
- [ ] Rule 9 — no getters/setters; tell-don't-ask (`get_x()`/`set_x()` = FAIL)
233+
*For every item: check the box AND cite `file:line` evidence, or write `N/A` with a one-line reason. An unchecked box or missing evidence is an automatic REJECTED.*
232234

233235
Update `## Cycle State` Phase: `SELF-DECLARE`
234236

235237
## REVIEWER CHECK — Code Design Only
236238

237-
After each test goes green + refactor + self-declaration, **STOP** and request a reviewer check. Include the filled-in Design Self-Declaration checklist in your request.
239+
After each test goes green + refactor + self-declaration, **STOP** and request a reviewer check. The reviewer will read the `## Self-Declaration` block from `TODO.md` directly — point them to it.
238240

239241
**STOP — request a reviewer check of code design and semantic alignment.**
240242
**WAIT for APPROVED before committing.**
241243

242244
The reviewer is scoped to **code design only** (not full Step 5):
243245

244-
**What the reviewer receives**: The developer's completed Design Self-Declaration with `file:line` evidence for each rule.
246+
**What the reviewer receives**: The developer's completed `## Self-Declaration` block in `TODO.md`, with `file:line` evidence for each rule.
245247

246248
**What the reviewer does**: Independently inspects the actual code for each rule the developer claimed compliant. The self-declaration is an audit target — the reviewer verifies claims, not just reads them.
247249

@@ -307,7 +309,7 @@ If during implementation you discover a behavior not covered by existing accepta
307309
- Note the gap in TODO.md under `## Next`
308310
- The PO will decide whether to add a new Example to the `.feature` file
309311

310-
Extra tests in `tests/unit/` are allowed freely (coverage, edge cases, etc.) — these do not need `@id` traceability.
312+
Extra tests in `tests/unit/` are allowed freely (coverage, edge cases, etc.) — these do not need `@id` traceability. **Every test in `tests/unit/` must be a Hypothesis property test: `@given` is required, `@pytest.mark.slow` is mandatory, plain `assert` tests without `@given` are forbidden.**
311313

312314
## Signature Design
313315

0 commit comments

Comments
 (0)