Project Architecture Analyzer - Capture the Architecture (Deliberate)
You are the architect seat for this run: you build the project's architecture picture by reasoning over cheap digests, and you record it as three artifacts - <docs-path>/architecture/ARCHITECTURE.md (the neutral structure map, deep-dives under <docs-path>/architecture/references/), <docs-path>/architecture/ASSESSMENT.md (the reasoned, tiered evaluation), and .claude/rules/baseline-project-architecture.md (the generated always-on awareness rule: a micro-summary plus the trigger to read the map, so every session and subagent knows the docs exist without hand-maintained pointers). The reading is delegated - architecture-analyzer characterizes one module per dispatch and returns a compact digest - but the judgment is NOT: you aggregate, reconcile, and evaluate in-session, then write the docs yourself. Architecture judgment is the expensive kind, so the frontmatter names opus/xhigh - but a skill-level model pin is not reliably honored for main-session turns (measured: invocations ran on the session model despite it, while agent-level pins in the same session held exactly), so set the session itself to Opus with /model before a capture and drop it back after. Check at run start, not after: when this session is not on Opus, say so in the first thing the user sees (inside the FIRST-capture mode ask where one fires, otherwise the opening line) so the switch can happen before the judgment is spent; the REPORT step's Model: line closes the loop.
This is capture only: it documents and evaluates, it fixes nothing and produces no implementation steps. Working the weaknesses is project-architecture-quality-loop, which runs this capture as its ANALYZE step and routes fixes by tier. The per-change fit verdict (extend / refactor first / isolate) is the domain solution-designers', reading the map this skill writes.
Read references/doc-shapes.md (the two docs' required shape) and references/hazards.md (the stack-keyed hazard catalog you hunt) before AGGREGATE - they are this skill's contract, not suggestions.
Execution modes
The dispatch decision keys off FIRST RUN vs UPDATE, not preference - pick once, hold for the run:
- FIRST capture (no existing docs, or no
Captured:stamp) - when dispatch is available, ask ONE question before gathering, via AskUserQuestion - characterize the modules via architecture-analyzer seats (recommend it: the cheap seats absorb the reads), or in-session? - unless a calling flow (a quality loop) already picked the run's mode, which is inherited, never re-asked. An interrupted or declined ask is answered by RE-ASKING, never by inference - a re-invoked slash command after a 'skip' answer is a new run that asks again, not consent for the declined one (measured: one run took exactly that sequence as 'run it'). DELEGATED: dispatch architecture-analyzer per module as below; reasoning and writing stay here. INLINE (chosen, or no dispatch - Cursor): characterize the modules yourself, serena-first and bounded, and continue at AGGREGATE identically. - UPDATE (docs + stamp exist) - INLINE in this session:
git diff --name-only <stamp-sha>..HEADscopes the drift, and you verify + reconcile ONLY the touched modules (serena-first, located reads) - the rest of the doc stands on the prior capture. A+dirtystamp breaks that trust: the prior capture included uncommitted work the diff cannot see (it may have moved to another branch or been discarded), so the doc's claims are verified beyond the diff, not assumed. Escalate back to per-module dispatch for the drifted modules when the diff spans many modules, when the stamp's sha is unreachable (a rebase) or+dirty- a first-run-sized job wearing an update's name - or when the USER explicitly asks for agents: their ask always wins over the inline default. One+dirtyescape hatch: when the SAME uncommitted files still sit in the tree unchanged since the capture (provable - their mtimes at or before the prior capture's write, or a matchinggit stash createsha), the dirty set is accounted for and the update may stay inline, saying so in the report (measured: one run proved exactly this and stayed inline correctly - the escalation is for a dirty set the diff CANNOT account for, not for the suffix itself).
The run
1. ORIENT
Read <docs-path>/architecture/ARCHITECTURE.md and <docs-path>/architecture/ASSESSMENT.md if they exist - a claim to verify, not ground truth - and note which <docs-path>/architecture/references/ files are yours versus user-authored.
Branch check first - the docs are machine-local, so they do NOT switch with git branches: read the docs' Captured: <branch>@<short-sha> stamp and compare against the current HEAD (git rev-parse --abbrev-ref HEAD / --short HEAD).
- Same branch (or no stamp yet): a normal refresh - reconcile the main docs in place.
- DIFFERENT branch: the main docs are the base picture and stay untouched - this capture's output becomes
<docs-path>/architecture/BRANCH-DELTA.mdinstead (below). Fully overwriting the main docs from a foreign branch happens only on the user's explicit ask (a genuinely diverged branch), and then the new stamp records it.
Read the project's decision log when present (ADRs - <docs-path>/decisions/ under the docs root, or wherever the project keeps them): each accepted ADR is declared intent to reconcile against, and a tradeoff an ADR records deliberately lands in ASSESSMENT.md as an accepted tradeoff, not a weakness. Read the config/manifest files for framework and package facts. Build the module inventory (a directory listing; get_symbols_overview is per FILE, for the files that matter, never a directory) - the list of areas to characterize. Scope it if the user did (one bounded context or module subtree on a large codebase); whole project otherwise - and on an UPDATE, the inventory is the drifted modules the stamp diff names, not the whole project.
2. GATHER - architecture-analyzer per module, in parallel
Dispatch architecture-analyzer per module/topic on the inventory - in a single message where the areas are independent. Each returns a compact digest (purpose, public surface, inbound/outbound dependencies, patterns, smells), every claim tied to a located symbol. You are the expensive seat: never read the codebase wholesale yourself - serena (get_symbols_overview, find_symbol / find_referencing_symbols) and Read are for light orientation and spot-verification of one edge only.
3. AGGREGATE + REASON - in-session
Load the vocabulary you reason with, matched from YOUR skill list by what each skill says it covers rather than by a remembered name: the router for the area's stack, the skill covering architecture styles and layering vocabulary, and the convention skill for any file type you judge in depth. In .NET also the skill covering GoF pattern fit, and the one covering architecture fitness tests when judging whether a boundary is enforced or merely conventional - that last one exists only where the project runs such tests, so with nothing matching, read the test project directly and never guess at a skill name. Assemble the digests into the structure - layers, dependency directions, patterns, boundaries - and reconcile against the existing docs: declared vs enforced part company exactly where coupling escapes the static graph (DI registrations, reflection and service location, string-keyed lookups, events and messaging, DTO/entity types reused across a boundary). Treat a name as a hypothesis, an edge as proven only from a usage - and an ABSENCE as proven only from the source of truth (the wire schema, the decompiled model, the vendor contract): a grep over usages proves the code does not USE the thing, never that the thing does not exist (measured: a wire frame 'had no field' by usage grep - the decompiled model carried it, unused). Hunt the references/hazards.md catalog for the stack in play. Reason out the strengths and weaknesses - and run the findings gate in references/doc-shapes.md on EVERY candidate before it is written down: all four questions answered explicitly (what breaks, who notices, actually new, already decided), the external-preference rule applied (a house reference preferring a different approach than one this project runs successfully is not a weakness), a re-measurement of an already-recorded limit folded into its existing entry - never opened as new, never re-tiered upward for gaining a number - and every gate-passing survivor recorded: the reasoning pass never stops early because the list already looks long enough (measured 2026-08-21: one round filed 5 tiered items including the first structural in thirteen rounds against code that had not regressed - three of the five were artifacts of a new lens, not defects). Coverage never enters the weakness list: missing or weak tests, low coverage, absent test infrastructure are project-test-coverage-analyzer's capture and its COVERAGE.md, not architecture findings - a digest smell of that kind is dropped here, and what stays is only the structural testability blocker (a missing seam, static coupling, a dependency that cannot be substituted), per references/doc-shapes.md.
4. RE-GATHER on the gaps
Where a part is unclear or uncovered, dispatch architecture-analyzer again on exactly that topic. Where two digests contradict each other on a CHECKABLE fact, settle it with the cheapest deterministic probe in-session first - a grep -c, a serena lookup, the stack's own command - and re-dispatch only for a judgment conflict no command can settle (measured: two digest conflicts settled by one command each, where a re-dispatch would have cost ~50k tokens and returned another opinion). Hard cap: 3 gather rounds. Still unsettled after 3: write what is established, mark what is uncertain and what would settle it - never guess to fill a section.
5. WRITE - the two docs, per references/doc-shapes.md
Every doc this capture writes opens with a stamp line - Captured: <branch>@<short-sha>, <YYYY-MM-DD>, with +dirty appended to the sha when the working tree holds uncommitted changes (git status --porcelain non-empty): a dirty capture describes code no commit contains, and the suffix is what stops a later update from trusting it. Prefer capturing on a clean tree; a dirty one is allowed but marked. The stamp is followed in ARCHITECTURE.md by the fixed pointer sentence On another branch, check architecture/BRANCH-DELTA.md for that branch's recorded changes. - so any later reader (and the next capture) knows exactly which code the map describes and where a branch's own picture lives; a reader on a different branch treats the main content as the base picture, approximate for their branch.
On a foreign branch (the ORIENT branch check), write <docs-path>/architecture/BRANCH-DELTA.md INSTEAD of touching the main docs: its own stamp plus ONLY what this branch changes against the base map - a new/removed module, a moved boundary, a new dependency edge, a pattern introduced - table-formatted, replaced whole on each delta capture, never a second full map. A capture running on the main docs' own branch again refreshes the main docs and then judges the delta against the code it just mapped: delta changes now PRESENT in this branch (the branch merged - the fresh map already covers them) -> DELETE the delta; changes still absent (the branch lives unmerged) -> the delta STAYS, it is another branch's record. A delta capture leaves the generated awareness rule untouched - the rule keeps describing the base map.
Load docs-as-code before writing - its Mermaid ground rules govern the core-map flowchart and any deep-dive sequence/ER diagram (one exception: a reconcile whose diagrams carry forward verified unchanged may skip the load and say so in the report). The same precondition covers step 3's vocabulary: a WRITE starting with the domain router / architecture-vocabulary skills unloaded this session loads them FIRST - the report's Vocabulary: receipt records the load, it never substitutes for it. doc-shapes.md fixes WHAT each doc contains - including the format-for-agents discipline (tables and grouped rows over prose, no ASCII art) that keeps the docs cheap for every seat that reads them at orientation; docs-as-code fixes HOW the diagrams are written. Then write <docs-path>/architecture/ARCHITECTURE.md (lean core map, deep-dives spilled to <docs-path>/architecture/references/<topic>.md files linked from a short index) and <docs-path>/architecture/ASSESSMENT.md (every gate-passing finding in its bucket - Must fix tiered with remediation, Worth knowing with its promotion condition, Deliberate tradeoffs - plus every genuine strength and the summary; counts are outputs, never targets or caps). Strength-check every ASSESSMENT remediation against the strengths you just recorded: a fix that would erode a listed strength names the tension in its entry and is shaped to preserve it, or the tradeoff is declared and the weakness tiered structural. Both docs are clean, scannable Markdown per the markdown-style skill. Create <docs-path>/architecture/ and <docs-path>/architecture/references/ only when absent. The references/ folder is shared with human-authored docs: rewrite only the topic files you author, link the user-authored ones from the index, and never delete a file. Re-run: reconcile in place - correct what drifted, add what is new, drop what is gone. Compose each doc's reconcile in-session and land it in one write (or one batched edit pass) per doc - never a per-claim edit stream (measured: 31 serial edits to one assessment in a single session, each later re-read costing more as the doc grew). Write ONLY under <docs-path>/architecture/ - never source, never another doc. After the write, wc -l the core map against doc-shapes' lean target: over it, spill the overweight sections to references/ files NOW - the spill is the sanctioned second pass on the doc (a restructure, not the per-claim edit stream the one-write rule forbids) - this run owns the doc, and no other flow will (measured: a map grew 460 -> 738 lines across rounds because the capture declined to fix it and the loop's intake is ASSESSMENT-only); an overrun this run genuinely cannot resolve is filed as a small-tier ASSESSMENT entry so the loop picks it up.
6. RULE - write .claude/rules/baseline-project-architecture.md
The awareness tier, generated from the fresh capture - a valid PATHLESS rule (frontmatter with a description: and NO paths:, so it is always-on). Fully derived, so regenerate wholesale: DELETE the existing rule file first, then Write it fresh - an in-place Write against an unread file costs a blocked-call round-trip (measured), and there is nothing in the old copy to preserve. Keep it to ~5 lines of body; always-on tokens are paid every session and every subagent, so the fat stays in the docs. The <docs-path> placeholders in the block below are baked to the literal resolved root at generation time (the rule cannot itself follow the remap convention it exists to reinforce):
---
description: Project architecture awareness - generated by /project-architecture-analyzer; edit via a re-run, not by hand.
---
# Architecture
<one line: project type + architecture style, from the capture - e.g. 'ASP.NET Core modular monolith, vertical slices'>
<one line: the modules/layers, named - e.g. 'Modules: Orders, Catalog, Identity; shared kernel in BuildingBlocks'>
The full map is `<docs-path>/architecture/ARCHITECTURE.md` (deep-dives under `<docs-path>/architecture/references/`,
the reasoned assessment in `<docs-path>/architecture/ASSESSMENT.md`) - read the map before planning or
designing any structural change, instead of re-deriving the project.
Replace <docs-path> with the LITERAL resolved root this capture wrote under - the generated rule is a deterministic
pointer and must name the real path, never a placeholder. Create .claude/rules/ when absent. Regenerate-only: the summary lines come from THIS run's capture (never stale-copied), the trigger paragraph is fixed - never hand-edit the copy, never let it grow module detail, package lists, or assessment content. Generated, never fetched: the installer's manifest must not list this rule (a fetch would overwrite the capture), so it survives a stack update.
7. REPORT
Confirm the files written (created vs refreshed, sections touched; the awareness rule created/refreshed; a branch delta vs a main-doc refresh, per the stamp). Then lean: gather rounds used and whether the picture settled within the cap; a Vocabulary: line naming the skills step 3 actually loaded (the line is what makes the load happen - measured: a run with no such report field loaded zero of them); a References: line naming which of references/hazards.md / references/doc-shapes.md this run actually Read - same receipt logic (measured: one capture read neither of its own contract references and nothing surfaced the skip); the structure headline; the assessment's shape - the per-bucket counts, the Must-fix tier tally, the top few highest-leverage fixes project-architecture-quality-loop should take first; a Findings gate: line - candidates considered, passed, routed to Worth knowing, folded into an existing entry, and rejected (naming the question each rejected one failed) - the named field is what makes the gate actually run, and the receipt that nothing was silently trimmed; the line is UNCONDITIONAL - an UPDATE whose reconcile surfaced no candidates writes Findings gate: 0 candidates - reconcile only, no hazard hunt rather than dropping the field; anything unverified and what would settle it. When the user asks for a follow-up prompt or the run hands work to a later session, shape it as a RESUME BLOCK - the exact invocation to paste, the docs to read first, one line on what the next run does - the same paste-ready format the loop skills end on (measured: ~12.9k tokens across two correction rounds reinventing exactly that shape because the convention lived only in a skill this run never loads). End with a Model: line whenever the session was raised for this run - reset to <session model> or still raised: <why> - the named field is what makes the reset happen (measured: 35 turns of an unrelated command rode Opus after a capture that closed with no such line). The docs follow the docs root - machine-local by default, re-captured after a fresh clone, never assumed to be in git. The map is what the domain solution-designers read to judge where a change fits, what project-solve-cross-task reads to pick a cross-domain run, and what the cross-domain seam interface is designed against. No re-paste of the doc bodies - point to the files. One line per named field above, a table where a field lists several items: the close is an answer like any other and the answer-length hook blocks a wall of prose (tables are exempt) - the receipts must survive that cap, not be re-answered away.
Don't game it
Record the structure that exists, not the one the names imply - in both docs, every claim traces to code you or an architecture-analyzer located, and anything unverified is marked unverified, never rounded up to certainty. Re-measure every COUNTABLE claim a digest returns - a line count, an N-of-anything, a file:line ref - with one command before it enters a doc: counts are the measured failure mode (3 of 8 digests in one capture carried a wrong count and NONE looked suspicious, so a too-clean look is not the trigger; the doc's own history was 22 corrections, mostly counts and line refs). Prose characterizations may stand on the digest; numbers may not. In the assessment, an honest weakness beats a flattering omission, and a deliberate tradeoff is labelled a tradeoff, not a defect. The finding count is an output, never a target, in either direction: inflating an observation into a weakness to look thorough, and dropping a real one to keep a list tidy, are the same class of dishonesty as padding to a quota - zero gate-passing weaknesses is a complete result said plainly, and twenty-five real ones are all recorded. None of this touches the re-measure rule above, which stays exactly as it is: the 2026-08-21 round that inflated three lens artifacts into tiered findings ALSO caught four wrong numbers by re-measurement - one where the doc and the code's own XML doc agreed with each other and were both wrong. Keep the re-measuring; kill the inflating.