---
name: improve-codebase-architecture
description: Find deepening opportunities in this repo, informed by docs/glossary.md and the ADRs in docs/decisions/. Use when the user wants to improve architecture, find refactoring opportunities, consolidate tightly-coupled modules, or make the codebase more testable and AI-navigable. Respects the conformance system, boundary rules, and the 21 ADRs that govern shape.
---

# Improve Codebase Architecture

Surface architectural friction and propose **deepening opportunities** — refactors that turn shallow modules into deep ones. The aim is testability and AI-navigability, scoped to what this template's existing rules permit.

## Glossary

Use these terms exactly in every suggestion. Consistent language is the point — don't drift into "component," "service" (we use that for DI ports specifically), or "boundary" (we use that for ESLint workspace-tag rules). Full definitions in [LANGUAGE.md](LANGUAGE.md).

- **Module** — **in this repo defaults to "feature"** (`packages/<name>/`). The abstract definition (anything with interface + implementation) still applies at narrower scales — a use case, controller, repository/service port, binder, or core package can also be a module — but say "feature" whenever that's the scope. Reach for "module" only when comparing depth/leverage across scales.
- **Interface** — everything a caller must know to use the module: types, invariants, error modes, ordering, schemas, DI shape. Not just the TypeScript type signature.
- **Implementation** — the code inside.
- **Depth** — leverage at the interface: a lot of behaviour behind a small interface. **Deep** = high leverage. **Shallow** = interface nearly as complex as the implementation.
- **Seam** — where an interface lives; a place behaviour can be altered without editing in place. In this repo seams take a concrete shape: `*.interface.ts` files, DI symbols, manifest declarations, and `<gen:*>` anchors.
- **Adapter** — a concrete thing satisfying an interface at a seam. In this repo: `<x>.repository.ts` (Payload real impl) vs `<x>.repository.mock.ts` vs `Recording*` test doubles.
- **Leverage** — what callers get from depth.
- **Locality** — what maintainers get from depth: change, bugs, knowledge concentrated in one place.

Key principles (see [LANGUAGE.md](LANGUAGE.md) for the full list):

- **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.**
- **One adapter = hypothetical seam. Two adapters = real seam.**

This skill is **informed by** the project's domain model and architecture decisions. Read [`docs/glossary.md`](../../../docs/glossary.md) for project vocabulary and the relevant ADR(s) in [`docs/decisions/`](../../../docs/decisions/) before proposing anything in their territory.

## Hard constraints (do not propose violations)

These are settled decisions — propose deepening WITHIN them, never against them:

- **Factory-function use cases & controllers** (ADR-012, ADR-013) — every use case is `(deps) => async (input) => output`; every controller is one verb-noun pair per file with a co-located `presenter`.
- **Schemas in the use-case file** (ADR-013) — `xInputSchema`/`xOutputSchema` colocate with the factory; don't propose moving them to a separate module.
- **Per-feature DI containers** (ADR-008) — don't propose a single global container.
- **Five boundary tags + the dependency-direction matrix** (ADR-006, ADR-010) — features may depend only on `core` + `tooling`; cross-feature reactions go through `IEventBus` (ADR-015).
- **Manifest-first ordering** (ADR-012, ADR-020) — new use cases land manifest → contracts → tests → impl; don't propose collapsing the steps.
- **Brand-based conformance** — `Instrumented` / `Captured` / `Audited` are attached at DI bind time via `withSpan` / `withCapture` / `withAudit`; don't propose moving the wrapping elsewhere.
- **Generator-first** — `pnpm turbo gen <kind>` is the entry point for new features/events/jobs/realtime channels. Don't propose hand-rolled scaffolding.
- **Conventional Commits** (CLAUDE.md Key Conventions) — any refactor lands as conventional-commit messages.
- **Hybrid versioning** (ADR-021) — refactors that move code between feature packages have version + CHANGELOG implications.

If a proposed deepening **would** violate an ADR, surface it explicitly with the ADR number and a "worth reopening because…" justification — but only if the friction is real enough. Most should be silently scoped out.

## Process

### 1. Explore

Read [`docs/glossary.md`](../../../docs/glossary.md) and any ADRs in the area you're touching first. Then walk the codebase noting friction. The primary unit of attention is the **feature** (`packages/<name>/`); narrower units (use cases, controllers, repositories) get attention when the friction lives at that scale.

- Where does understanding one concept require bouncing between many small files across `entities/`, `application/`, `infrastructure/`, `interface-adapters/` inside a single feature?
- Where is a feature **shallow** — its public surface (the `.` + `./ui` + `./api` exports) nearly as complex as its internal implementation? Or where inside a feature is a smaller unit shallow:
  - A `service.interface.ts` with one method that wraps a single repository call.
  - A `presenter` that's just `(x) => x`.
  - A controller body that's just `useCase(parsed.data)` with no transformation.
  - A repository wrapping another repository.
  - A use case wrapping another use case.
- Where have pure functions been extracted just for testability, but the real bugs hide in how they're called (no **locality**)?
- Where do tightly-coupled features leak across their seams? (e.g. a feature reaches into another feature's internals via deep import — though ESLint should catch this.)
- Which parts are untested, or hard to test through their current interface? `pnpm coverage:diff` and `pnpm fallow` surface candidates.

Useful exploration shortcuts in this repo:

- `pnpm fallow` — dead exports, dupes, complexity hotspots, circular deps
- `pnpm fallow:audit` — the AI-change audit; surfaces drift across recent edits
- `git log --oneline --follow -- <path>` — change frequency is a depth signal
- `cat packages/<feature>/src/feature.manifest.ts` — declared surface of a feature
- `pnpm turbo boundaries` — workspace dependency graph

Apply the **deletion test** to anything you suspect is shallow: would deleting it concentrate complexity, or just move it? A "yes, concentrates" is the signal you want.

### 2. Present candidates

Present a numbered list of deepening opportunities. For each candidate:

- **Files** — which files / features / smaller units are involved (give exact paths)
- **Problem** — why the current architecture is causing friction
- **Solution** — plain English description of what would change
- **Benefits** — explained in terms of **locality** and **leverage**, plus how tests would improve
- **ADR impact** — any ADR this touches. If the proposed change conflicts with a current ADR, mark it explicitly: _"contradicts ADR-NNN — worth reopening because…"_ (only when the friction warrants it).
- **Manifest impact** — if the change moves use cases / events / jobs / channels across features, the `feature.manifest.ts` of each feature involved will need an update; flag this so the user knows the conformance gates will require manifest edits before code edits (manifest-first ordering).

**Use [`docs/glossary.md`](../../../docs/glossary.md) vocabulary for the domain (use case, manifest, slice, feature, etc.) and [LANGUAGE.md](LANGUAGE.md) vocabulary for the architecture (module, seam, adapter, depth, leverage, locality).**

Do NOT propose interfaces yet. Ask the user: "Which of these would you like to explore?"

### 3. Grilling loop

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 sits behind the seam, what tests survive.

Side effects happen inline as decisions crystallize:

- **Naming a deepened module after a concept not in [`docs/glossary.md`](../../../docs/glossary.md)?** Add the term to the glossary right there — same discipline as the `grill-with-docs` skill. Pick the appropriate section (Packages / Architecture layers / Feature building blocks / Conformance / Cross-feature / Instrumentation / Workflow / Releasing).
- **Sharpening a fuzzy term during the conversation?** Update the glossary inline.
- **User rejects the candidate with a load-bearing reason?** Offer an ADR, framed as: _"Want me to record this as ADR-NNN so future architecture reviews don't re-suggest it?"_ Only offer when the reason would actually be needed by a future agent to avoid re-suggesting the same thing. The next ADR number is `001 + max(existing)` (currently `ADR-022`). Our ADR shape: `Context → Decision → Alternatives considered → Consequences → Related`.
- **Refactor will move code between feature packages?** Flag the release-please impact: both affected packages will bump versions on the next release PR.
- **Want to explore alternative interfaces for the deepened module?** See [INTERFACE-DESIGN.md](INTERFACE-DESIGN.md).

## Related skills

- `grill-with-docs` (`.claude/skills/grill-with-docs/`) — stress-tests plans against ADRs + glossary + manifests; share the same glossary-update discipline.
- `to-prd` — if the deepening is large enough to merit a multi-task epic, materialize it as a PRD.
