mirror of
https://github.com/mattpocock/skills.git
synced 2026-04-30 14:03:53 +07:00
Added DDD-awareness to improve-codebase-architecture
This commit is contained in:
@@ -52,7 +52,7 @@ These skills help you write, refactor, and fix code.
|
|||||||
npx skills@latest add mattpocock/skills/triage-issue
|
npx skills@latest add mattpocock/skills/triage-issue
|
||||||
```
|
```
|
||||||
|
|
||||||
- **improve-codebase-architecture** — Explore a codebase for architectural improvement opportunities, focusing on deepening shallow modules and improving testability.
|
- **improve-codebase-architecture** — Find deepening opportunities in a codebase, informed by the domain language in `CONTEXT.md` and the decisions in `docs/adr/`.
|
||||||
|
|
||||||
```
|
```
|
||||||
npx skills@latest add mattpocock/skills/improve-codebase-architecture
|
npx skills@latest add mattpocock/skills/improve-codebase-architecture
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# Deepening
|
||||||
|
|
||||||
|
How to deepen a cluster of shallow modules safely, given its dependencies.
|
||||||
|
|
||||||
|
## Dependency categories
|
||||||
|
|
||||||
|
When assessing a candidate for deepening, classify its dependencies. The category determines how the deepened module is tested.
|
||||||
|
|
||||||
|
### 1. In-process
|
||||||
|
|
||||||
|
Pure computation, in-memory state, no I/O. Always deepenable — just merge the modules and test directly.
|
||||||
|
|
||||||
|
### 2. Local-substitutable
|
||||||
|
|
||||||
|
Dependencies that have local test stand-ins (e.g. PGLite for Postgres, in-memory filesystem). Deepenable if the test substitute exists. The deepened module is tested with the local stand-in running in the test suite.
|
||||||
|
|
||||||
|
### 3. Remote but owned (Ports & Adapters)
|
||||||
|
|
||||||
|
Your own services across a network boundary (microservices, internal APIs). Define a port (interface) at the module boundary. The deep module owns the logic; the transport is injected. Tests use an in-memory adapter. Production uses the real HTTP/gRPC/queue adapter.
|
||||||
|
|
||||||
|
Recommendation shape: *"Define a shared interface (port), implement an HTTP adapter for production and an in-memory adapter for testing, so the logic can be tested as one deep module even though it's deployed across a network boundary."*
|
||||||
|
|
||||||
|
### 4. True external (Mock)
|
||||||
|
|
||||||
|
Third-party services (Stripe, Twilio, etc.) you don't control. Mock at the boundary. The deepened module takes the external dependency as an injected port, and tests provide a mock implementation.
|
||||||
|
|
||||||
|
## Testing strategy: replace, don't layer
|
||||||
|
|
||||||
|
- Old unit tests on shallow modules are waste once boundary tests exist — delete them.
|
||||||
|
- Write new tests at the deepened module's interface boundary.
|
||||||
|
- Tests assert on observable outcomes through the public interface, not internal state.
|
||||||
|
- Tests should survive internal refactors — they describe behavior, not implementation.
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# Interface Design
|
||||||
|
|
||||||
|
When the user wants to explore alternative interfaces for a chosen deepening candidate, use this parallel sub-agent pattern. Based on "Design It Twice" (Ousterhout) — your first idea is unlikely to be the best.
|
||||||
|
|
||||||
|
## Process
|
||||||
|
|
||||||
|
### 1. Frame the problem space
|
||||||
|
|
||||||
|
Before spawning sub-agents, write a user-facing explanation of the problem space for the chosen candidate:
|
||||||
|
|
||||||
|
- The constraints any new interface would need to satisfy
|
||||||
|
- The dependencies it would need to rely on (see [DEEPENING.md](DEEPENING.md))
|
||||||
|
- A rough illustrative code sketch to ground the constraints — this is not a proposal, just a way to make the constraints concrete
|
||||||
|
|
||||||
|
Show this to the user, then immediately proceed to Step 2. The user reads and thinks while the sub-agents work in parallel.
|
||||||
|
|
||||||
|
### 2. Spawn sub-agents
|
||||||
|
|
||||||
|
Spawn 3+ sub-agents in parallel using the Agent tool. Each must produce a **radically different** interface for the deepened module.
|
||||||
|
|
||||||
|
Prompt each sub-agent with a separate technical brief (file paths, coupling details, dependency category from [DEEPENING.md](DEEPENING.md), what's being hidden). The brief is independent of the user-facing problem-space explanation in Step 1. Give each agent a different design constraint:
|
||||||
|
|
||||||
|
- Agent 1: "Minimize the interface — aim for 1-3 entry points max"
|
||||||
|
- Agent 2: "Maximize flexibility — support many use cases and extension"
|
||||||
|
- Agent 3: "Optimize for the most common caller — make the default case trivial"
|
||||||
|
- Agent 4 (if applicable): "Design around the ports & adapters pattern for cross-boundary dependencies"
|
||||||
|
|
||||||
|
Include CONTEXT.md vocabulary in the brief so each sub-agent names things consistently with the project's domain language.
|
||||||
|
|
||||||
|
Each sub-agent outputs:
|
||||||
|
|
||||||
|
1. Interface signature (types, methods, params)
|
||||||
|
2. Usage example showing how callers use it
|
||||||
|
3. What complexity it hides internally
|
||||||
|
4. Dependency strategy (see [DEEPENING.md](DEEPENING.md))
|
||||||
|
5. Trade-offs
|
||||||
|
|
||||||
|
### 3. Present and compare
|
||||||
|
|
||||||
|
Present designs sequentially so the user can absorb each one, then compare them in prose.
|
||||||
|
|
||||||
|
After comparing, give your own recommendation: which design you think is strongest and why. If elements from different designs would combine well, propose a hybrid. Be opinionated — the user wants a strong read, not a menu.
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
# Reference
|
|
||||||
|
|
||||||
## Dependency Categories
|
|
||||||
|
|
||||||
When assessing a candidate for deepening, classify its dependencies:
|
|
||||||
|
|
||||||
### 1. In-process
|
|
||||||
|
|
||||||
Pure computation, in-memory state, no I/O. Always deepenable — just merge the modules and test directly.
|
|
||||||
|
|
||||||
### 2. Local-substitutable
|
|
||||||
|
|
||||||
Dependencies that have local test stand-ins (e.g., PGLite for Postgres, in-memory filesystem). Deepenable if the test substitute exists. The deepened module is tested with the local stand-in running in the test suite.
|
|
||||||
|
|
||||||
### 3. Remote but owned (Ports & Adapters)
|
|
||||||
|
|
||||||
Your own services across a network boundary (microservices, internal APIs). Define a port (interface) at the module boundary. The deep module owns the logic; the transport is injected. Tests use an in-memory adapter. Production uses the real HTTP/gRPC/queue adapter.
|
|
||||||
|
|
||||||
Recommendation shape: "Define a shared interface (port), implement an HTTP adapter for production and an in-memory adapter for testing, so the logic can be tested as one deep module even though it's deployed across a network boundary."
|
|
||||||
|
|
||||||
### 4. True external (Mock)
|
|
||||||
|
|
||||||
Third-party services (Stripe, Twilio, etc.) you don't control. Mock at the boundary. The deepened module takes the external dependency as an injected port, and tests provide a mock implementation.
|
|
||||||
|
|
||||||
## Testing Strategy
|
|
||||||
|
|
||||||
The core principle: **replace, don't layer.**
|
|
||||||
|
|
||||||
- Old unit tests on shallow modules are waste once boundary tests exist — delete them
|
|
||||||
- Write new tests at the deepened module's interface boundary
|
|
||||||
- Tests assert on observable outcomes through the public interface, not internal state
|
|
||||||
- Tests should survive internal refactors — they describe behavior, not implementation
|
|
||||||
|
|
||||||
## Issue Template
|
|
||||||
|
|
||||||
<issue-template>
|
|
||||||
|
|
||||||
## Problem
|
|
||||||
|
|
||||||
Describe the architectural friction:
|
|
||||||
|
|
||||||
- Which modules are shallow and tightly coupled
|
|
||||||
- What integration risk exists in the seams between them
|
|
||||||
- Why this makes the codebase harder to navigate and maintain
|
|
||||||
|
|
||||||
## Proposed Interface
|
|
||||||
|
|
||||||
The chosen interface design:
|
|
||||||
|
|
||||||
- Interface signature (types, methods, params)
|
|
||||||
- Usage example showing how callers use it
|
|
||||||
- What complexity it hides internally
|
|
||||||
|
|
||||||
## Dependency Strategy
|
|
||||||
|
|
||||||
Which category applies and how dependencies are handled:
|
|
||||||
|
|
||||||
- **In-process**: merged directly
|
|
||||||
- **Local-substitutable**: tested with [specific stand-in]
|
|
||||||
- **Ports & adapters**: port definition, production adapter, test adapter
|
|
||||||
- **Mock**: mock boundary for external services
|
|
||||||
|
|
||||||
## Testing Strategy
|
|
||||||
|
|
||||||
- **New boundary tests to write**: describe the behaviors to verify at the interface
|
|
||||||
- **Old tests to delete**: list the shallow module tests that become redundant
|
|
||||||
- **Test environment needs**: any local stand-ins or adapters required
|
|
||||||
|
|
||||||
## Implementation Recommendations
|
|
||||||
|
|
||||||
Durable architectural guidance that is NOT coupled to current file paths:
|
|
||||||
|
|
||||||
- What the module should own (responsibilities)
|
|
||||||
- What it should hide (implementation details)
|
|
||||||
- What it should expose (the interface contract)
|
|
||||||
- How callers should migrate to the new interface
|
|
||||||
|
|
||||||
</issue-template>
|
|
||||||
@@ -1,19 +1,30 @@
|
|||||||
---
|
---
|
||||||
name: improve-codebase-architecture
|
name: improve-codebase-architecture
|
||||||
description: Explore a codebase to find opportunities for architectural improvement, focusing on making the codebase more testable by deepening shallow modules. Use when user wants to improve architecture, find refactoring opportunities, consolidate tightly-coupled modules, or make a codebase more AI-navigable.
|
description: Find deepening opportunities in a codebase, informed by the domain language in CONTEXT.md and the decisions in docs/adr/. Use when the user wants to improve architecture, find refactoring opportunities, consolidate tightly-coupled modules, or make a codebase more testable and AI-navigable.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Improve Codebase Architecture
|
# Improve Codebase Architecture
|
||||||
|
|
||||||
Explore a codebase like an AI would, surface architectural friction, discover opportunities for improving testability, and propose module-deepening refactors as GitHub issue RFCs.
|
Surface architectural friction and propose deepening opportunities — refactors that consolidate shallow modules into deeper ones with smaller interfaces. The aim is testability and AI-navigability.
|
||||||
|
|
||||||
A **deep module** (John Ousterhout, "A Philosophy of Software Design") has a small interface hiding a large implementation. Deep modules are more testable, more AI-navigable, and let you test at the boundary instead of inside.
|
A **deep module** (Ousterhout, "A Philosophy of Software Design") has a small interface hiding a large implementation. Deep modules are easier to test at the boundary and easier for both humans and AI to navigate.
|
||||||
|
|
||||||
|
This skill is _informed_ by the project's domain model — `CONTEXT.md` and any `docs/adr/`. The domain language gives names to good module boundaries; ADRs record decisions the skill should not re-litigate.
|
||||||
|
|
||||||
|
See [CONTEXT-FORMAT.md](../domain-model/CONTEXT-FORMAT.md) and [ADR-FORMAT.md](../domain-model/ADR-FORMAT.md) for the file formats.
|
||||||
|
|
||||||
## Process
|
## Process
|
||||||
|
|
||||||
### 1. Explore the codebase
|
### 1. Explore
|
||||||
|
|
||||||
Use the Agent tool with subagent_type=Explore to navigate the codebase naturally. Do NOT follow rigid heuristics — explore organically and note where you experience friction:
|
Read existing documentation first:
|
||||||
|
|
||||||
|
- `CONTEXT.md` (or `CONTEXT-MAP.md` + each `CONTEXT.md` in a multi-context repo)
|
||||||
|
- Relevant ADRs in `docs/adr/` (and any context-scoped `docs/adr/` directories)
|
||||||
|
|
||||||
|
If any of these files don't exist, proceed silently — don't flag their absence or suggest creating them upfront.
|
||||||
|
|
||||||
|
Then use the Agent tool with `subagent_type=Explore` to walk the codebase. Don't follow rigid heuristics — explore organically and note where you experience friction:
|
||||||
|
|
||||||
- Where does understanding one concept require bouncing between many small files?
|
- Where does understanding one concept require bouncing between many small files?
|
||||||
- Where are modules so shallow that the interface is nearly as complex as the implementation?
|
- Where are modules so shallow that the interface is nearly as complex as the implementation?
|
||||||
@@ -25,52 +36,26 @@ The friction you encounter IS the signal.
|
|||||||
|
|
||||||
### 2. Present candidates
|
### 2. Present candidates
|
||||||
|
|
||||||
Present a numbered list of deepening opportunities. For each candidate, show:
|
Present a numbered list of deepening opportunities. For each candidate:
|
||||||
|
|
||||||
- **Cluster**: Which modules/concepts are involved
|
- **Cluster**: which modules/concepts are involved
|
||||||
- **Why they're coupled**: Shared types, call patterns, co-ownership of a concept
|
- **Why they're coupled**: shared types, call patterns, co-ownership of a concept
|
||||||
- **Dependency category**: See [REFERENCE.md](REFERENCE.md) for the four categories
|
- **Dependency category**: see [DEEPENING.md](DEEPENING.md)
|
||||||
- **Test impact**: What existing tests would be replaced by boundary tests
|
- **Test impact**: what existing tests would be replaced by boundary tests
|
||||||
|
|
||||||
|
**Use CONTEXT.md vocabulary when describing candidates.** If `CONTEXT.md` defines "Order," talk about "the Order intake module" rather than "the FooBarHandler."
|
||||||
|
|
||||||
|
**ADR conflicts**: if a candidate contradicts an existing ADR, only surface it when the friction you noticed is real enough to warrant revisiting the ADR. Mark it clearly (e.g. _"contradicts ADR-0007 — but worth reopening because…"_). Don't list every theoretical refactor an ADR forbids.
|
||||||
|
|
||||||
Do NOT propose interfaces yet. Ask the user: "Which of these would you like to explore?"
|
Do NOT propose interfaces yet. Ask the user: "Which of these would you like to explore?"
|
||||||
|
|
||||||
### 3. User picks a candidate
|
### 3. Grilling loop
|
||||||
|
|
||||||
### 4. Frame the problem space
|
Once the user picks a candidate, drop into a grilling conversation. Walk the design tree with them — constraints, dependencies, the shape of the deepened module, what gets hidden, what tests survive.
|
||||||
|
|
||||||
Before spawning sub-agents, write a user-facing explanation of the problem space for the chosen candidate:
|
Side effects happen inline as decisions crystallize:
|
||||||
|
|
||||||
- The constraints any new interface would need to satisfy
|
- **Naming a deepened module after a concept not in `CONTEXT.md`?** Add the term to `CONTEXT.md` — same discipline as `/domain-model` (see [CONTEXT-FORMAT.md](../domain-model/CONTEXT-FORMAT.md)). Create the file lazily if it doesn't exist.
|
||||||
- The dependencies it would need to rely on
|
- **Sharpening a fuzzy term during the conversation?** Update `CONTEXT.md` right there.
|
||||||
- A rough illustrative code sketch to make the constraints concrete — this is not a proposal, just a way to ground the constraints
|
- **User rejects the candidate with a load-bearing reason?** Offer an ADR, framed as: _"Want me to record this as an ADR so future architecture reviews don't re-suggest it?"_ Only offer when the reason would actually be needed by a future explorer to avoid re-suggesting the same thing — skip ephemeral reasons ("not worth it right now") and self-evident ones. See [ADR-FORMAT.md](../domain-model/ADR-FORMAT.md).
|
||||||
|
- **Want to explore alternative interfaces for the deepened module?** See [INTERFACE-DESIGN.md](INTERFACE-DESIGN.md).
|
||||||
Show this to the user, then immediately proceed to Step 5. The user reads and thinks about the problem while the sub-agents work in parallel.
|
|
||||||
|
|
||||||
### 5. Design multiple interfaces
|
|
||||||
|
|
||||||
Spawn 3+ sub-agents in parallel using the Agent tool. Each must produce a **radically different** interface for the deepened module.
|
|
||||||
|
|
||||||
Prompt each sub-agent with a separate technical brief (file paths, coupling details, dependency category, what's being hidden). This brief is independent of the user-facing explanation in Step 4. Give each agent a different design constraint:
|
|
||||||
|
|
||||||
- Agent 1: "Minimize the interface — aim for 1-3 entry points max"
|
|
||||||
- Agent 2: "Maximize flexibility — support many use cases and extension"
|
|
||||||
- Agent 3: "Optimize for the most common caller — make the default case trivial"
|
|
||||||
- Agent 4 (if applicable): "Design around the ports & adapters pattern for cross-boundary dependencies"
|
|
||||||
|
|
||||||
Each sub-agent outputs:
|
|
||||||
|
|
||||||
1. Interface signature (types, methods, params)
|
|
||||||
2. Usage example showing how callers use it
|
|
||||||
3. What complexity it hides internally
|
|
||||||
4. Dependency strategy (how deps are handled — see [REFERENCE.md](REFERENCE.md))
|
|
||||||
5. Trade-offs
|
|
||||||
|
|
||||||
Present designs sequentially, then compare them in prose.
|
|
||||||
|
|
||||||
After comparing, give your own recommendation: which design you think is strongest and why. If elements from different designs would combine well, propose a hybrid. Be opinionated — the user wants a strong read, not just a menu.
|
|
||||||
|
|
||||||
### 6. User picks an interface (or accepts recommendation)
|
|
||||||
|
|
||||||
### 7. Create GitHub issue
|
|
||||||
|
|
||||||
Create a refactor RFC as a GitHub issue using `gh issue create`. Use the template in [REFERENCE.md](REFERENCE.md). Do NOT ask the user to review before creating — just create it and share the URL.
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: ubiquitous-language
|
name: ubiquitous-language
|
||||||
description: Extract a DDD-style ubiquitous language glossary from the current conversation, flagging ambiguities and proposing canonical terms. Saves to UBIQUITOUS_LANGUAGE.md. Use when user wants to define domain terms, build a glossary, harden terminology, create a ubiquitous language, or mentions "domain model" or "DDD".
|
description: Extract a DDD-style ubiquitous language glossary from the current conversation, flagging ambiguities and proposing canonical terms. Saves to UBIQUITOUS_LANGUAGE.md. Use when user wants to define domain terms, build a glossary, harden terminology, create a ubiquitous language, or mentions "domain model" or "DDD".
|
||||||
|
disable-model-invocation: true
|
||||||
---
|
---
|
||||||
|
|
||||||
# Ubiquitous Language
|
# Ubiquitous Language
|
||||||
|
|||||||
Reference in New Issue
Block a user