diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 836d543a7..6ac251f35 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -72,6 +72,12 @@ "source": "./plugins/diagnose", "category": "debugging", "tags": ["debug", "diagnose", "troubleshooting", "root-cause", "regression", "skill"] + }, + { + "name": "improve-architecture", + "source": "./plugins/improve-architecture", + "category": "architecture", + "tags": ["architecture", "refactoring", "deep-modules", "ousterhout", "module-design", "testability", "skill"] } ] } diff --git a/README.md b/README.md index 746692a1a..c4ac4e23e 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ Browse and manage with `/plugin`. To refresh after updates: `/plugin marketplace | [`guardrails`](plugins/guardrails) | Hook | Bundles four independently-toggleable PreToolUse safety guards: secret-pattern detection, hardcoded machine-path check, git hook-bypass blocking (`--no-verify`, `core.hooksPath`, `LEFTHOOK=0`), and advisory CLI-flag verification. | | [`bug-report`](plugins/bug-report) | Skill | Turns an informal defect description into a structured five-field bug report (title, repro, expected vs actual, severity, fix location). Read-only — it captures, never fixes or files on its own. | | [`diagnose`](plugins/diagnose) | Skill | Debugs observed failures via a disciplined six-phase loop — build a fast deterministic reproduction signal, reproduce, rank falsifiable hypotheses, instrument, fix with a regression test, then clean up and post-mortem. | +| [`improve-architecture`](plugins/improve-architecture) | Skill | Scans an existing codebase for module-level architecture friction — shallow modules, seam leaks, locality gaps — using Ousterhout's deep-module lens, presents candidates as a self-contained HTML report, and interviews the selected candidate before handing off for planning. | Install one: `/plugin install @melodic-software`. diff --git a/plugins/improve-architecture/.claude-plugin/plugin.json b/plugins/improve-architecture/.claude-plugin/plugin.json new file mode 100644 index 000000000..4ad902be4 --- /dev/null +++ b/plugins/improve-architecture/.claude-plugin/plugin.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", + "name": "improve-architecture", + "version": "0.1.0", + "description": "Scans an existing codebase for module-level architecture friction — shallow modules, seam leaks, and locality gaps — using Ousterhout's deep-module lens, presents candidates as a self-contained HTML report, and runs an interview loop on the selected candidate before handing off for planning.", + "author": { + "name": "Melodic Software", + "email": "info@melodicsoftware.com" + }, + "keywords": ["architecture", "refactoring", "deep-modules", "ousterhout", "module-design", "testability", "skill"] +} diff --git a/plugins/improve-architecture/README.md b/plugins/improve-architecture/README.md new file mode 100644 index 000000000..aead771f7 --- /dev/null +++ b/plugins/improve-architecture/README.md @@ -0,0 +1,65 @@ +# improve-architecture + +A Claude Code plugin that scans an existing codebase for **module-level +architecture friction** and proposes concrete improvements. It is proactive +discovery, distinct from reviewing a diff or planning new work: it hunts for +shallow modules, seam leaks, and locality gaps in code that already exists. + +The first (and default) lens implements John Ousterhout's **deep-module** +concept from *A Philosophy of Software Design* — a module is *shallow* when its +interface is nearly as complex as its implementation, and *deep* when a small +interface hides large behavior. Deepening shallow modules improves both +testability and AI/agent-navigability: a small interface lets a reader grasp a +module's purpose without traversing the whole import graph. + +## What it does + +1. **Explore for friction.** Walks the codebase (via a read-only exploration + subagent), reads the project's glossary and architecture decision records if + present, and applies the *deletion test* to anything suspected shallow — + would deleting it concentrate complexity, or merely move it? +2. **Present candidates.** Writes a self-contained HTML report (inline styles and + inline SVG only, no remote fetch) to the OS temp directory, one card per + candidate with a before/after diagram, a recommendation badge, and a + dependency-category badge. Alongside it, writes a durable machine-readable + candidate list that survives the session. +3. **Interview the selected candidate.** Once you pick one, walks the design tree + — constraints, dependencies, the shape of the deepened module, what sits + behind the seam, which tests survive — and records the agreed shape for a + planning step to consume. + +## Invoke + +```shell +/improve-architecture:improve-architecture # defaults to the deepening lens +/improve-architecture:improve-architecture deepening # explicit +``` + +Trigger phrases (Claude may also invoke it automatically): "improve +architecture", "find deepening opportunities", "shallow modules", "architecture +scan", "make this more testable", "module seams", "locality". + +## Persistence + +The durable candidate list is written to `${CLAUDE_PLUGIN_DATA}` by default. If +your project maintains its own per-task work-artifact convention (a tracked +working directory documented in your `CLAUDE.md` or rules), the skill honors that +location instead and reports the path either way. + +## Configuration + +This plugin has no `userConfig`. It adapts to your project through your +project's own context: its glossary (if any), its architecture decision records, +and its work-artifact convention. There is nothing to hand-edit in the plugin. + +## Install + +```shell +/plugin marketplace add melodic-software/claude-code-plugins +/plugin install improve-architecture@melodic-software +``` + +## License + +MIT (SPDX-License-Identifier: MIT). See the `LICENSE` file at the root of the +`melodic-software/claude-code-plugins` repository. diff --git a/plugins/improve-architecture/skills/improve-architecture/SKILL.md b/plugins/improve-architecture/skills/improve-architecture/SKILL.md new file mode 100644 index 000000000..9d775c355 --- /dev/null +++ b/plugins/improve-architecture/skills/improve-architecture/SKILL.md @@ -0,0 +1,62 @@ +--- +name: improve-architecture +description: "Scan an existing codebase for module-level friction and architecture improvement opportunities. Explores for shallow modules (interface nearly as complex as implementation), seam leaks, and locality gaps; presents candidates as a self-contained HTML report; runs an interview loop on the selected candidate; hands off an agreed candidate shape for planning. Use when: 'improve architecture', 'find deepening opportunities', 'shallow modules', 'architecture improvement', 'Ousterhout deepening', 'make code more testable', 'make code more AI-navigable', 'find refactoring opportunities', 'what should we improve', 'architecture scan', 'codebase friction', 'module seams', 'locality'. Skip when: applying mechanical code-level tidyings (this operates at module level), reviewing a diff before merge, enforcing architecture rules on a change, or root-cause debugging a specific failure." +argument-hint: "[action] (e.g., deepening)" +user-invocable: true +disable-model-invocation: true +--- + +## Pre-computed context + +Current branch: !`git branch --show-current 2>/dev/null || echo "unknown"` +Recent commits: !`git log --oneline -10 2>/dev/null || echo "no commits"` +Working tree status: !`git status --porcelain 2>/dev/null | head -10 || echo "clean"` + +## Variables + +Arguments: `$ARGUMENTS` + +## Purpose + +Improvement is distinct from review and planning. Review evaluates a DIFF against criteria (reactive). Planning designs NEW work (forward-looking). This skill scans EXISTING code for friction and proposes candidates for improvement (proactive). + +The scan-present-pick process generalizes across improvement **lenses**. Each lens (action) brings its own analysis method and vocabulary via an `actions/.md` playbook plus a `research//` reference set, loaded only when that lens runs. The first lens, `deepening`, implements Ousterhout's deep-module concept — finding shallow modules (interface nearly as complex as implementation) and proposing how to deepen them. The aim is **testability and AI/agent-navigability (AX)**: a deep module's small interface lets a reader — human or agent — grasp its purpose without traversing the whole import graph. + +This finds existing friction — it does not plan new work, apply mechanical code-level tidyings, enforce rules on a diff, or review changes before a PR. Those are separate concerns handled by planning, tidying, rule-enforcement, and review tools respectively (see "Composition"). + +## Actions + +| Argument | Action | What it does | +|----------|--------|-------------| +| *(empty)* | Defaults to `deepening` | Runs the deepening lens | +| `deepening` | **Deepening (Ousterhout)** | Shallow→deep module scan → HTML report → interview loop → hand off an agreed candidate for planning. Full process: `actions/deepening.md` | + +One lens per invocation — lenses don't chain implicitly. Read the action's playbook for its full process. + +### Adding a lens + +A new improvement lens (e.g. `coupling`, `testability`, dependency-direction review) is a pure ADD — never edit an existing lens's contract to add one (open for extension, closed for modification): + +- `actions/.md` — the lens playbook (phases, gates, output shape) +- `research//` — reference for that lens, loaded only when its action runs (per-action progressive disclosure) +- one row in the Actions table above + +## What this skill does NOT do + +- **Does not plan implementation** — produces candidates + agreed shape; a planning step plans the work +- **Does not enforce rules** — a rule-enforcement reviewer does that reactively on a diff +- **Does not apply mechanical tidyings** — code-level tidyings (rename, extract, inline) are a separate, smaller-grained concern +- **Does not review a diff** — pre-merge review tools do that +- **Does not write code** — discovery and design skill only +- **Does not brainstorm a rough problem** — this skill hunts architecture friction on its own lenses; open-ended "how could we approach X" divergence is a brainstorming concern + +## Composition + +Graceful degradation — where a named step below is not available in the consuming project, inline the equivalent work in this session instead of blocking on it. + +| When | Then | How | +|------|------|-----| +| A debugging pass finds an architectural root cause | Run this skill's deepening lens | Structured deepening review of the affected module | +| A candidate shape is agreed | Hand off to a planning skill if the project has one; else summarize the agreed shape for planning | Consumes the `agreed-shape` entry from the candidate artifact (see `actions/deepening.md`) | +| During the interview loop | Update the project's ubiquitous-language glossary if it maintains one | Inline term additions/sharpening as decisions crystallize | +| Post-improvement | Review the implemented changes with the project's review tool | Standard diff review | diff --git a/plugins/improve-architecture/skills/improve-architecture/actions/deepening.md b/plugins/improve-architecture/skills/improve-architecture/actions/deepening.md new file mode 100644 index 000000000..0e7249dbf --- /dev/null +++ b/plugins/improve-architecture/skills/improve-architecture/actions/deepening.md @@ -0,0 +1,68 @@ +# Deepening lens (Ousterhout) + +The `deepening` action implements Ousterhout's "deepening" concept — finding shallow modules (interface nearly as complex as implementation) and proposing how to deepen them (small interface, large behavior behind it). + +Three phases. Each has a hard gate before the next. + +## Phase 1 — Explore for friction + +Read the project's domain glossary if it maintains one — the nearest `UBIQUITOUS-LANGUAGE.md` (or equivalent), found by walking UP from the directory being examined toward the repo root and stopping at the first match (the same way `.editorconfig` / `.gitignore` resolve). Also read any architecture decision records in the area being examined. + +Use the Agent tool with `subagent_type=Explore` (or any read-only exploration subagent available) to walk the codebase. Explore organically — note where friction appears: + +- Where does understanding one concept require bouncing between many small modules? +- Where are modules **shallow** — interface nearly as complex as implementation? +- Where have pure functions been extracted for testability, but real bugs hide in how they're called (no **locality**)? +- Where do tightly-coupled modules leak across their **seams**? +- Where do bugs recur *at the seams between* several owned subsystems (e.g. frontend ↔ API ↔ CLI ↔ store) rather than inside any one — a signal to wrap them behind a single **deep** interface so one integration test exercises the whole flow instead of debugging each boundary? +- Which parts of the codebase are untested, or hard to test through their current **interface**? + +Apply the **deletion test** to anything suspected shallow: would deleting it concentrate complexity, or move it? "Concentrates" is the signal. Full vocabulary in [../research/deepening/vocabulary.md](../research/deepening/vocabulary.md). + +Classify each candidate's dependencies per [../research/deepening/dependencies.md](../research/deepening/dependencies.md) — the category determines testing strategy. + +## Phase 2 — Present candidates as HTML report + +Write a self-contained HTML file via a secure temp-file primitive so the path is unpredictable and permissions are restrictive. On Unix/Linux, create it with `mktemp` (e.g. `mktemp --tmpdir deepening-review-XXXXXX.html` or `mktemp -t deepening-review.XXXXXX.html`); on Windows, use a user-scoped temp under `%LOCALAPPDATA%\Temp` or equivalent. Open for user: `start ` on Windows, `open ` on macOS, `xdg-open ` on Linux. Report the absolute path. + +Report is **self-contained — inline ` + + +
+
...
+
...
+
...
+
+ + +``` + +## Header + +Repo name, date, compact legend: solid box = module, dashed line = seam, red arrow = leakage, thick dark box = deep module. No intro paragraph — straight into candidates. + +## Candidate card + +Each candidate is one `
`: + +- **Title** — short, names the deepening ("Collapse the Order intake pipeline") +- **Badge row** — recommendation strength (`Strong` = emerald, `Worth exploring` = amber, `Speculative` = slate) + dependency category tag (`in-process`, `local-substitutable`, `ports & adapters`, `mock`) +- **Files** — monospaced list, `font-mono text-sm` +- **Before / After diagram** — two columns, side by side. See patterns below +- **Problem** — one sentence +- **Solution** — one sentence +- **Wins** — bullets, ≤6 words each. Use vocabulary terms: "locality: bugs concentrate in one module", "leverage: one interface, N call sites", "interface shrinks; implementation absorbs the wrappers" +- **ADR callout** (if applicable) — amber-tinted box + +No paragraphs of explanation. If diagram needs a paragraph, redraw it. + +## Diagram patterns + +Pick pattern that fits. Mix them — variety is the point. No Mermaid or other remote runtime — use inline SVG or hand-built HTML/CSS only. + +### Flowchart (inline SVG or hand-built boxes) + +Use when point is "X calls Y calls Z, look at the mess." Style leakage edges with `stroke: var(--rust)` and deep modules with the `.deep` class. + +```html +
+ + + OrderHandler + + +
+``` + +### Hand-built boxes-and-arrows + +Modules as `
`s with borders. Arrows as inline SVG. Use when "after" diagram should feel like one thick-bordered deep module with greyed-out internals — Mermaid won't render that weight. + +### Cross-section (layered shallowness) + +Stack horizontal bands (`h-12 border-l-4`) showing layers a call passes through. Before: 6 thin layers. After: 1 thick band with consolidated responsibility. + +### Mass diagram (interface as wide as implementation) + +Two rectangles per module — interface surface area + implementation. Before: interface nearly as tall as implementation (shallow). After: interface short, implementation tall (deep). + +### Call-graph collapse + +Before: tree of function calls as nested boxes. After: same tree collapsed into one box, internal calls faded inside. + +## Style guidance + +- Lean editorial, not corporate-dashboard. Generous whitespace +- Color sparingly: one accent (emerald or indigo) + red for leakage + amber for warnings +- Diagrams ~320px tall so before/after fits side-by-side without scrolling +- `text-xs uppercase tracking-wider` for module labels inside diagrams +- Inline styles and inline SVG only — no CDN, no remote scripts + +## Top recommendation section + +One larger card. Candidate name, one sentence why, anchor link to its card. + +## Tone + +Use exactly: module, interface, implementation, depth, deep, shallow, seam, adapter, leverage, locality. + +Never substitute: component, service, unit (for module); API, signature (for interface); boundary (for seam); layer, wrapper (for module). + +No hedging, no throat-clearing, no "it's worth noting that..." + +Phrasings that fit the style: + +- "Order intake module is shallow — interface nearly matches the implementation." +- "Pricing leaks across the seam." +- "Deepen: one interface, one place to test." +- "Two adapters justify the seam: HTTP in prod, in-memory in tests." + +Wins bullets name the gain in glossary terms. Never "easier to maintain" or "cleaner code" — those terms are not in the vocabulary. Sequence diagrams work well for "before: 6 round-trips; after: 1." diff --git a/plugins/improve-architecture/skills/improve-architecture/research/deepening/interface-design.md b/plugins/improve-architecture/skills/improve-architecture/research/deepening/interface-design.md new file mode 100644 index 000000000..e2299b6fb --- /dev/null +++ b/plugins/improve-architecture/skills/improve-architecture/research/deepening/interface-design.md @@ -0,0 +1,42 @@ +# Interface Design — "Design It Twice" + +When the user wants to explore alternative interfaces for a chosen deepening candidate, use parallel subagents. Based on Ousterhout's "Design It Twice" — first idea is unlikely to be best. + +## Process + +### 1. Frame the problem space + +Before spawning subagents, write a user-facing explanation: + +- Constraints any new interface must satisfy +- Dependencies and their category (per [dependencies.md](dependencies.md)) +- Rough illustrative code sketch grounding the constraints — not a proposal + +Show to user, then immediately proceed to Step 2. User reads while subagents work. + +### 2. Spawn parallel subagents + +Spawn 3+ subagents via Agent tool. Each must produce a **radically different** interface for the deepened module. + +Prompt each with a separate technical brief (file paths, coupling details, dependency category, what sits behind the seam). Give each a different design constraint: + +- **Agent 1:** "Minimize the interface — 1-3 entry points max. Maximize leverage per entry point." +- **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 ports and adapters for cross-seam dependencies." + +Include both [vocabulary.md](vocabulary.md) terms and the project's own glossary terms (if it maintains one) in the brief so subagents name things consistently. + +Each subagent outputs: + +1. Interface (types, methods, params — plus invariants, ordering, error modes) +2. Usage example showing how callers use it +3. What the implementation hides behind the seam +4. Dependency strategy and adapters +5. Trade-offs — where leverage is high, where it's thin + +### 3. Present and compare + +Present designs sequentially so user absorbs each, then compare in prose. Contrast by **depth** (leverage at interface), **locality** (where change concentrates), and **seam placement**. + +Give your recommendation: which design is strongest and why. If elements from different designs combine well, propose a hybrid. Be opinionated — user wants a strong read, not a menu. diff --git a/plugins/improve-architecture/skills/improve-architecture/research/deepening/vocabulary.md b/plugins/improve-architecture/skills/improve-architecture/research/deepening/vocabulary.md new file mode 100644 index 000000000..a2948add0 --- /dev/null +++ b/plugins/improve-architecture/skills/improve-architecture/research/deepening/vocabulary.md @@ -0,0 +1,44 @@ +# Deepening Vocabulary + +Shared vocabulary for every suggestion the `deepening` lens makes. Use these terms exactly — consistent language is the point. + +Source: Ousterhout's "A Philosophy of Software Design", adapted for practical use. + +## Terms + +**Module** — anything with an interface and an implementation. Scale-agnostic: function, class, package, vertical slice. Avoid: unit, component, service. + +**Interface** — everything a caller must know to use the module correctly. Includes type signature, invariants, ordering constraints, error modes, required configuration, performance characteristics. Avoid: API, signature (too narrow — type-level surface only). + +**Implementation** — what's inside a module. Distinct from **Adapter**: a thing can be a small adapter with a large implementation (Postgres repo) or a large adapter with a small implementation (in-memory fake). Use "adapter" when the seam is the topic; "implementation" otherwise. + +**Depth** — leverage at the interface. A module is **deep** when large behavior sits behind a small interface. A module is **shallow** when the interface is nearly as complex as the implementation. + +**Seam** (Feathers) — a place where behavior can be altered without editing in that place. The location at which a module's interface lives. Choosing seam placement is its own design decision, distinct from what goes behind it. Avoid: boundary (overloaded with DDD's bounded context). + +**Adapter** — a concrete thing satisfying an interface at a seam. Describes role (what slot it fills), not substance (what's inside). + +**Leverage** — what callers get from depth. More capability per unit of interface they must learn. One implementation pays back across N call sites and M tests. + +**Locality** — what maintainers get from depth. Change, bugs, knowledge, and verification concentrate at one place rather than spreading across callers. + +## Principles + +- **Depth is a property of the interface, not the implementation.** A deep module can be internally composed of small, mockable, swappable parts — they just aren't part of the interface. A module can have **internal seams** (private, used by own tests) and **external seams** (at its interface). +- **The deletion test.** Imagine deleting the module. If complexity vanishes, it was a pass-through. If complexity reappears across N callers, it was earning its keep. +- **The interface is the test surface.** Callers and tests cross the same seam. If you want to test *past* the interface, the module is probably the wrong shape. +- **One adapter = hypothetical seam. Two adapters = real seam.** Don't introduce a seam unless something actually varies across it. + +## Relationships + +- A **Module** has exactly one **Interface** +- **Depth** is a property of a **Module**, measured against its **Interface** +- A **Seam** is where a **Module**'s **Interface** lives +- An **Adapter** sits at a **Seam** and satisfies the **Interface** +- **Depth** produces **Leverage** for callers and **Locality** for maintainers + +## Rejected framings + +- **Depth as ratio of implementation-lines to interface-lines** (Ousterhout literal) — rewards padding. We use depth-as-leverage +- **"Interface" as TypeScript `interface` keyword or class's public methods** — too narrow; interface includes every fact a caller must know +- **"Boundary"** — overloaded with DDD bounded context. Use **seam** or **interface**