audit-arch
Macro-scale architectural audit. Finds structural conflicts — patterns whose replacement would simplify the whole system — judged against the language, the runtime environment, and the project's OWN s
SKILL.md.claude/skills/audit-arch/SKILL.md
GitHubArchitecture Audit
/audit-arch # whole repo, fitness judged from evidence
/audit-arch src/engine # scoped target
/audit-arch scaling up # adopt the scale-invested archetype
/audit-arch soft-realtime game # adopt the frame-budget archetype
Contract
One sentence you must be able to complete for every finding you report:
"Replacing {current structure} with {simpler structure} would let us delete {concrete things}, because in {language/runtime} the idiomatic shape for this problem is {pattern}."
If you cannot fill every slot with evidence (file:line), the finding does not exist.
Prohibitions — read before auditing, these are where audits go wrong
- Never audit against an unsignaled quality dimension — any dimension. Every quality axis a criterion could invoke (scalability, portability, extensibility, multi-tenancy, i18n, security hardening beyond the threat model, performance beyond observed need, configurability, cross-platform, offline mode, …) is in scope ONLY if the project signaled it — in docs, the skill argument, config, dependencies, or the user's words. This list is illustrative, not exhaustive: the rule is that the dimension itself needs a signal before any finding on it exists. Before writing a finding, name the dimension it judges against and cite its signal; no signal → the finding is deleted, not softened. Absence of every signal means one bar remains: fitness and simplicity for what the code demonstrably does today.
- Never report micro findings. Naming, single-function complexity, missing null checks, one file's style — out of scope. If the fix touches fewer than ~3 modules or deletes nothing, it belongs to a code review, not this audit.
- Never propose an abstraction that adds a layer without deleting two. Every recommendation must net-reduce: fewer moving parts, fewer places a change lands, fewer facts a new contributor must learn.
- Never infer architecture from names.
EventBus.csis not evidence of pub/sub;Cacheis not evidence of caching. Read the call sites. A finding cites how data/control actually flows, with file:line. - Never bundle. One structural conflict per finding, findings independent, ranked by leverage × centrality (Step 1c), not by severity theater.
- Never let "bad" outrank "unimportant-but-bad." Importance comes from the project's ethos, not from how wrong the code is. A textbook-poor pattern in a subsystem the project barely cares about (e.g. clumsy multiplayer plumbing in a primarily single-player game) is at most a footnote; the audit's headline findings live where the project's point lives.
Step 1 — Establish fitness scope
Before reading code, fix the yardstick. Two inputs:
a. Declared intent (overrides everything): the skill argument, plus README/docs//CLAUDE.md/ADRs. If the arg names an ambition ("scaling up", "plugin ecosystem", "mobile port"), adopt the matching archetype:
| Argument signals… | Audit as… | Which adds criteria like… |
|---|---|---|
| scale / load / users | Scale-invested architect | contention points, O(n) fan-outs, sync-over-async seams |
| realtime / game / frame | Frame-budget architect | per-frame allocation, poll-vs-event, update-order coupling |
| team growth / onboarding | Legibility architect | module count a newcomer must hold, hidden coupling |
| plugins / extensibility | Seam architect | closed enums vs registries, hardcoded dispatch |
| (no argument) | Resident senior engineer | idiomatic fitness for what the code demonstrably does today |
b. Demonstrated intent: what the code is actually shaped to do — a single-process desktop game, a CLI, a service. The demonstrated shape sets the default bar.
c. Ethos — the importance modifier. Beyond what the project does, articulate what it cares about: the philosophy, the point, the thing that would make its author say "this is why the project exists" (e.g. a single-player systemic sim cares about simulation fidelity and feel; a data pipeline cares about correctness and replayability; a prototyping tool cares about iteration speed). Every subsystem then has a centrality: core (embodies the ethos), supporting (serves it), peripheral (incidental to it). Centrality scales importance for the rest of the audit — a genuinely poor architecture in a peripheral subsystem is still poor, but it is not important, and this audit reports what's important. A moderate conflict in the core loop outranks a severe one in a corner the ethos doesn't care about; if the top-5 cut must drop something, peripheral findings go first, noted in one line under "Explicitly not flagged."
State the scope in one line before proceeding:
Auditing as: {archetype}. Fitness bar: {what this system is for}. Ethos: {what it cares about most} — core subsystems: {list}.
Step 2 — Map the macro structure (dispatch, don't wander)
First tool call is an Explore/investigate dispatch, not direct reads. The brief:
- Identify the top ~10 modules by fan-in/fan-out and the direction of every major dependency between them.
- Trace the three dominant flows end to end (e.g. input→state→render; request→domain→persistence; tick→simulation→UI).
- For each flow, record the coordination mechanism at every hop: direct call, poll, shared mutable state, event, message, callback, singleton reach-around.
- Note where the same fact is stored/derived in more than one place.
The coordination-mechanism list is the raw material: most macro conflicts are a mechanism mismatched to its hop.
Step 3 — Detect structural conflicts
For each hop/cluster, ask the conflict questions. These are shape-level, and each carries its own don't-flag guard:
| Conflict | Flag when | Do NOT flag when |
|---|---|---|
| Poll where push belongs | N consumers re-derive/re-check state every tick that changes rarely, and producers know when it changes | the poll IS the domain (a game update loop), or N=1 and it's cheap |
| Push where poll belongs | event cascades re-implement "current state" via replay/flags | events genuinely model one-shot facts |
| Shared mutable state as API | modules communicate by writing fields other modules read on a timer | it's a deliberate blackboard/ECS with a single owner |
| Layer inversion | low-level module knows high-level types (engine imports UI, domain imports transport) | the "layer" is aspirational, not declared or needed |
| Duplicated source of truth | the same fact is computed/stored in ≥2 places that can disagree | one is a documented cache with an owner and invalidation |
| God orchestrator | one module's edit-frequency and fan-out dwarf all others; every feature lands there | it's a thin composition root that only wires |
| Framework fought, not used | hand-rolled machinery duplicating what the runtime/framework provides idiomatically (e.g. manual change-detection beside an ECS; hand-rolled async beside Tasks) | the hand-rolled version exists for a measured, documented reason |
| Config/flag switchboard | booleans threaded through many layers select behavior that a seam (interface + two adapters) would isolate | two flags, one consumer — leave it |
| Ambition mismatch (archetype-gated) | declared ambition (Step 1a) is structurally blocked — e.g. "scaling up" + in-memory singletons holding session state | no such ambition was declared |
Verify each suspect with the deletion test (from /improve-codebase-architecture): imagine the current mechanism gone, replaced by the simpler shape. List what concretely gets deleted. Nothing deletable → not a finding.
Step 4 — Report
Rank by leverage × centrality: how much of the system simplifies, scaled by how close the finding sits to the project's ethos (Step 1c). Severity alone never promotes a peripheral finding over a core one. Maximum 5 findings — a macro audit that reports 20 things has reported nothing. Format each exactly like this:
### {n}. {Current shape} → {Proposed shape}
**Where:** {files/modules, with the 2–3 most load-bearing file:line cites}
**Current flow:** {2–3 sentences: how data/control moves today, mechanism named}
**Conflict:** {which conflict from Step 3, and why the guard column doesn't save it}
**Simpler architecture:** {the target shape in plain language — no interfaces, no code; name the idiomatic pattern for this language/runtime}
**Deletion test:** {the concrete list of code/concepts/sync-points that disappear}
**Cost honesty:** {what the migration touches; what new risk it introduces}
Close with:
**Fitness bar used:** {restated from Step 1}
**Explicitly not flagged:** {1–3 things a generic audit would flag that this fitness bar excuses — and why. This line is mandatory; it proves the bar was applied.}
Findings only. Do not implement anything. If the user picks a finding to pursue, hand off to /improve-codebase-architecture for the grilling loop and interface design — this skill decides what conflicts exist, that one decides how the replacement gets shaped.