
Doc Architecture Review
- 18 installs
- 28 repo stars
- Updated June 29, 2026
- nickcrew/claude-ctx-plugin
Helps with ai & agent building tasks.
About
doc-architecture-review is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- doc-architecture-review
- AI & Agent Building
- AI-coding skill
Doc Architecture Review by the numbers
- 18 all-time installs (skills.sh)
- Ranked #10,674 of 16,556 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/nickcrew/claude-ctx-plugin --skill doc-architecture-reviewAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 18 |
|---|---|
| repo stars | ★ 28 |
| Last updated | June 29, 2026 |
| Repository | nickcrew/claude-ctx-plugin ↗ |
What it does
Helps with ai & agent building tasks.
Files
Documentation Architecture Review
Evaluate whether documentation is organized so that readers can find what they need, understand where they are, and navigate efficiently. The output is an architecture assessment with specific restructuring recommendations — not new content.
When to Use
- When restructuring or reorganizing documentation
- When adding a new section or doc type to an existing set
- When users report "I know it's documented somewhere but can't find it"
- When the doc set has grown organically and needs rationalization
- After
doc-completeness-auditidentifies gaps — before filling them, ensure the structure
can accommodate new content
- Periodic review of navigation and discoverability
Quick Reference
| Resource | Purpose | Load when |
|---|---|---|
references/personas.md | Six concrete reader personas with eval signals | Always (Phase 0) |
scripts/link_graph.py | Mechanical link-graph analyzer (orphans, reciprocity, broken links, hubs) | Always (Phase 1) |
references/ia-heuristics.md | Doc-type-aware IA evaluation heuristics | Always (Phase 2) |
---
Workflow Overview
Phase 0: Personas → Establish the doc set's primary 1-3 personas
Phase 1: Map → Build the current doc structure map (incl. link graph)
Phase 2: Evaluate → Score against IA heuristics, parameterized by personas + doc type
Phase 3: Model → Compare structure to user mental models per persona
Phase 4: Report → Produce the architecture review with per-persona findings---
Phase 0: Establish Personas
A "good" architecture is good for someone specific. Without personas, the heuristics apply a default standard that systematically misjudges docs serving non-default audiences (a flat reference doc scored as "poorly hierarchical" because it doesn't follow Quick Start → advanced).
Step 0a — Identify the doc set's audiences
Read the doc set's entry pages (README, index.md, landing pages) and the highest-traffic top-level docs. Identify which 1–3 personas from references/personas.md are the primary readers. Common patterns:
| Doc set shape | Likely personas |
|---|---|
| Library / SDK with public API | API Looker-Up + Onboarding User |
| End-user product | Onboarding User + Operator |
| Internal infrastructure | Operator + Incident Responder + Architect Debugger |
| OSS project | Onboarding User + Contributor |
| Operations-heavy system | Operator + Incident Responder |
Step 0b — Draft persona profiles
For each identified persona, copy the profile from references/personas.md verbatim. Don't paraphrase — the explicit profile is what calibrates downstream sub-agents. If a persona almost fits but a dimension differs, define a custom persona using the same five-field structure.
Step 0c — Note conflicts
If the doc set serves more than one persona with conflicting needs (e.g., Onboarding User wants narrative, API Looker-Up wants terseness), note this explicitly. The synthesis report will surface where current structure favors one persona at the cost of another.
Output: A persona block (1–3 personas + any conflict notes) that feeds every downstream sub-agent prompt.
---
Phase 1: Map the Current Structure
Build a complete picture of the documentation architecture.
Step 1a: Physical Structure
Generate the file tree of all documentation:
find docs/ site/ -name '*.md' -o -name '*.html' | sortRecord:
- Directory hierarchy and nesting depth
- File count per directory
- Naming conventions (kebab-case, snake_case, mixed)
Step 1b: Navigation Structure
Identify every way a reader can navigate:
| Navigation type | Where to find it |
|---|---|
| Sidebar / table of contents | _config.yml nav, front matter nav_order/parent, SUMMARY.md |
| Landing pages | index.md files — read each one for link lists |
| In-page cross-references | [text](link) and {% link %} references between pages |
| Breadcrumbs | Theme configuration or layout templates |
| Search | Search configuration, indexed content |
| Previous/Next links | Auto-generated or manual nav_order sequencing |
Step 1c: Entry Points
Identify how readers arrive:
- Direct — typing a URL or bookmarking
- Search — site search or external search engine
- Navigation — sidebar, breadcrumb, landing page links
- Cross-reference — link from another doc page
- External — README, GitHub, blog post, error message linking to docs
Map which pages are reachable from each entry point. Pages unreachable from common entry points are effectively invisible.
Step 1d: Link Graph (mechanical)
Run the bundled link graph analyzer to extract deterministic facts about inter-doc linking:
python3 skills/doc-architecture-review/scripts/link_graph.py --scope all --json > graph.json
# Or human-readable:
python3 skills/doc-architecture-review/scripts/link_graph.py --scope allThe script produces:
- Orphans — pages with no inbound links (excluding entry points like
index.md and README.md). Direct input to Heuristic 1 (Findability).
- Dead-ends — pages with no outbound links. Content silos.
- Reciprocity ratio — fraction of edges that have a back-link. Direct
input to Heuristic 4 (Cross-Linking Quality).
- Hubs — pages with high in-degree. Natural reference targets.
- Broken links — internal links that don't resolve. Direct input to
Heuristic 4.
These are mechanical facts, not judgments. The judgment-heavy parts of Heuristics 1 and 4 (are links contextual? do navigation labels use user language?) are evaluated by sonnet sub-agents in Phase 2.
Output: A structure map with physical hierarchy, navigation paths, entry points, and the link graph JSON.
---
Phase 2: Evaluate Against IA Heuristics
Assess the structure against seven heuristics. Load references/ia-heuristics.md for detailed scoring criteria.
Mechanical vs judgment split
For a doc set of any meaningful size, the orchestrator can't read every page to score every heuristic — that strains the context window and produces patchy evaluation. Phase 2 splits work:
- Mechanical part — driven by the Phase 1d link graph JSON. Orphan
counts, reciprocity ratio, broken-link counts, hub identification: these are facts, not judgments. The orchestrator reads the JSON and assigns scores deterministically.
- Judgment part — dispatched to
general-purpose+sonnetsub-agents
organized by heuristic. Each agent receives a focused slice of the doc set and returns specific findings with citations.
Sonnet sub-agent dispatch
Three judgment-heavy heuristics warrant dedicated agents. Each agent's prompt inlines the persona block from Phase 0 and the relevant doc-type criteria from references/ia-heuristics.md. The agent scores per-persona, not against a generic default.
Agent 1 — Findability narrative review (Heuristic 1):
subagent_type: "general-purpose"
model: "sonnet"
description: "Findability narrative review"Prompt template:
Read landing pages, navigation configs (_config.yml, front-matter
nav_order/parent), and the orphans list from the link graph JSON.
Personas (from Phase 0):
<INLINE PERSONA BLOCK — full profile per persona, not summary>
Doc-type criteria for Heuristic 1:
<INLINE Heuristic 1 section from references/ia-heuristics.md>
For each persona, score Findability 1-5 and identify specific failures:
- Are navigation labels in this persona's language?
- Are entry points appropriate for how this persona arrives?
- Are orphans concentrated in a doc type that fails this persona's task?
Output per-persona scores plus findings. When personas conflict (e.g.,
nav labels in one's language fail another), surface the conflict
explicitly rather than averaging.Agent 2 — Cross-linking quality review (Heuristic 4):
subagent_type: "general-purpose"
model: "sonnet"
description: "Cross-link quality review"Prompt template:
Read 5-10 representative pages across doc types. Read the link graph
JSON's reciprocity statistics.
Personas (from Phase 0):
<INLINE PERSONA BLOCK>
Doc-type criteria for Heuristic 4:
<INLINE Heuristic 4 section from references/ia-heuristics.md — note
the per-doc-type linking patterns table>
For each persona, score Cross-Linking 1-5. Distinguish:
- Are links contextual (explain why to follow)? Low priority for
Looker-Up reference scanning, high priority for Onboarding User
exploration.
- Does linking density match the doc type's pattern?
- Are there mutual links between related concepts (high reciprocity)
where appropriate?Agent 3 — Pattern consistency review (Heuristic 5, per-doc-type dispatch):
For each doc type present (reference, tutorial, guide, explanation, ADR, runbook, README), dispatch one sonnet agent with all docs of that type:
subagent_type: "general-purpose"
model: "sonnet"
description: "Consistency review for <doc-type>"Prompt template:
Examine all <DOC_TYPE> docs in the set: <LIST_OF_PATHS>.
Persona affected (from Phase 0):
<INLINE PERSONA BLOCK for this doc type's primary persona>
Expected template for <DOC_TYPE> per references/ia-heuristics.md
Heuristic 5:
<INLINE template signals row>
Identify the implicit template — common section headings, structural
conventions — and flag pages that deviate. For each deviation:
- Is it intentional (handles a special case the template doesn't
cover)? Note the reason.
- Is it accidental (older page; different author; conventions hadn't
settled)? Flag for harmonization.
Score 1-5 for within-type consistency.Heuristic scoring
Heuristic 1: Findability
Can readers locate information without knowing where it lives?
| Score | Criteria |
|---|---|
| 5 | Multiple discovery paths to every page. Search works. Navigation reflects user goals |
| 3 | Most content findable via navigation or search. Some pages only reachable by direct link |
| 1 | Content buried. No search. Navigation reflects implementation, not user needs |
Check:
- Orphaned pages (no inbound links, not in navigation)
- Dead ends (pages with no outbound links to related content)
- Search coverage (are all pages indexed? do headings use searchable terms?)
- Navigation labels (do they use user language or developer jargon?)
Heuristic 2: Hierarchy Coherence
Does the nesting make sense? Can a reader predict where to find something?
| Score | Criteria |
|---|---|
| 5 | Clean, predictable hierarchy. Each level represents a meaningful grouping. Max 3 levels deep |
| 3 | Generally logical but some surprises. Occasional misplaced content. 4 levels in places |
| 1 | Arbitrary nesting. Related content scattered. Deep hierarchies (5+). Categories overlap |
Check:
- Depth — flag anything nested >3 levels
- Breadth — flag directories with >10 immediate children (consider subcategories)
- Sibling coherence — are items at the same level truly peers?
- Naming — do directory names describe contents from the reader's perspective?
Heuristic 3: Progressive Disclosure
Does the doc set layer information from simple to complex?
| Score | Criteria |
|---|---|
| 5 | Clear learning path. Quick start → guides → reference → advanced. Each layer self-sufficient |
| 3 | Some layering exists but not explicit. Reader may hit advanced content before basics |
| 1 | All content at same depth. No distinction between introductory and advanced material |
Check:
- Quick start exists and is prominently linked
- Getting started path is linear and completable in <15 minutes
- Advanced topics are separated from basics, not interleaved
- Each doc states its prerequisites
- Cross-references point readers to deeper material, not shallower
Heuristic 4: Cross-Linking Quality
Do links between pages create useful connections or noise?
| Score | Criteria |
|---|---|
| 5 | Links are contextual, bidirectional where appropriate, and create meaningful paths |
| 3 | Links exist but some are one-directional, orphaned, or link to the wrong section |
| 1 | Few cross-links. Pages are isolated. No "See also" or "Related" patterns |
Check:
- Link density — pages with zero outbound links, pages with >20
- Reciprocity — if A links to B, does B link back (where appropriate)?
- Context — links explain why the reader would follow them, not just "click here"
- Anchor precision — links go to the right section, not just the right page
- Broken links — links that resolve to 404 or wrong content
Heuristic 5: Consistency of Patterns
Do similar pages follow similar structures?
| Score | Criteria |
|---|---|
| 5 | Clear templates per doc type. All reference pages look alike. All tutorials follow the same flow |
| 3 | Some patterns visible but not universal. Newer docs follow conventions, older ones don't |
| 1 | Every page is a snowflake. No discernible pattern across similar content types |
Check:
- Do all reference pages have the same sections?
- Do all tutorials follow the same progression?
- Do all guides have prerequisites and next steps?
- Are metadata conventions (front matter, titles, descriptions) consistent?
Heuristic 6: Separation of Concerns
Are different doc types (reference, tutorial, guide, explanation) kept distinct?
| Score | Criteria |
|---|---|
| 5 | Clear separation. Reference is reference. Tutorials are tutorials. No hybrid pages |
| 3 | Mostly separated but some pages mix types (reference data inside a tutorial) |
| 1 | No separation. Single pages try to be reference, tutorial, and explanation simultaneously |
Check:
- Pages that mix "how to" with "what it is" with "API details"
- Tutorials that double as reference (readers can't scan for a specific flag)
- Reference pages that include narrative explanations better suited to guides
- Use the Diataxis framework as a lens: tutorials, how-to guides, reference, explanation
Heuristic 7: Maintenance Burden
Is the structure sustainable as docs grow?
| Score | Criteria |
|---|---|
| 5 | Adding a new doc page requires no restructuring. Clear home for every doc type |
| 3 | Most new content has a natural home. Occasional need to reorganize |
| 1 | Every new page requires debate about where it goes. Structure is at capacity |
Check:
- Is there a clear directory/category for new feature docs?
- Are naming conventions documented and followed?
- Would doubling the doc set break the navigation?
- Are there catch-all directories growing without bounds?
---
Phase 3: Mental Model Comparison
Compare the documentation structure to how users actually think about the product.
User Mental Models
Identify the primary mental models users bring:
| Model type | Structure | Example |
|---|---|---|
| Task-based | "I want to do X" | Organized by workflow: install → configure → deploy |
| Feature-based | "I want to learn about X" | Organized by component: agents, skills, rules, hooks |
| Role-based | "I'm a [role]" | Organized by audience: user guide, admin guide, developer guide |
| Chronological | "What do I do first?" | Organized by sequence: getting started → daily use → advanced |
Most doc sets serve multiple models. The question is: which model does the navigation reflect, and does it match the primary user need?
Mismatch Indicators
- Users search for task-based terms but docs are organized by feature
- Getting started guide assumes feature knowledge the reader doesn't have yet
- Navigation uses internal terminology that users don't recognize
- Users land on the right page but can't find the right section
---
Phase 4: Produce the Architecture Review
Report Format
# Documentation Architecture Review
**Review date:** YYYY-MM-DD
**Scope:** [doc set reviewed]
**Total pages:** N
**Max depth:** N levels
**Orphaned pages:** N
**Personas evaluated:** [comma-separated list from Phase 0]
---
## Personas
[Inline the Phase 0 persona block — full profile per persona, plus any conflict notes]
---
## Summary
[2-3 sentences: overall architecture assessment, per-persona where relevant]
Heuristic scores per persona:
| Heuristic | Persona A | Persona B | Persona C | Notes |
|-----------|-----------|-----------|-----------|-------|
| Findability | N/5 | N/5 | N/5 | [one line, surface persona conflicts] |
| Hierarchy Coherence | N/5 | N/5 | N/5 | [one line] |
| Progressive Disclosure | N/5 or N/A | N/5 or N/A | N/5 or N/A | [N/A is valid for reference/ADR — see rubric] |
| Cross-Linking Quality | N/5 | N/5 | N/5 | [one line] |
| Consistency of Patterns | N/5 | N/5 | N/5 | [one line] |
| Separation of Concerns | N/5 | N/5 | N/5 | [one line] |
| Maintenance Burden | N/5 | N/5 | N/5 | [one line] |
| **Per-persona total** | **N/35** | **N/35** | **N/35** | |
Architecture grade per persona: [A / B / C / D / F]
When grades differ across personas, that's a finding, not a defect to
average away. Flag the structural bias toward whichever persona scores
highest.
---
## Structure Map
[File tree with annotations: orphan markers, depth warnings, misplacement flags]
---
## Critical Findings
### [Finding title]
**Heuristic:** [which]
**Impact:** [who is affected and how]
**Evidence:** [specific examples — pages, paths, search queries]
**Recommendation:** [specific restructuring action]
---
## Navigation Path Analysis
### Path: New User Onboarding
**Entry point:** [where they start]
**Goal:** [what they need to accomplish]
**Actual path:** [pages they traverse]
**Friction points:** [where they get lost or stuck]
**Ideal path:** [what it should be]
### Path: [Another key user journey]
...
---
## Orphaned Pages
| Page | Why It's Orphaned | Recommendation |
|------|-------------------|----------------|
| [path] | [no inbound links / not in nav] | [add to nav / link from X / archive] |
---
## Mental Model Alignment
**Primary user model:** [task / feature / role / chronological]
**Current structure model:** [which model the nav reflects]
**Alignment:** [match / partial / mismatch]
**Recommendation:** [restructure, add alternative navigation, or accept the gap]
---
## Restructuring Recommendations
Ordered by impact:
1. [Highest impact structural change]
2. [Second highest]
3. ...
---
## Strengths
[What's working well in the current architecture]---
Integration with Other Doc Skills
doc-maintenance → Structural health (links, orphans, folders)
doc-claim-validator → Semantic accuracy (do claims match code?)
doc-completeness-audit → Topic coverage (is everything documented?)
doc-quality-review → Prose quality (is it well-written?)
doc-architecture-review → Information architecture (is it findable?)Run this skill after doc-completeness-audit — you need to know what's missing before evaluating whether the structure can accommodate it. Run before filling gaps, so new content lands in the right place.
---
Anti-Patterns
- Do not reorganize during the review — produce findings, not a new file tree
- Do not treat your preferred structure as the "correct" one — evaluate against user needs
- Do not evaluate archived docs (
docs/archive/) — they are historical - Do not confuse "I know where things are" with "a new user would know" — test with fresh eyes
- Do not recommend restructuring for its own sake — the cost of moving docs (broken links,
muscle memory, bookmarks) must be justified by the improvement
- Do not ignore the cost of change — a mediocre-but-stable structure may be better than
a perfect structure that requires moving 50 pages
---
Bundled Resources
References
references/ia-heuristics.md— Detailed scoring criteria and examples for each heuristic
Information Architecture Heuristics — Doc-Type-Aware Criteria
Detailed scoring criteria for each IA heuristic, parameterized by doc type. A "good" architecture for a reference doc looks different from a "good" architecture for a tutorial — applying a single rubric across both produces systematic misjudgment.
Pair this file with references/personas.md. Each heuristic specifies which personas it matters most for; the doc-type criteria say what 5/5 looks like for that combination.
Doc types
These types appear throughout the heuristics below. Each has a characteristic shape that informs scoring.
| Type | Characteristic shape | Default primary persona |
|---|---|---|
| Reference (API, CLI, config) | Flat, scannable, optimized for lookup | API Looker-Up |
| Tutorial | Linear, sequential, builds mental model | Onboarding User |
| Guide / How-to | Task-focused, prereq → task → next steps | Onboarding User or Operator |
| Explanation / Conceptual | Topic-grouped, dependency-ordered | Architect Debugger |
| ADR / Decision Record | Self-contained, chronological set | Architect Debugger |
| Runbook | Scenario-keyed, urgency-ordered | Incident Responder |
| README / Landing | Entry point, route to other types | Onboarding User + Casual evaluator |
A doc set typically contains multiple types. Evaluate each type against its own criteria; the synthesis identifies cross-type problems (misplaced docs, type-mixing).
---
Heuristic 1: Findability
Core question: Can the target persona locate this content without already knowing where it lives?
Per-doc-type criteria for 5/5
| Doc type | "Findable" looks like |
|---|---|
| Reference | Stable URL per symbol; deep-linkable; alphabetical/categorical lookup; appears in IDE/tooling links |
| Tutorial | Visible from the front door; sequence position clear ("step 2 of 5") |
| Guide | Discoverable by task name; matches search queries readers would actually type |
| Explanation | Surfaceable when the concept is encountered elsewhere (linked from reference and tutorial) |
| ADR | Listed in an ADR index; filterable by status (proposed / accepted / superseded) |
| Runbook | Alert text matches runbook heading; runbook URL appears in the alert payload itself |
| README | Visible from project root, package registry page, CI badge, every entry surface |
Diagnostic checks (universal)
- Orphan analysis — pages with zero inbound links. Excludes README/index entry points. (
scripts/link_graph.pyproduces this.) - Search effectiveness — do headings match queries the relevant persona would type? Test specifically: an Incident Responder searching the alert text, an Onboarding User searching "how to install"
- Multiple discovery paths — high-value pages should have ≥2 paths (nav + cross-link, or nav + search hit)
- Navigation labeling — labels reflect persona language, not internal jargon
Persona-specific failure modes
- Onboarding User: front door doesn't surface quick start; terms are jargon
- Looker-Up: deep links don't work; reference content embedded in tutorial prose
- Incident Responder: runbook URL not in alert; alert text doesn't match heading
- Operator: config docs scattered; missing config items
Scoring
| Score | Mechanical (link graph) | Qualitative |
|---|---|---|
| 5 | 0% orphans (excluding entry pages) | Persona language; multiple discovery paths to high-value pages |
| 4 | <5% orphans | Mostly user-oriented; minor jargon |
| 3 | 5–15% orphans | Mixed user/system language |
| 2 | 15–30% orphans | System-oriented labels |
| 1 | >30% orphans | Developer jargon throughout |
---
Heuristic 2: Hierarchy Coherence
Core question: Can the target persona predict where to find something?
Per-doc-type criteria for 5/5
| Doc type | "Coherent hierarchy" looks like |
|---|---|
| Reference | Flat or shallow (≤2 levels). Categorical groupings (by symbol kind, by module). Predictable lookup. |
| Tutorial | Linear with phases (1 level). Order matters. Phase boundaries visible. |
| Guide | 2 levels max — task category → task. Predictable: "how to X" for any X has an obvious home. |
| Explanation | Concept dependency graph respected. Foundational concepts before derived. |
| ADR | Flat list, sometimes filtered by status. Numbered for citation. |
| Runbook | 2 levels — scenario family → specific runbook. Family matches alert type. |
| README | Mostly flat — landing → 5–10 top-level destinations. |
Why depth differs by doc type
A reference doc going 3 levels deep means the Looker-Up has to know the category before they can find the symbol. A tutorial needs phase hierarchy because order matters. A runbook should be 2 levels because scenarios cluster naturally. Forcing reference into tutorial-style depth, or flattening a tutorial into reference-style, both fail.
Diagnostic checks
- Depth test — flag deviations from the type's expected depth
- Sibling coherence — items at the same level should be the same kind
- Predictability test — given a topic, can the persona guess the directory? Hesitation = ambiguity
- Category overlap — same topic in two directories signals unclear hierarchy
Persona-specific failure modes
- Looker-Up: reference nested deeper than necessary; category guesswork required
- Onboarding User: tutorial flattened; phase boundaries invisible
- Incident Responder: runbook scenarios too granular (50 specific scenarios) or too broad (3 catch-all docs)
Scoring
Apply the type-appropriate depth from the table above:
| Score | Depth deviation | Sibling coherence | Predictability |
|---|---|---|---|
| 5 | At expected depth | All siblings are peers | Always predictable |
| 4 | One step deviation | Minor exceptions | Usually predictable |
| 3 | Some areas deviate | Some mixed siblings | Sometimes surprising |
| 2 | Frequently deviates | Frequent mixing | Often surprising |
| 1 | Wrong shape entirely | No coherence | Unpredictable |
---
Heuristic 3: Progressive Disclosure
Core question: Does the doc set layer information appropriately for its readers? This heuristic applies very differently by doc type.
Per-doc-type criteria
| Doc type | Progressive disclosure expectation |
|---|---|
| Reference | Anti-applies. Forcing progressive disclosure into reference is the failure mode. Score N/A or score against "is the flat structure consistent and complete?" |
| Tutorial | Required. Each step builds on prior. Concepts introduced before use. No forward references. |
| Guide | Light. Prerequisites stated, task itself focused, "next steps" optional. |
| Explanation | Required. Foundational concepts before derived ones. Reading order matters. |
| ADR | Anti-applies. Each ADR is a self-contained unit. Score N/A. |
| Runbook | Inverted. Most urgent / most common scenario first, edge cases later. The "most basic" content is the least useful under incident pressure. |
| README | Required at the doc-set level. README is where the journey starts; it should layer toward Quick Start prominently. |
Common misjudgment to avoid
A reference doc with no Quick Start section is correctly structured, not deficient. Scoring it 2/5 because "advanced topics are interleaved with basics" misreads what reference structure is for. Score N/A or focus on reference-appropriate criteria (completeness, scannability) instead.
Diagnostic checks (apply only when doc type uses progressive disclosure)
- Quick Start prominence — for tutorial/guide/README sets, can the
reader find quick start in <10 seconds from the front door?
- Linear path completability — can a fresh reader complete the
getting-started path in <15 minutes?
- Prerequisites stated — each tutorial/guide names what the reader
must already know
- Forward references — flag tutorials that reference concepts
before introducing them
Persona-specific failure modes
- Onboarding User: doc set lacks visible Quick Start; advanced before basics
- Looker-Up: reference forced into "intro / basics / advanced" structure that slows lookup
- Incident Responder: runbook starts with "understanding the system" before the procedure
Scoring
| Score | For types where applies | For types where N/A |
|---|---|---|
| 5 | Clear layered path; each step builds | Score N/A — evaluate completeness/scannability instead |
| 3 | Layering exists but inconsistent | — |
| 1 | All content at same depth; no quick start | — |
---
Heuristic 4: Cross-Linking Quality
Core question: Do links between pages create useful connections for the relevant personas?
Per-doc-type criteria for 5/5
| Doc type | Cross-linking pattern |
|---|---|
| Reference | Links to related symbols, types, methods. Low narrative density. Mutual links between related symbols. |
| Tutorial | Forward to next step, back to prerequisites. Sparse external links (don't break the flow). |
| Guide | Links to relevant references, related guides, optional deep-dives. |
| Explanation | Links to other concepts (dependency-aware), examples in tutorials, the code that implements the concept. |
| ADR | Links to superseded/superseding ADRs, related decisions, the code/system the ADR affects. |
| Runbook | Links to related runbooks (scenario neighbors), monitoring dashboards, incident channels. No deep design rationale links — wrong context. |
| README | Links to all major doc destinations + external project page. |
Mechanical inputs (from link_graph.py)
- Reciprocity ratio (mutual link pairs / total directed edges)
- Link density per doc (avg outbound links per page)
- Hub identification (in-degree distribution)
Qualitative checks (sonnet sub-agent)
- Contextual links — do links explain why the reader would follow them, or are they "click here" / dumped lists?
- Anchor precision — do links go to the right section, not just the right page?
- Relevance per persona — does the linked-to content serve the linking persona's task?
Persona-specific failure modes
- Looker-Up: reference pages with no cross-links to related symbols (forces back-and-forth between pages)
- Onboarding User: tutorial links jump to advanced reference too eagerly
- Architect Debugger: ADRs that don't link to the code they affect or the ADRs they supersede
Scoring
| Score | Reciprocity | Contextual links | Anchor precision |
|---|---|---|---|
| 5 | >0.6 | All links contextual | Anchored to section |
| 4 | 0.4–0.6 | Mostly contextual | Mostly anchored |
| 3 | 0.2–0.4 | Mix of contextual and dumped | Page-level |
| 2 | 0.1–0.2 | Mostly "see also" lists | Page-level only |
| 1 | <0.1 | Few cross-links at all | — |
---
Heuristic 5: Consistency of Patterns
Core question: Do similar pages follow similar structures?
Per-doc-type criteria for 5/5
For each doc type, all instances of that type should follow a consistent template. The templates differ by type but consistency within type is universal.
| Doc type | Template signals to check |
|---|---|
| Reference | Same heading structure (Signature, Parameters, Returns, Examples, Edge cases). Same parameter table format. |
| Tutorial | Same step structure (Goal, Prereqs, Steps, Verify, Next). Same pacing. |
| Guide | Same opening (When to use, Prereqs), same closing (Next steps, Related). |
| Explanation | Same structure (Context, Concept, Examples, Related). |
| ADR | Same template (Context, Decision, Consequences, Status). Numbered. |
| Runbook | Same urgent structure (Symptoms, Recovery, Verification, Rollback, Postmortem reminder). |
| README | Standard sections (What it is, Why use it, Install, Quick start, Docs links, Contributing). |
Diagnostic checks
- Template adherence per type — for each doc type, identify the implicit template and flag deviations
- Frontmatter consistency — same fields, same conventions
- Heading hierarchy consistency —
##vs###use, capitalization - Code block conventions — language tags, indentation
When deviation is acceptable
A doc that intentionally deviates from the template for a documented reason is not a finding. Score the deviation only if the agent can identify no reason for the difference.
Scoring
| Score | Within-type consistency |
|---|---|
| 5 | Clear template per type. All instances follow it. Deviations are documented. |
| 4 | Templates visible. Most instances follow. Newer pages adhere more than older. |
| 3 | Some patterns visible per type, but not universal. |
| 2 | Frequent inconsistency within type. |
| 1 | Every page is a snowflake. No discernible per-type pattern. |
---
Heuristic 6: Separation of Concerns
Core question: Are different doc types kept distinct, or do individual pages mix types?
What "separation" means per type
| Type | Should NOT contain |
|---|---|
| Reference | Long narrative tutorials. Decision rationale. (Cite the ADR / link the tutorial.) |
| Tutorial | Exhaustive parameter listings. (Link the reference.) Design rationale. |
| Guide | Reference data dumps. ADR content. |
| Explanation | Step-by-step procedures (link the tutorial). Exhaustive reference. |
| ADR | How-to content. (Decisions describe what was chosen, not how to use it.) |
| Runbook | Design rationale. Background reading. (Cite the architecture doc.) |
| README | Deep technical content. (Link the docs.) Tutorial content beyond a Quick Start tease. |
Diataxis as a lens
The Diataxis framework (tutorial / how-to guide / reference / explanation) is useful here. Single pages that try to be three of these at once — the README that's also tutorial that's also reference — are the dominant failure mode. Persona-mismatched mixing is the second.
Persona-specific failure modes
- Looker-Up reading reference: narrative explanations slow lookup
- Onboarding User reading tutorial: parameter exhaustiveness drowns the journey
- Incident Responder reading runbook: background context wastes seconds during incidents
- Architect Debugger reading ADR: how-to instructions instead of decision rationale
Diagnostic checks
- Page audit — for each page, identify its declared type and check for content of other types
- Hybrid detection — pages that combine 3+ types are almost always doing too much
- Persona impact — score the cost of the mixing per persona
Scoring
| Score | Separation |
|---|---|
| 5 | Clear separation. Each page does one thing. Type-mixing rare and intentional. |
| 4 | Mostly separated. Occasional pages mix two types. |
| 3 | Notable mixing. Reference docs include narrative; tutorials include reference data. |
| 2 | Pervasive mixing. Most pages mix 2+ types. |
| 1 | No separation visible. Everything is everything. |
---
Heuristic 7: Maintenance Burden
Core question: Is the structure sustainable as the doc set grows?
Per-doc-type criteria for 5/5
| Doc type | Sustainable looks like |
|---|---|
| Reference | Auto-generates from code OR has clear update triggers tied to code changes |
| Tutorial | Stable backbone with versioned variants (or clear deprecation path for old tutorials) |
| Guide | Task-focused (ages well) rather than implementation-focused (rots fast) |
| Explanation | Concept-stable; updated when design actually changes, not on every code change |
| ADR | Append-only — never edit, supersede with new ADR |
| Runbook | Tested in incident drills; obvious owner; updated after each related incident |
| README | Minimal surface to maintain — link out rather than duplicate |
Diagnostic checks
- Adding a new doc — does any new feature have an obvious home in the existing structure?
- Naming conventions — are they documented and followed?
- Catch-all directories — are any directories growing unbounded (e.g.,
docs/misc/)? - Doubling test — would a 2x doc-set break the navigation or hierarchy?
Persona-specific impact
- Maintenance burden is felt mostly by Contributor, but its symptoms surface to all personas as stale docs
- A doc set that's hard to maintain produces stale content that fails every persona
Scoring
| Score | Maintainability |
|---|---|
| 5 | New docs have clear homes. Conventions documented and followed. Doubling-tested. |
| 4 | Most new content has a natural home. Occasional reorg needed. |
| 3 | Several gray-area placements. Some catch-all directories growing. |
| 2 | Frequent placement debate. Catch-all directories expanding. |
| 1 | Structure at capacity. Each new doc requires restructuring. |
---
Aggregating per-persona scores
When evaluating a doc set against multiple personas, score each heuristic per-persona, not as a single average. The synthesis surfaces conflicts:
Heuristic 2 (Hierarchy Coherence)
For Looker-Up: 5/5 (flat reference structure, predictable lookup)
For Onboarding User: 2/5 (no learning path; expected to navigate flat
structure without guidance)
Synthesis: structure biased toward Looker-Up. If Onboarding User is
a primary persona, recommend layering a Quick Start above the
flat reference.Per-persona scoring is what makes the audit useful when audiences conflict. A single average score hides the bias and produces recommendations that help one persona at the cost of another.
Personas Library
Concrete reader profiles for evaluating documentation. Use these — don't invent fuzzier ones — when a sub-agent needs to score a doc against specific reader needs.
A "good" hierarchy or progressive disclosure looks different for each persona below. A reference doc that works for the API Looker-Up may fail the Onboarding User entirely; that's not a defect, it's a persona mismatch. The skill flags persona mismatches as findings, not as universal errors.
This file is shared with doc-quality-review. When updating personas here, sync the change to that skill's references/personas.md to keep evaluation consistent across the doc-* family.
How to use this library
1. In Phase 0, identify the 1–3 personas that the doc set serves primarily. A doc set serving more than 3 simultaneously is usually doing too much; flag that as an architectural finding. 2. In agent prompts, inline the relevant personas verbatim. Don't summarize — sub-agents calibrate better with the explicit profile than with a one-line audience label. 3. In findings, name the persona affected. "Findability fails for Incident Responder because alert text doesn't match runbook headings" beats "findability is poor." 4. For multi-persona docs, evaluate per-persona. The synthesis surfaces conflicts ("works for expert lookup, fails new user onboarding — current bias is toward expert").
Personas
Onboarding User
| Field | Value |
|---|---|
| Primary task | Learn enough to complete the first meaningful action successfully |
| Entry point | README, "Get Started" link, project landing page, blog post |
| Expertise | New to this project; may have general domain background but no project-specific context |
| Time pressure | Leisurely — willing to invest time, but easily lost or abandoned if confused |
| Success criterion | Finished a representative first task without abandoning; has a working mental model of what to learn next |
Evaluates positively:
- Quick start visible from the front door, completable in <15 minutes
- Each step explains why, not just what, so the mental model builds
- Clear "what's next" pointer at the end of each stage
- Vocabulary is introduced before use
- Examples are complete (no "fill in your own X" without showing what X looks like)
Evaluates negatively:
- Front door overwhelms with everything at once (no quick-start prominence)
- Required prerequisites aren't stated
- Jargon used before definition
- Examples reference undefined variables or omit setup
- Quick start doesn't actually work end-to-end
API Looker-Up
| Field | Value |
|---|---|
| Primary task | Find the exact signature, parameter, behavior, or return value of one specific symbol |
| Entry point | Search, IDE autocomplete pointing to docs, error message linking to a reference page |
| Expertise | Already familiar with the broader API; needs this one detail |
| Time pressure | Focused — context-switched from coding, wants to context-switch back fast |
| Success criterion | Got the precise answer in under 30 seconds, didn't have to read narrative |
Evaluates positively:
- Direct URL per symbol (deep-linkable)
- Flat or shallow hierarchy — no required reading order
- Tables for parameter listings
- Type signatures upfront, examples below
- Cross-links to related symbols
Evaluates negatively:
- Reference content embedded in tutorial prose
- Required reading of multiple sections to find one fact
- Missing edge cases (what if the parameter is null? what does it return on failure?)
- Inconsistent template across reference pages — has to relearn the layout
Incident Responder
| Field | Value |
|---|---|
| Primary task | Identify and apply the right recovery procedure for an active incident |
| Entry point | Alert, error message, runbook link from monitoring, on-call escalation |
| Expertise | Operational familiarity with the system; may not know this specific failure mode |
| Time pressure | Urgent — production is degraded, every minute matters |
| Success criterion | Found the right procedure in under 2 minutes; executed it without misstep |
Evaluates positively:
- Scenario-keyed entry (alert text matches runbook heading)
- Worst-case / most-common failure first, not last
- Steps are imperative, copy-pasteable
- Decision points clearly marked ("if X, do A; if Y, do B")
- Rollback path stated explicitly
Evaluates negatively:
- Background / "why" content before the procedure
- Discovery flow that requires understanding the system to find the right runbook
- Steps phrased as suggestions ("you might want to check…")
- Buried prerequisites
- Multiple runbooks for related scenarios with no cross-links
Architect Debugger
| Field | Value |
|---|---|
| Primary task | Build a mental model of how a subsystem works in order to track down a problem or plan a change |
| Entry point | Code reading led them to "what is this responsible for and why?" |
| Expertise | Senior engineer, comfortable reading code, wants design intent and constraints |
| Time pressure | Focused but patient — willing to read for understanding |
| Success criterion | Understood design intent and trade-offs; can predict component behavior under stress |
Evaluates positively:
- Architecture docs that explain the design and what alternatives were rejected
- Diagrams that match the code (citable file:line for each box)
- ADRs that capture constraints and reasoning, not just decisions
- Honest discussion of known limitations
- Cross-links between conceptual docs and the code that implements them
Evaluates negatively:
- Marketing-style "we built X because it's amazing" (no constraint discussion)
- Diagrams that don't match the code
- Implementation details with no design rationale
- Missing ADRs for non-obvious choices
Contributor
| Field | Value |
|---|---|
| Primary task | Make a code or doc change that fits project conventions and gets accepted |
| Entry point | CONTRIBUTING.md, issue they're working on, PR template |
| Expertise | Comfortable with the language and tooling; new to this project's conventions |
| Time pressure | Focused — has a specific change to make, wants to ship it |
| Success criterion | PR submitted that follows conventions, passes CI, gets approving review |
Evaluates positively:
- CONTRIBUTING.md surfaces dev setup, test commands, style rules in one place
- Clear conventions doc (file naming, commit format, PR shape)
- Examples of well-formed contributions to model on
- Explicit ownership of areas (who reviews what)
Evaluates negatively:
- Conventions scattered across many docs
- "We use $TOOL" without explaining how to run it locally
- PR feedback that cites unwritten rules
- Outdated dev setup that doesn't match current code
Operator
| Field | Value |
|---|---|
| Primary task | Deploy, configure, monitor, or upgrade the system in their environment |
| Operational expertise; may not know application internals | |
| Entry point | Installation guide, configuration reference, deployment docs, upgrade notes |
| Time pressure | Focused — has a specific deployment / change to do |
| Success criterion | System running correctly in their environment; knows how to monitor it; knows how to roll back |
Evaluates positively:
- Complete configuration reference (every env var and config key documented)
- Concrete deployment recipes for common platforms
- Migration paths between versions, with explicit data/state implications
- Monitoring and alerting recommendations
- Capacity / scaling guidance with real numbers
Evaluates negatively:
- "Configure as needed" with no list of what's configurable
- Deployment docs that assume a specific platform without saying so
- Missing upgrade notes between versions
- No rollback guidance
- Vague capacity planning
When to define a custom persona
These six cover most projects. Define a custom persona only when:
- The doc set serves a clearly distinct audience not represented (e.g.,
a regulator reviewing for compliance, an auditor checking security controls, a researcher cross-referencing methodology)
- A core persona above almost fits but a specific dimension differs
significantly (e.g., a low-expertise operator who needs more hand-holding than the standard Operator persona above)
Custom personas use the same five-field structure plus the two "evaluates positively / negatively" lists. Add them inline in the relevant report — don't try to maintain a project-specific persona library here unless the project is large enough that the six standard ones are routinely insufficient.
Multi-persona conflicts
A single doc legitimately serving multiple personas faces structural conflicts. Common conflicts and how to surface them:
| Conflict | Symptom | Surface as |
|---|---|---|
| New user vs expert | Reference doc with long narrative explanations slows expert lookup but a flat reference confuses new user | Finding: "structure biased toward <persona>; for <other persona>, recommend <change>" |
| Operator vs incident responder | Configuration reference doubles as runbook, mixing leisurely setup info with urgent recovery steps | Finding: "split runbook content into separate doc keyed by alert text" |
| Onboarding vs contributor | README tries to be both "what is this?" and "how to contribute?" | Finding: "split README into landing + CONTRIBUTING; landing optimizes for Onboarding User" |
Don't pretend conflicts don't exist. The most common architectural failure is silently optimizing for one persona while claiming to serve all of them.
#!/usr/bin/env python3
"""
Documentation Link Graph
Builds a directed graph of internal markdown links and reports:
- Orphans (pages with no inbound links from other docs)
- Dead-ends (pages with no outbound links to other docs)
- Reciprocity stats (mutual vs one-way links)
- Broken links (targets that don't resolve)
- Hub pages (high in-degree) and Bridge pages (high betweenness, approximated)
This script handles the *mechanical* side of architecture review (Heuristic 1
"Findability" orphan detection, Heuristic 4 "Cross-Linking Quality" graph
metrics). Judgment-heavy heuristics (whether a link is contextual, whether
patterns are consistent) require sonnet sub-agents — see SKILL.md.
Usage:
python3 skills/doc-architecture-review/scripts/link_graph.py [OPTIONS]
Options:
--json Output as JSON instead of markdown
--root PATH Project root directory (default: git root or cwd)
--scope Which docs to scan: docs, manual, all (default: all)
--min-fanout N
Hub threshold — pages with >= N inbound links are listed (default: 5)
"""
import argparse
import json
import re
import subprocess
import sys
from collections import defaultdict
from dataclasses import asdict, dataclass, field
from pathlib import Path
SCOPE_DIRS = {
"docs": ["docs"],
"manual": ["manual"],
"all": ["docs", "manual", "site"],
}
ALWAYS_INCLUDE = ["README.md", "CONTRIBUTING.md"]
SKIP_DIRS = {"node_modules", ".git", "__pycache__", "archive", "_site"}
MD_LINK_RE = re.compile(r"\[([^\]]*)\]\(([^)]+)\)")
JEKYLL_LINK_RE = re.compile(r"\{%\s*link\s+([^\s%}]+)\s*%\}")
@dataclass
class GraphNode:
path: str
inbound: list = field(default_factory=list) # paths that link to this
outbound: list = field(default_factory=list) # paths this links to
broken_outbound: list = field(default_factory=list)
def get_project_root(root_override=None):
if root_override:
return Path(root_override).resolve()
try:
result = subprocess.run(
["git", "rev-parse", "--show-toplevel"],
capture_output=True, text=True, check=True,
)
return Path(result.stdout.strip())
except (subprocess.CalledProcessError, FileNotFoundError):
return Path.cwd()
def find_markdown_files(root, scope):
files = []
dirs = SCOPE_DIRS.get(scope, SCOPE_DIRS["all"])
for d in dirs:
search_root = root / d
if search_root.exists():
for md in search_root.rglob("*.md"):
if any(part in SKIP_DIRS for part in md.relative_to(root).parts):
continue
files.append(md)
for name in ALWAYS_INCLUDE:
f = root / name
if f.exists():
files.append(f)
return sorted(set(files))
def extract_link_targets(filepath, root):
"""Yield (target_path, raw_target) for every internal markdown link."""
try:
content = filepath.read_text(encoding="utf-8", errors="replace")
except OSError:
return
# Standard markdown links
for match in MD_LINK_RE.finditer(content):
target = match.group(2).strip()
# Skip URLs, anchors, mailto
if target.startswith(("http://", "https://", "#", "mailto:")):
continue
# Strip anchor fragment
clean = target.split("#")[0]
if not clean:
continue
yield clean, target
# Jekyll {% link path %} references — site/ uses these heavily
for match in JEKYLL_LINK_RE.finditer(content):
clean = match.group(1).strip()
yield clean, match.group(0)
def resolve_target(source_file, target, root):
"""Resolve a link target to an absolute path.
Handles relative paths and Jekyll-rooted paths. Returns the resolved
Path or None if the target doesn't resolve to an existing file.
"""
# Jekyll {% link foo.md %} — relative to site root, but accept relative-to-doc too
candidates = [
(source_file.parent / target).resolve(),
(root / target).resolve(),
]
# If target lacks .md extension, also try with it (Just-The-Docs sometimes does this)
if "." not in Path(target).name:
candidates.extend([
(source_file.parent / (target + ".md")).resolve(),
(root / (target + ".md")).resolve(),
])
for c in candidates:
if c.exists():
return c
return None
def build_graph(root, md_files):
"""Construct the link graph keyed by relative path."""
nodes = {}
for md in md_files:
rel = str(md.relative_to(root))
nodes[rel] = GraphNode(path=rel)
for md in md_files:
rel_source = str(md.relative_to(root))
for clean_target, raw_target in extract_link_targets(md, root):
resolved = resolve_target(md, clean_target, root)
if resolved is None or not resolved.is_file():
nodes[rel_source].broken_outbound.append(raw_target)
continue
try:
rel_target = str(resolved.relative_to(root))
except ValueError:
# Outside the repo root — skip
continue
if rel_target not in nodes:
# Linked file isn't markdown (could be image, asset) — skip
continue
nodes[rel_source].outbound.append(rel_target)
nodes[rel_target].inbound.append(rel_source)
return nodes
def find_orphans(nodes, root):
"""Pages with no inbound links and not navigation entry points."""
orphans = []
for path, node in nodes.items():
# Skip entry-point pages — they're not expected to be linked
name = Path(path).name.lower()
if name in ("readme.md", "index.md"):
continue
if not node.inbound:
orphans.append(path)
return sorted(orphans)
def find_dead_ends(nodes):
"""Pages with no outbound internal links — content silos."""
dead_ends = []
for path, node in nodes.items():
if not node.outbound:
dead_ends.append(path)
return sorted(dead_ends)
def compute_reciprocity(nodes):
"""Count mutual vs one-way edges in the link graph."""
edges = set()
mutual = 0
one_way = 0
for path, node in nodes.items():
for target in set(node.outbound):
edges.add((path, target))
for source, target in edges:
if (target, source) in edges:
mutual += 1
else:
one_way += 1
# mutual edges counted once per pair below; halve to get unique mutual pairs
unique_mutual_pairs = mutual // 2
return {
"total_directed_edges": len(edges),
"mutual_pairs": unique_mutual_pairs,
"one_way_edges": one_way,
"reciprocity_ratio": (
round(unique_mutual_pairs * 2 / len(edges), 3) if edges else 0.0
),
}
def find_hubs(nodes, min_fanout):
"""Pages with high in-degree — natural reference targets."""
hubs = []
for path, node in nodes.items():
in_degree = len(set(node.inbound))
if in_degree >= min_fanout:
hubs.append((path, in_degree))
return sorted(hubs, key=lambda t: -t[1])
def find_broken_links(nodes):
"""Pages with at least one broken outbound link."""
broken = []
for path, node in nodes.items():
if node.broken_outbound:
broken.append({
"source": path,
"broken_targets": list(node.broken_outbound),
})
return sorted(broken, key=lambda d: d["source"])
def generate_markdown_report(stats, orphans, dead_ends, recip, hubs, broken, root):
lines = [
"# Documentation Link Graph Report",
"",
f"**Project root:** `{root}`",
f"**Total pages:** {stats['total_pages']}",
f"**Total internal edges:** {recip['total_directed_edges']}",
"",
"## Reciprocity",
"",
f"- Mutual link pairs: {recip['mutual_pairs']}",
f"- One-way edges: {recip['one_way_edges']}",
f"- Reciprocity ratio: {recip['reciprocity_ratio']:.3f} "
f"(1.0 = every edge has a back-link)",
"",
]
lines.append(f"## Orphans ({len(orphans)})")
lines.append("")
if orphans:
lines.append("Pages with no inbound links from any other doc — effectively invisible:")
lines.append("")
for o in orphans:
lines.append(f"- `{o}`")
else:
lines.append("No orphan pages.")
lines.append("")
lines.append(f"## Dead-Ends ({len(dead_ends)})")
lines.append("")
if dead_ends:
lines.append("Pages with no outbound internal links — content silos:")
lines.append("")
for d in dead_ends:
lines.append(f"- `{d}`")
else:
lines.append("No dead-end pages.")
lines.append("")
lines.append(f"## Hubs ({len(hubs)})")
lines.append("")
if hubs:
lines.append("Pages with high in-degree — natural reference targets:")
lines.append("")
lines.append("| Page | Inbound Links |")
lines.append("|------|---------------|")
for path, count in hubs[:20]:
lines.append(f"| `{path}` | {count} |")
else:
lines.append("No hub pages above threshold.")
lines.append("")
lines.append(f"## Broken Links ({len(broken)})")
lines.append("")
if broken:
lines.append("| Source | Broken Targets |")
lines.append("|--------|----------------|")
for entry in broken:
targets = ", ".join(f"`{t}`" for t in entry["broken_targets"])
lines.append(f"| `{entry['source']}` | {targets} |")
else:
lines.append("No broken internal links.")
lines.append("")
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(description="Doc link graph analyzer")
parser.add_argument("--json", action="store_true", help="Output as JSON")
parser.add_argument("--root", type=str, default=None, help="Project root path")
parser.add_argument(
"--scope",
choices=["docs", "manual", "all"],
default="all",
help="Which docs to scan (default: all)",
)
parser.add_argument(
"--min-fanout", type=int, default=5,
help="Hub threshold — pages with >= N inbound links (default: 5)",
)
args = parser.parse_args()
root = get_project_root(args.root)
md_files = find_markdown_files(root, args.scope)
print(f"Scanning {len(md_files)} markdown files in {root}...", file=sys.stderr)
nodes = build_graph(root, md_files)
orphans = find_orphans(nodes, root)
dead_ends = find_dead_ends(nodes)
recip = compute_reciprocity(nodes)
hubs = find_hubs(nodes, args.min_fanout)
broken = find_broken_links(nodes)
stats = {"total_pages": len(nodes)}
if args.json:
report = {
"project_root": str(root),
"stats": stats,
"reciprocity": recip,
"orphans": orphans,
"dead_ends": dead_ends,
"hubs": [{"path": p, "in_degree": d} for p, d in hubs],
"broken_links": broken,
}
print(json.dumps(report, indent=2))
else:
print(generate_markdown_report(stats, orphans, dead_ends, recip, hubs, broken, root))
if __name__ == "__main__":
main()