enhance-arch-boundaries · git:20260923.68c095e · 2026-09-23 · sha256 cbf4cfac39e44c3b
enhance-arch-boundaries git:20260923.68c095eA
Immutable. This exact content never changes and is served at /api/v1/blob/cbf4cfac39e44c3b.
--- name: enhance-arch-boundaries description: > Enforce architecture boundaries in CI with dependency-cruiser or eslint-boundaries: layer direction, feature isolation, forbidden imports. Use when "enforce module boundaries" or "stop spaghetti imports". Advice only → audit-backend-architecture. license: MIT effort: high --- # enhance-arch-boundaries — Architecture as a fitness function **Degree of freedom: MIXED.** Recovering the model `[HIGH freedom]`; do-not-invent, shrink-only baseline, and deliberate-violation probes `[LOW freedom — run exactly]`. Codify the repo's intended structure as rules that block merge. **Import spaghetti is how agents degrade architecture: each import looks locally reasonable, no single diff is wrong, and after forty sessions the layering is gone.** A rule in AGENTS.md is advisory. A dependency-cruiser rule in the aggregator gate is physics. ## This skill vs neighbors | Skill | Owns | |---|---| | **enhance-arch-boundaries** (this) | Mechanical boundary rules + shrink-only baseline | | `audit-backend-architecture` | Advises which pattern to adopt — does not enforce | | `housekeep-gates` | Wires this check into the aggregator | | `docs-adr` | Records *why* the model was chosen | | `enhance-agent-guardrails` | Broader guard install; this owns the import graph | ## How to reason 1. **Observe** — folder layout, existing conventions, any architecture audit 2. **Interpret** — what layering does the repo already mean to have? 3. **Classify** — recoverable model / no intended structure (stop) / inventing (forbidden) 4. **Severity** — client → server-only / service-role is the worst edge ## Worked example > **Observe:** `app/dashboard/page.tsx` imports `@/lib/supabase/admin` > (service-role). No boundary rule. > **Interpret:** the intended split exists in folder names but is not physics. > **Classify:** forbidden edge — client → server-only. > **Fix:** dependency-cruiser / eslint-boundaries rule; grandfather other > violations into a shrink-only baseline; probe a new forbidden import fails CI. --- ## Phase 0 — Recover the intended architecture (do not invent one) [LOW freedom — stop if none] The rules must encode the architecture the repo *means* to have. - Read folder structure and existing conventions (`features/*`, `app/`, `lib/`, `server/`, `components/`, monorepo packages). - Read `audit-backend-architecture` output if it exists — that is the drift to stop. - Confirm with the user in one pass: layers and allowed direction (e.g. `ui → application → domain → infrastructure`, never reverse), feature units that must stay isolated, special zones (server-only, secrets, generated). State the model in ten lines before writing any rule. If the repo genuinely has no intended structure, **stop and say so** — enforcing a structure nobody chose creates fights, not quality. Propose a minimal layering first. --- ## Phase 1 — Install the rule set [HIGH freedom; mapped to the confirmed model] Tool by stack: **dependency-cruiser** (framework-agnostic JS/TS), **eslint-plugin-boundaries** (when living inside ESLint is preferable), Nx module-boundary tags, import-linter (Python). Core rules, each mapped to the confirmed model: - **Layer direction** — lower layers cannot import upward; skipping layers flagged where the model says so. - **Feature isolation** — features import each other only via a public surface (`features/x/index.ts`), never deep paths. Shared code goes through `shared/`, not sideways. - **No circular dependencies** — anywhere. Cycles are where "change one thing" stops being possible. - **Forbidden edges** — client importing server-only / service-role modules (cross-ref `plan-rls-audit` / `plan-secrets-audit`), UI importing the DB layer directly, production importing test utilities, anything importing generated internals. - **No orphans** — modules nothing reaches (feeds `workflow-housekeep`). - **Dependency hygiene** — no `devDependencies` from shipped code; deprecated internals marked un-importable so migration ratchets. --- ## Phase 2 — Grandfather, then ratchet [LOW freedom — shrink-only baseline] Do not weaken the rules to fit existing violations, and do not block the repo on fixing them all. - Capture existing violations into the tool's **known-violations baseline** so CI is green today. - The baseline is a **shrink-only ratchet**: new violations fail; existing ones are tracked debt; the baseline file can only get smaller without review — same reset policy as `housekeep-gates`. - File the grandfathered list as a burndown, worst edges first (cycles and client→server-secrets before cosmetic layer skips). --- ## Phase 3 — Wire and make agent-legible [HIGH freedom] - Add the check to CI wired into the aggregator gate (`housekeep-gates`) and mirror it in local hooks (same command, two callers). - Generate the dependency **graph visualization** into docs; regenerate it on the scheduled run so it cannot go stale. - Write the ten-line model + "how to fix a boundary violation" into agent rules *referencing the mechanical check* — the rule text explains, the gate enforces. - Record the architecture decision as an ADR (`docs-adr`) so a future session knows the layering was chosen, by whom, and why the rejected alternative lost. --- ## Definition of Done - [ ] Intended model recovered from the repo + confirmed with the user (or the no-structure finding raised) - [ ] Rules installed: layer direction, feature isolation via public surfaces, no cycles, forbidden edges (incl. server-only → client), no orphans, dep hygiene - [ ] Existing violations grandfathered into a shrink-only baseline; burndown filed worst-first - [ ] Check in the aggregator gate + local hooks with one shared command - [ ] Graph visualization generated into docs with a regeneration schedule - [ ] Agent rules updated to state the model and point at the gate; decision recorded via `docs-adr` - [ ] Deliberate-violation probe: a test import that breaks each rule class fails CI ## Self-critique before claiming done [LOW freedom — do not skip] 1. **Model confirmed** — or you stopped; you did not invent a layering 2. **Baseline shrink-only** — new violations fail; no silent growth 3. **Probe per rule class** — a test import failed CI 4. **Right owner** — pattern advice → `audit-backend-architecture`; why → `docs-adr` 5. **Aggregator wired** — `housekeep-gates` ## Output format 1. **Architecture model** — ten-line statement of layers, directions, features, special zones 2. **Rule set** — rule | what it forbids | why (mapped to the model) 3. **Grandfathered baseline** — count by rule | worst-first burndown 4. **Wiring** — gate job, hook parity, graph location, agent-rule text added 5. **Probe evidence** — violation per rule class | CI result Applied directly (enhance-family). Pause for approval only on the model confirmation and before committing the baseline.