Skip to content

Commit 6153e61

Browse files
authored
feat(workflow): architecture stubs, feature-selection, refactor skill, research split — v5.1 (#67)
* feat(branding): add logo, banner and rewrite README for v5.1 - Add docs/images/logo.svg — Doric temple mark, marble palette - Add docs/images/banner.svg — logo + Python Project Template typeset - Rewrite README.md with banner hero, tighter structure and cleaner copy * chore: remove feedback.md * fix(branding): widen banner to full width, add spacing above shields * docs(research): split academic_research.md into domain-based files under docs/scientific-research/ * docs(architecture): add ADR template for Step 2 architectural decisions * feat(workflow): add standalone refactor skill with Fowler catalogue, SOLID, OC, and green-bar rules * feat(workflow): add feature-selection skill with WSJF scoring for backlog prioritisation * refactor(skills): narrow design-patterns to pure GoF catalogue, drop SOLID/OC/LoD * feat(workflow): overhaul Step 2/3 — package stubs replace feature file architecture section, stub rules drop docstrings, folder structure as suggestions * feat(workflow): verify skill — add orphaned stub check, structured Next Steps in report * feat(workflow): session-workflow — enforce Next line format with agent name, idle state loads feature-selection * chore(agents): register feature-selection and refactor skills across all agent files * docs(workflow): sync workflow.md with all Step 2/3/4 changes — stubs, feature selection, handoff clarity * chore(release): bump version to v5.1.20260418 - Emergent Colugo
1 parent 7eca266 commit 6153e61

File tree

30 files changed

+1930
-1611
lines changed

30 files changed

+1930
-1611
lines changed

.opencode/agents/product-owner.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,5 @@ When a gap is reported (by software-engineer or reviewer):
5959
## Available Skills
6060

6161
- `session-workflow` — session start/end protocol
62+
- `feature-selection` — when TODO.md is idle: score and select next backlog feature using WSJF
6263
- `scope` — Step 1: 3-session discovery (Phase 1 + 2), stories (Phase 3), and criteria (Phase 4)

.opencode/agents/software-engineer.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ Load `skill session-workflow` first — it reads TODO.md, orients you to the cur
3838
| Step | Action |
3939
|---|---|
4040
| **Step 2 — ARCH** | Load `skill implementation` — contains Step 2 architecture protocol |
41-
| **Step 3 — TDD LOOP** | Load `skill implementation` — contains Step 3 TDD Loop |
41+
| **Step 3 — TDD LOOP** | Load `skill implementation` — contains Step 3 TDD Loop; load `skill refactor` when entering REFACTOR phase or doing preparatory refactoring |
4242
| **Step 5 — after PO accepts** | Load `skill pr-management` and `skill git-release` as needed |
4343

4444
## Ownership Rules
@@ -57,7 +57,8 @@ If during implementation you discover behavior not covered by existing acceptanc
5757

5858
- `session-workflow` — session start/end protocol
5959
- `implementation` — Steps 2-3: architecture + TDD loop
60-
- `design-patterns` — on-demand when smell detected during refactor
60+
- `refactor` — REFACTOR phase and preparatory refactoring (load on-demand)
61+
- `design-patterns` — on-demand when smell detected during architecture or refactor
6162
- `pr-management` — Step 5: PRs with conventional commits
6263
- `git-release` — Step 5: calver versioning and themed release naming
6364
- `create-skill` — meta: create new skills when needed

.opencode/skills/create-skill/SKILL.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Before writing any skill, research the domain to ground the skill in industry st
2727
- Vendor documentation (OpenAI, Anthropic, Google, Microsoft)
2828
- Industry standards (ISO, NIST, OMG)
2929
- Established methodologies (e.g., FDD, Scrum, Kanban for process skills)
30-
3. **Read existing research**: Check `docs/academic_research.md` for related entries
30+
3. **Read existing research**: Check `docs/scientific-research/` for related entries — each file covers a domain (testing, oop-design, architecture, ai-agents, etc.)
3131
4. **Synthesize conclusions**: Extract actionable conclusions — what works, why, and when to apply it
3232
5. **Embed as guidance**: Write the skill's steps, checklists, and decision rules based on those conclusions — not as academic citations but as direct guidance ("Use X because it produces Y outcome")
3333

@@ -133,6 +133,7 @@ Add the skill name to the agent's "Available Skills" section so the agent knows
133133
| Skill | Used By | Purpose |
134134
|---|---|---|
135135
| `session-workflow` | all agents | Session start/end protocol |
136+
| `feature-selection` | product-owner | Score and select next backlog feature (WSJF) |
136137
| `scope` | product-owner | Step 1: define acceptance criteria |
137138
| `implementation` | software-engineer | Steps 2-3: architecture + TDD loop |
138139
| `design-patterns` | software-engineer | Steps 2, 3: refactor when smell detected |

.opencode/skills/design-patterns/SKILL.md

Lines changed: 11 additions & 188 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
11
---
22
name: design-patterns
3-
description: Reference skill for GoF design patterns, SOLID, Object Calisthenics, Python Zen, and other SE principles — with smell triggers and Python before/after examples
4-
version: "1.0"
3+
description: GoF design pattern catalogue — smell triggers and Python before/after examples
4+
version: "2.0"
55
author: software-engineer
66
audience: software-engineer
77
workflow: feature-lifecycle
88
---
99

1010
# Design Patterns Reference
1111

12-
Load this skill when:
13-
- Running the architecture smell check in Step 2 and a smell is detected
14-
- Refactoring in Step 3 and a pattern smell appears in the self-declaration
12+
Load this skill when the refactor skill's smell table points to a GoF pattern and you need the Python before/after example.
13+
14+
Sources: Gamma, Helm, Johnson, Vlissides. *Design Patterns: Elements of Reusable Object-Oriented Software*. Addison-Wesley, 1995. See `docs/scientific-research/oop-design.md` entry 34.
1515

1616
---
1717

1818
## How to Use This Skill
1919

20-
1. **Identify the smell** from the checklist in your self-declaration or architecture check
20+
1. **Identify the smell** from the refactor skill's lookup table
2121
2. **Find the smell category** below (Creational / Structural / Behavioral)
2222
3. **Read the trigger and the before/after example**
23-
4. **Apply the pattern** and update the Architecture section (Step 2) or the refactored code (Step 3)
23+
4. **Apply the pattern** update the stub files (Step 2) or the refactored code (Step 3)
2424

2525
---
2626

@@ -178,7 +178,7 @@ def apply_discount(order: Order, strategy: DiscountStrategy) -> Money:
178178
#### Smell: Feature Envy
179179
**Signal**: A method in class A uses data from class B more than its own data. The method "envies" class B.
180180

181-
**Pattern**: Move Method to the envied class (not a GoF pattern — a Fowler refactoring that often precedes Strategy or Command)
181+
**Pattern**: Move Method to the envied class (Fowler refactoring that often precedes Strategy or Command)
182182

183183
```python
184184
# BEFORE — OrderPrinter knows too much about Order internals
@@ -309,9 +309,9 @@ class Order:
309309
class Order:
310310
def confirm(self) -> None:
311311
self.status = "confirmed"
312-
EmailService().send_confirmation(self) # direct coupling
313-
InventoryService().reserve(self) # direct coupling
314-
AnalyticsService().record_conversion(self) # direct coupling
312+
EmailService().send_confirmation(self) # direct coupling
313+
InventoryService().reserve(self) # direct coupling
314+
AnalyticsService().record_conversion(self) # direct coupling
315315
```
316316

317317
```python
@@ -380,182 +380,6 @@ class JsonImporter(Importer):
380380

381381
---
382382

383-
## SOLID — Python Examples
384-
385-
### S — Single Responsibility
386-
One class, one reason to change.
387-
388-
```python
389-
# WRONG — Report handles both data and formatting
390-
class Report:
391-
def generate(self) -> dict: ...
392-
def to_pdf(self) -> bytes: ... # separate concern
393-
def to_csv(self) -> str: ... # separate concern
394-
395-
# RIGHT — split concerns
396-
class Report:
397-
def generate(self) -> ReportData: ...
398-
399-
class PdfRenderer:
400-
def render(self, data: ReportData) -> bytes: ...
401-
```
402-
403-
### O — Open/Closed
404-
Open for extension, closed for modification.
405-
406-
```python
407-
# WRONG — must edit this function to add a new format
408-
def export(data: ReportData, fmt: str) -> bytes:
409-
if fmt == "pdf": ...
410-
elif fmt == "csv": ...
411-
412-
# RIGHT — new formats extend without touching existing code
413-
class Exporter(Protocol):
414-
def export(self, data: ReportData) -> bytes: ...
415-
416-
class PdfExporter:
417-
def export(self, data: ReportData) -> bytes: ...
418-
```
419-
420-
### L — Liskov Substitution
421-
Subtypes must be fully substitutable for their base type.
422-
423-
```python
424-
# WRONG — ReadOnlyFile violates the contract of File
425-
class File:
426-
def write(self, content: str) -> None: ...
427-
428-
class ReadOnlyFile(File):
429-
def write(self, content: str) -> None:
430-
raise PermissionError # narrows the contract — LSP violation
431-
432-
# RIGHT — separate interfaces for readable and writable
433-
class ReadableFile(Protocol):
434-
def read(self) -> str: ...
435-
436-
class WritableFile(Protocol):
437-
def write(self, content: str) -> None: ...
438-
```
439-
440-
### I — Interface Segregation
441-
No implementor should be forced to implement methods it doesn't use.
442-
443-
```python
444-
# WRONG — Printer is forced to implement scan() and fax()
445-
class Machine(Protocol):
446-
def print(self, doc: Document) -> None: ...
447-
def scan(self, doc: Document) -> None: ...
448-
def fax(self, doc: Document) -> None: ...
449-
450-
# RIGHT — each capability is its own Protocol
451-
class Printer(Protocol):
452-
def print(self, doc: Document) -> None: ...
453-
454-
class Scanner(Protocol):
455-
def scan(self, doc: Document) -> None: ...
456-
```
457-
458-
### D — Dependency Inversion
459-
Domain depends on abstractions (Protocols), not on concrete I/O or frameworks.
460-
461-
```python
462-
# WRONG — domain imports infrastructure directly
463-
from app.db import PostgresConnection
464-
465-
class OrderRepository:
466-
def __init__(self) -> None:
467-
self.db = PostgresConnection() # domain imports infra
468-
469-
# RIGHT — domain defines the Protocol; infra implements it
470-
class OrderRepository(Protocol):
471-
def find(self, order_id: OrderId) -> Order: ...
472-
def save(self, order: Order) -> None: ...
473-
474-
class PostgresOrderRepository: # in adapters/
475-
def find(self, order_id: OrderId) -> Order: ...
476-
def save(self, order: Order) -> None: ...
477-
```
478-
479-
---
480-
481-
## Object Calisthenics — Python Rules
482-
483-
Jeff Bay's 9 rules for object-oriented discipline. Each has a Python signal.
484-
485-
| Rule | Constraint | Python Signal of Violation |
486-
|---|---|---|
487-
| **OC-1** | One indent level per method | `for` inside `if` inside a method body |
488-
| **OC-2** | No `else` after `return` | `if cond: return x \n else: return y` |
489-
| **OC-3** | Wrap all primitives that have domain meaning | `def process(user_id: int)` instead of `def process(user_id: UserId)` |
490-
| **OC-4** | Wrap all collections that have domain meaning | `list[Order]` passed around instead of `OrderCollection` |
491-
| **OC-5** | One dot per line | `obj.repo.find(id).name` |
492-
| **OC-6** | No abbreviations | `usr`, `mgr`, `cfg`, `val`, `tmp` |
493-
| **OC-7** | Keep classes small (≤50 lines) and methods short (≤20 lines) | Any method requiring scrolling |
494-
| **OC-8** | No class with more than 2 instance variables | `__init__` with 3+ `self.x =` assignments |
495-
| **OC-9** | No getters/setters | `def get_name(self)` / `def set_name(self, v)` |
496-
497-
---
498-
499-
## Python Zen — Mapped to Code Practices
500-
501-
The relevant items from PEP 20 (`import this`) with concrete code implications:
502-
503-
| Zen Item | Code Practice |
504-
|---|---|
505-
| Beautiful is better than ugly | Name things clearly; prefer named types over bare primitives |
506-
| Explicit is better than implicit | Explicit return types; explicit Protocol dependencies; no magic |
507-
| Simple is better than complex | KISS — one function, one job; prefer a plain function over a class |
508-
| Complex is better than complicated | A well-designed abstraction is acceptable; an accidental tangle is not |
509-
| Flat is better than nested | OC-1 — one indent level; early returns |
510-
| Sparse is better than dense | One statement per line; no semicolons; no lambda chains |
511-
| Readability counts | OC-6 — no abbreviations; docstrings on every public function |
512-
| Special cases aren't special enough to break the rules | Do not add `if isinstance` branches to avoid refactoring |
513-
| Errors should never pass silently | No bare `except:`; no `except Exception: pass` |
514-
| In the face of ambiguity, refuse the temptation to guess | Raise on invalid input; never silently return a default |
515-
| There should be one obvious way to do it | DRY — every shared concept in exactly one place |
516-
| If the implementation is hard to explain, it's a bad idea | KISS — if you can't describe the function in one sentence, split it |
517-
518-
---
519-
520-
## Other Principles
521-
522-
### Law of Demeter (Tell, Don't Ask)
523-
A method should only call methods on:
524-
- `self`
525-
- Objects passed as parameters
526-
- Objects it creates
527-
- Direct component objects (`self.x`)
528-
529-
**Violation signal**: `a.b.c()` — two dots. Assign `b = a.b` and call `b.c()`, or better: ask `a` to do what you need (`a.do_thing()`).
530-
531-
### Command-Query Separation (CQS)
532-
A method either **changes state** (command) or **returns a value** (query) — never both.
533-
534-
```python
535-
# WRONG — pop() both returns and mutates
536-
value = stack.pop()
537-
538-
# RIGHT (CQS strict)
539-
value = stack.peek() # query — no mutation
540-
stack.remove_top() # command — no return value
541-
```
542-
543-
Note: Python's standard library violates CQS in places (`list.pop()`, `dict.update()`). Apply CQS to your domain objects; do not fight the stdlib.
544-
545-
### Tell, Don't Ask
546-
Instead of querying an object's state and acting on it externally, tell the object to do the work itself.
547-
548-
```python
549-
# WRONG — ask state, decide externally
550-
if order.status == OrderStatus.PENDING:
551-
order.status = OrderStatus.CONFIRMED
552-
553-
# RIGHT — tell the object
554-
order.confirm() # Order decides if the transition is valid
555-
```
556-
557-
---
558-
559383
## Quick Smell → Pattern Lookup
560384

561385
| Smell | Pattern |
@@ -569,4 +393,3 @@ order.confirm() # Order decides if the transition is valid
569393
| Class directly calls B, C, D on state change | Observer |
570394
| Two functions share the same skeleton, differ in one step | Template Method |
571395
| Subsystem is complex and callers need a simple entry point | Facade |
572-
| Object needs logging/caching without changing its class | Decorator / Proxy |

0 commit comments

Comments
 (0)