
Doc Maintenance
- 2 installs
- 28 repo stars
- Updated June 29, 2026
- nickcrew/claude-cortex
This is a copy of doc-maintenance by nickcrew - installs and ranking accrue to the original listing.
Helps with ai & agent building tasks.
About
doc-maintenance is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- doc-maintenance
- AI & Agent Building
- AI-coding skill
Doc Maintenance by the numbers
- 2 all-time installs (skills.sh)
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/nickcrew/claude-cortex --skill doc-maintenanceAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2 |
|---|---|
| repo stars | ★ 28 |
| Last updated | June 29, 2026 |
| Repository | nickcrew/claude-cortex ↗ |
What it does
Helps with ai & agent building tasks.
Files
Documentation Maintenance
Systematically audit, organize, and remediate project documentation by comparing the codebase against existing docs to find staleness, gaps, and misorganization.
When to Trigger
- After merging a feature branch or completing a refactor
- After dependency upgrades or API changes
- When onboarding surfaces confusion about project docs
- Periodic maintenance (monthly or per-release)
- When
scripts/doc_audit.pyis run manually and reports findings
Workflow Overview
Phase 1: Audit → Run deterministic scan + haiku search agents
Phase 2: Triage → Classify findings by severity and action type
Phase 3: Remediate → Dispatch specialized agents to fix/create docs
Phase 4: Quality → docs-architect reviews all changes---
Phase 1: Audit
Step 1a — Run the deterministic scan
Execute the bundled audit script to get a baseline report:
python3 skills/doc-maintenance/scripts/doc_audit.pyThe script produces a structured report covering:
- Broken internal links (markdown
[text](path)pointing to missing files) - Orphan docs (files not linked from any other doc or README)
- Missing required structure (expected folders/files absent from
docs/ormanual/) - Stale-relative-to-code (docs whose referenced subtree has churned significantly since the doc was last touched — measured via git log of code commits between the doc's last edit and now, not absolute doc age, which produces noise on stable architecture docs)
- Empty or stub files (< 3 lines of content)
Pass --json for machine-readable output. Pass --root PATH to override project root detection.
Step 1b — Dispatch search agents
After the deterministic scan, launch subagents to perform deeper analysis. Model selection is task-calibrated: haiku for pattern enumeration, sonnet for multi-file correlation and judgment. See references/agent-dispatch.md for full prompt templates.
Agent 1 — Code-to-doc coverage scan (subagent_type: "Explore", model: "haiku"): Search the codebase for public APIs, CLI commands, config schemas, and exported modules. Cross-reference against existing docs (presence-check via grep). Report anything undocumented. Haiku is sufficient because the inner loop is pattern enumeration plus a presence grep.
Agent 2 — Doc-to-code freshness scan (subagent_type: "general-purpose", model: "sonnet", per-docfile dispatch): For each markdown file in scope, dispatch a sonnet agent with the file content. The agent (a) extracts every concrete code reference (function names, CLI flags, file paths, config keys, API endpoints), (b) verifies each against current code via grep / codanna / Read, (c) reports mismatches as RENAMED / REMOVED / CHANGED. Sonnet + per-docfile is needed because freshness verification often requires multi-file traces (handler → middleware → config) and distinguishing renamed from removed requires reading the new location, not just confirming the old one is gone.
Agent 3 — Structure compliance scan (subagent_type: "Explore", model: "haiku"): Compare current docs/ and manual/ layout against the prescribed folder structure in references/folder-structure.md. Report missing folders, misplaced files, naming violations. Haiku is correct here — pure pattern matching against a spec.
Agent 4a — ASCII diagram detector (subagent_type: "Explore", model: "haiku"): Scan markdown for box-drawing characters (─ │ ┌ ┐ └ ┘ ├ ┤ ┬ ┴ ┼), arrow notation (-->, <--, ==>), or indented tree structures beyond a few nodes. Report each as a candidate for Mermaid conversion with the suggested diagram type. Haiku is correct because this is mechanical pattern detection — find the characters, report the location.
Agent 4b — Missing-diagram judgment scan (subagent_type: "general-purpose", model: "sonnet", per-docfile dispatch): For each markdown file in scope, dispatch a sonnet agent. The agent reads the doc and flags sections where adding a diagram would meaningfully improve comprehension — multi-step flows where ordering matters, architecture relationships with named components, state transitions, request/response sequences. Crucially, sonnet flags only when a diagram adds value over the prose (not every step list needs a diagram). Haiku tends to either over-flag (every list looks diagrammable) or under-flag (misses implicit flow descriptions); sonnet's judgment is what makes this signal trustworthy.
Launch agents 1, 2, 3, 4a, 4b in parallel — but agents 2 and 4b dispatch internally per-docfile, so the actual call count is 2 + (2 × N_docfiles) rather than 5.
Step 1c — Merge results
Combine the script output with agent findings into a single audit report. Deduplicate overlapping findings. The report becomes the input for Phase 2.
---
Phase 2: Triage
Classify each finding into one of these action categories:
| Category | Description | Example |
|---|---|---|
| stale | Doc exists but references outdated code/behavior | CLI flag renamed but docs show old name |
| missing | No doc exists for a documented-worthy item | Public API endpoint with no reference doc |
| orphan | Doc exists but is unreachable / unlinked | Guide file not in any index or nav |
| misplaced | Doc exists but is in the wrong folder | Tutorial sitting in docs/architecture/ |
| irrelevant | Doc covers removed functionality | Guide for a deleted feature |
| structural | Folder structure deviates from prescribed layout | Missing docs/security/ folder |
| diagram-convert | ASCII/text diagram should be Mermaid | Complex box-drawing flowchart in architecture doc |
| diagram-missing | Section would benefit from a diagram | Multi-step process described only in prose |
Assign severity:
- P0 — User-facing doc is factually wrong (manual/)
- P1 — Developer doc references nonexistent code
- P2 — Missing doc for public API or feature
- P3 — Structural / organizational issues
- P4 — Minor staleness, cosmetic
---
Phase 3: Remediate
Route each finding to the appropriate specialist agent. Use the Task tool with the subagent types listed below. See references/agent-dispatch.md for detailed prompt templates.
| Doc type | Subagent type | Target location |
|---|---|---|
| API reference docs | reference-builder | docs/reference/ or docs/api/ |
| Architecture docs | technical-writer | docs/architecture/ |
| Developer guides (style, local dev, workflows) | technical-writer | docs/development/ |
| Testing docs | technical-writer | docs/testing/ |
| Security docs | technical-writer | docs/security/ |
| User-facing tutorials | learning-guide | manual/tutorials/ |
| User-facing how-to guides | learning-guide | manual/guides/ |
| User-facing getting started | learning-guide | manual/getting-started/ |
| Plans and proposals | technical-writer | docs/plans/ |
| ASCII diagram conversion | mermaid-expert | Inline in existing doc |
| New diagrams for prose sections | mermaid-expert | Inline in existing doc |
Parallel dispatch: Group independent remediation tasks and dispatch them simultaneously. Only serialize when one doc depends on another (e.g., an API reference needed before a tutorial that links to it). Dispatch up to 4 remediation agents in parallel per batch.
For updates to existing docs: Provide the agent with the current file contents and the specific finding to fix. Instruct it to make minimal, targeted edits.
For new docs: Provide the agent with the relevant source code, the target file path, and the folder-structure spec so it follows naming conventions.
---
Phase 4: Quality Gate
After all remediation agents complete, dispatch a single docs-architect agent to review the full set of changes. The quality gate checks:
1. Accuracy — Do docs match current code? 2. Completeness — Are all public interfaces covered? 3. Organization — Does folder structure match the prescribed layout? 4. Cross-references — Are all internal links valid? 5. Consistency — Tone, formatting, heading levels 6. No orphans — Every new doc is linked from an index or parent doc
If the quality gate fails, loop back to Phase 3 for the specific issues flagged. Maximum 2 remediation loops before escalating to the user.
---
Folder Structure
The prescribed folder layout is defined in references/folder-structure.md. Summary:
docs/ — Internal / developer documentation
docs/
├── architecture/ — System design, ADRs, component diagrams
├── development/ — Developer guides: style, local setup, issue tracking
├── plans/ — Proposals, RFCs, roadmaps
├── reviews/ — Code review records, audit reports
├── testing/ — Test strategy, coverage reports, test plans
├── reports/ — Generated reports, metrics, analysis
├── security/ — Security policies, threat models, audit findings
├── api/ — Internal API docs (OpenAPI specs, gRPC protos)
├── reference/ — CLI reference, config reference, manpages
├── ideas/ — Exploratory notes, spikes, brainstorms
└── archive/ — Deprecated docs preserved for historymanual/ — User-facing documentation (project root)
manual/
├── getting-started/ — Installation, quickstart, first steps
├── guides/ — How-to guides for common tasks
├── tutorials/ — Step-by-step learning paths
├── reference/ — User-facing command/config reference
└── troubleshooting/ — FAQ, common errors, known issuesREADME.md — Project root
The main README is audited for accuracy but not reorganized. Findings about the README are reported as stale/missing items for manual remediation.
---
Anti-Patterns
- Do not delete docs without confirming the feature they describe is truly removed
- Do not reorganize docs without updating all internal cross-references
- Do not create stub files just to fill the folder structure — only create docs with real content
- Do not duplicate content between
docs/andmanual/— link instead - Do not move user-facing docs into
docs/or developer docs intomanual/
Agent Dispatch Reference
This reference defines which subagent types to use for documentation tasks, how to prompt them, and coordination patterns.
Search Agents (Phase 1)
Model selection is task-calibrated. Pure pattern enumeration (find every X) runs on haiku + Explore. Multi-file correlation and judgment runs on sonnet + general-purpose, dispatched per-docfile so each call has focused context.
Code-to-Doc Coverage Agent
Purpose: Find codebase constructs that lack documentation.
Task tool parameters:
subagent_type: "Explore"
model: "haiku"
description: "Scan code for undocumented items"Prompt template:
Search the codebase for publicly exported or user-facing constructs:
- Exported functions, classes, and constants
- CLI entry points and subcommands
- Configuration schemas and environment variables
- Public API endpoints
- Key data models
For each item found, check whether corresponding documentation exists in
docs/ or manual/. Report items that are NOT documented, including:
- The item name and type (function, class, CLI command, etc.)
- The source file and line number
- Which doc folder it should live in per the folder structure
Do NOT read the full contents of large files. Use Grep to find exports
and Glob to check for matching doc files.Doc-to-Code Freshness Agent (per-docfile sonnet)
Purpose: Verify that existing docs still match the codebase. Multi-file trace work — handler → middleware → config — is common, so haiku's excerpt reads aren't sufficient.
Dispatch pattern: One agent call per markdown file in scope. Total calls = N markdown files. Each call has focused context (one doc + the codebase) for higher precision.
Task tool parameters:
subagent_type: "general-purpose"
model: "sonnet"
description: "Doc-to-code freshness for <docfile>"Prompt template (one per docfile):
Read the doc file at <DOCFILE_PATH>. Identify every concrete code reference:
- Function or method names
- CLI flags and commands
- File paths referenced in the doc
- Configuration keys and values
- API endpoints or routes
- Class names and module paths
For each reference, verify it still exists in the codebase. Use codanna MCP
when available; otherwise grep + Read. For each reference that has changed:
Classify as:
- RENAMED: construct exists under a different name (cite both old and new
locations as `path:line`)
- REMOVED: construct no longer exists anywhere (cite where the doc references
it, plus a grep that returned 0 matches)
- CHANGED: construct exists but signature/behavior differs (cite the current
definition and quote the divergence)
Output as YAML, one entry per finding:
- doc_file: <path>
- line: <line in doc>
- reference: <what the doc says>
- status: RENAMED | REMOVED | CHANGED
- evidence: <verbatim doc line>
- current_location: <path:line> (for RENAMED/CHANGED)
- discrepancy: <what's different> (for CHANGED)
Optimize for accuracy over volume — 5 verified mismatches beat 20 with
fabricated paths. Verify each location exists before reporting.Structure Compliance Agent
Purpose: Verify folder layout matches the prescribed structure.
Task tool parameters:
subagent_type: "Explore"
model: "haiku"
description: "Audit doc folder structure"Prompt template:
Read the folder structure specification at:
skills/doc-maintenance/references/folder-structure.md
Then examine the actual directory trees under docs/ and manual/ using
Glob patterns. Report:
- MISSING: Required folders that do not exist
- MISPLACED: Files that exist in the wrong folder per the spec
- NAMING: Files that violate naming conventions (spaces, camelCase, etc.)
- NO_INDEX: Folders that lack an index.md or README.md
Use Glob with patterns like "docs/**/*.md" and "manual/**/*.md" to
discover all files, then classify each by its parent folder.Agent 4a — ASCII Diagram Detector (haiku, mechanical)
Purpose: Find ASCII/text diagrams that should be converted to Mermaid. Pure pattern matching — find the box-drawing characters or arrow notation, report their location.
Task tool parameters:
subagent_type: "Explore"
model: "haiku"
description: "Scan docs for ASCII diagrams to convert"Prompt template:
Scan markdown files under docs/, manual/, and README.md for ASCII diagrams.
Look for:
- Box-drawing characters (─ │ ┌ ┐ └ ┘ ├ ┤ ┬ ┴ ┼) in code blocks or indented sections
- Arrow notation (-->, <--, ==>) in code blocks
- Pipe-based tables used as diagrams
- Indented tree structures beyond a few simple nodes
Only flag diagrams with more than a few nodes — trivial 2-3 node diagrams can
stay as ASCII.
Output format for each finding:
- File: [path]
- Lines: [start]-[end]
- Suggested Mermaid type: [flowchart|sequenceDiagram|stateDiagram|erDiagram|etc.]
- Approximate node count: [N]
Do NOT make judgment calls about whether prose sections without ASCII diagrams
"need" a diagram — that's a separate scan handled by Agent 4b.Agent 4b — Missing-Diagram Judgment Scan (sonnet, per-docfile)
Purpose: Identify prose sections where adding a diagram would meaningfully improve comprehension. This is judgment, not pattern matching — haiku tends to either over-flag (every step list looks diagrammable) or under-flag (misses implicit flow descriptions). Sonnet's calibration is what makes this signal trustworthy.
Dispatch pattern: One agent call per markdown file in scope.
Task tool parameters:
subagent_type: "general-purpose"
model: "sonnet"
description: "Missing-diagram judgment for <docfile>"Prompt template (one per docfile):
Read the doc file at <DOCFILE_PATH>. Identify sections where adding a Mermaid
diagram would *meaningfully* improve comprehension. Be selective.
Flag a section ONLY when ALL of these hold:
1. The prose describes a multi-step flow, architectural relationship, state
transition, request/response sequence, data model with relationships, or
decision tree.
2. The relationship is non-obvious from a quick read — readers would
construct a mental diagram anyway, and putting it on the page saves effort.
3. The diagram would NOT just repeat what's already clear from the prose
structure (numbered lists describing simple sequence don't need diagrams).
4. No diagram or visual already exists in or near the section.
Common false positives to skip:
- Any list of 3-4 steps (these read fine as prose)
- Any "if X then Y" pair (not enough to merit a diagram)
- Sections that describe a single entity's properties (use a table, not a diagram)
Output format for each finding:
- File: <path>
- Lines: <start>-<end>
- Suggested Mermaid type: <flowchart|sequenceDiagram|stateDiagram|erDiagram>
- What the diagram should depict: <one sentence>
- Why prose alone is insufficient: <one sentence — must be specific to this section, not generic>
Optimize for precision over recall. A clean log of 3 high-value diagrams beats
20 marginal candidates the user will mostly ignore.---
Remediation Agents (Phase 3)
These agents create or update documentation. Use the specific subagent types below.
reference-builder
Use for: API documentation, configuration references, CLI references, parameter listings.
Task tool parameters:
subagent_type: "reference-builder"
description: "Build API/CLI reference doc"Prompt template (new doc):
Create a comprehensive reference document for [ITEM].
Source code to document:
[FILE_PATH]
Target output path:
[TARGET_PATH per folder structure]
Requirements:
- Document every public parameter, option, and return value
- Include usage examples for each entry
- Follow the naming conventions in the folder structure spec
- Use tables for parameter listings
- Include a table of contents for documents with >5 sectionsPrompt template (update existing):
Update the reference document at [DOC_PATH].
The following items are stale or missing:
[LIST OF FINDINGS]
Read the current document, then read the source code at [SOURCE_PATH].
Make minimal, targeted edits to fix only the identified issues.
Do not reorganize or restyle unaffected sections.technical-writer
Use for: Architecture docs, developer guides, testing docs, security docs, plans, and any internal documentation.
Task tool parameters:
subagent_type: "technical-writer"
description: "Write/update developer doc"Prompt template (new doc):
Create a [DOC_TYPE] document for [TOPIC].
Relevant source files:
[FILE_PATHS]
Target output path:
[TARGET_PATH per folder structure]
Requirements:
- Write for a developer audience familiar with the project
- Include concrete code examples where relevant
- Follow existing doc conventions in the project
- Add to the parent folder's index.md if one existsPrompt template (update existing):
Update the document at [DOC_PATH].
Findings to address:
[LIST OF FINDINGS]
Read the current document and the relevant source code.
Fix only the identified issues. Preserve the existing structure
and voice of the document.learning-guide
Use for: User-facing tutorials, getting-started guides, how-to guides, troubleshooting docs. All output goes to manual/.
Task tool parameters:
subagent_type: "learning-guide"
description: "Write user-facing tutorial/guide"Prompt template (new doc):
Create a [GUIDE_TYPE] for [TOPIC] targeting end users.
Relevant source files for understanding the feature:
[FILE_PATHS]
Target output path:
[TARGET_PATH under manual/]
Requirements:
- Write for users who may not be developers
- Use progressive disclosure: start simple, add complexity
- Include concrete, copy-pasteable examples
- Add troubleshooting tips for common pitfalls
- Follow the naming convention: [CONVENTION per folder-structure.md]Prompt template (update existing):
Update the user guide at [DOC_PATH].
Findings to address:
[LIST OF FINDINGS]
Read the current guide and the relevant source code.
Fix only the identified issues. Maintain the existing
progressive-disclosure structure and user-friendly tone.mermaid-expert
Use for: Converting ASCII/text diagrams to Mermaid and creating new diagrams where prose would benefit from visual representation. Diagrams are inlined into the markdown file as fenced mermaid code blocks.
Task tool parameters:
subagent_type: "mermaid-expert"
description: "Create/convert Mermaid diagram"Prompt template (convert ASCII to Mermaid):
Convert the ASCII/text diagram in [DOC_PATH] at lines [LINE_RANGE] to a
Mermaid diagram.
Read the file and understand the diagram's intent from the surrounding context.
Replace the ASCII diagram with a fenced mermaid code block:
[diagram code]
Requirements:
- Preserve all nodes, edges, and labels from the original
- Choose the most appropriate Mermaid diagram type: [SUGGESTED_TYPE]
- Keep the diagram readable — use short node labels with longer descriptions
in the surrounding prose if needed
- Remove the original ASCII diagram after inserting the Mermaid block
- Do not change any other content in the filePrompt template (create new diagram):
Add a Mermaid diagram to [DOC_PATH] near line [LINE_NUMBER] to illustrate
the [DESCRIPTION] described in that section.
Read the file and the surrounding context. Insert an inline fenced mermaid
code block:
[diagram code]
Requirements:
- Diagram type: [SUGGESTED_TYPE]
- Capture the key relationships/flow described in the prose
- Keep diagrams focused — 5-15 nodes is ideal, avoid overwhelming detail
- Place the diagram immediately after the prose paragraph it illustrates
- Do not duplicate information already clear from the text — the diagram
should complement the prose, not repeat it verbatim
- Do not change any other content in the filedocs-architect (Quality Gate)
Use for: Final review of all documentation changes from a maintenance pass.
Task tool parameters:
subagent_type: "docs-architect"
description: "Quality gate review of doc changes"Prompt template:
Review all documentation changes from this maintenance pass.
Files created or modified:
[LIST OF FILE_PATHS]
Folder structure spec:
skills/doc-maintenance/references/folder-structure.md
Check for:
1. ACCURACY — Do docs match current code?
2. COMPLETENESS — Are all public interfaces covered?
3. ORGANIZATION — Does folder structure match the spec?
4. CROSS-REFERENCES — Are all internal links valid?
5. CONSISTENCY — Tone, formatting, heading levels
6. NO ORPHANS — Every new doc is linked from an index or parent
Output a structured verdict:
- PASS: All checks pass
- FAIL: List specific issues that must be fixed before closing
If FAIL, categorize each issue by which remediation agent should fix it.---
Coordination Patterns
Parallel dispatch
Group independent remediation tasks and dispatch simultaneously:
# Good: these don't depend on each other
Task 1: reference-builder → docs/api/auth-service.md
Task 2: technical-writer → docs/architecture/data-flow.md
Task 3: learning-guide → manual/guides/how-to-configure-auth.mdSerial dispatch
When one doc depends on another, serialize:
# The tutorial links to the API reference, so reference must exist first
Task 1: reference-builder → docs/api/auth-service.md
Task 2: learning-guide → manual/tutorials/02-authentication.md (depends on Task 1)Batch size
Dispatch up to 4 remediation agents in parallel. If more than 4 findings need remediation, batch them in groups of 4 and wait for each batch to complete before starting the next.
Documentation Folder Structure Specification
This reference defines the canonical folder structure for project documentation. All documentation maintenance operations must conform to this layout.
Two-Root Model
Documentation lives in two root directories:
| Root | Audience | Location |
|---|---|---|
docs/ | Developers, contributors, internal teams | Project root |
manual/ | End users, operators, external consumers | Project root |
These two roots must never be mixed. Developer docs belong in docs/, user-facing docs belong in manual/.
---
docs/ — Developer & Internal Documentation
docs/architecture/
System design and architectural decision records.
Contains:
- Architecture Decision Records (ADRs) —
adr-NNN-title.md - System diagrams and component overviews
- Data flow documentation
- Integration architecture
Naming: ADRs use adr-NNN-title.md format. Other files use kebab-case.
docs/development/
Developer guides for contributing to and working with the project.
Contains:
- Style guides (code style, commit conventions, PR process)
- Local development setup instructions
- Issue tracking and workflow documentation
- Environment configuration guides
- Tooling guides (linters, formatters, CI)
Naming: kebab-case, descriptive filenames (e.g., local-setup.md, style-guide.md).
docs/plans/
Proposals, RFCs, and roadmap documents.
Contains:
- Feature proposals and RFCs
- Release plans and roadmaps
- Migration plans
- Deprecation plans
Naming: YYYY-MM-title.md for dated proposals, kebab-case for evergreen plans.
docs/reviews/
Code review records and audit reports.
Contains:
- Review summaries and decisions
- Audit findings and remediation tracking
- Post-mortem documents
Naming: YYYY-MM-DD-title.md for dated reviews.
docs/testing/
Test strategy and coverage documentation.
Contains:
- Test strategy and philosophy
- Coverage reports and goals
- Test plan documents
- Testing infrastructure guides
Naming: kebab-case.
docs/reports/
Generated reports, metrics, and analysis.
Contains:
- Performance benchmarks
- Dependency audit reports
- Documentation audit results
- Metrics dashboards and summaries
Naming: YYYY-MM-DD-title.md for dated reports, kebab-case for templates.
docs/security/
Security policies, threat models, and audit findings.
Contains:
- Security policies and guidelines
- Threat models (STRIDE, attack trees)
- Vulnerability disclosures and remediation
- Compliance documentation
Naming: kebab-case. Sensitive findings may use restricted-access patterns.
docs/api/
Internal API documentation.
Contains:
- OpenAPI / Swagger specs
- gRPC protobuf documentation
- Internal service API references
- Webhook schemas
Naming: Match the API/service name (e.g., auth-service.md, openapi.yaml).
docs/reference/
CLI reference, configuration reference, and manpages.
Contains:
- CLI command reference (manpage sources)
- Configuration file reference
- Environment variable reference
- Glossary and terminology
Naming: Match the tool or config name (e.g., cortex.1, config-reference.md).
docs/ideas/
Exploratory notes, spikes, and brainstorms.
Contains:
- Spike results and exploratory research
- Brainstorm notes
- Rough ideas not yet promoted to plans
- Technology evaluations
Naming: kebab-case, informal. These may be promoted to docs/plans/ when mature.
docs/archive/
Deprecated documentation preserved for historical reference.
Contains:
- Docs for removed features
- Superseded architecture docs
- Old plans that were completed or abandoned
Naming: Preserve original filename. Optionally prefix with ARCHIVED-.
Rule: Moving a doc to archive requires removing all inbound links to it or replacing them with a note that the doc is archived.
---
manual/ — User-Facing Documentation
manual/getting-started/
First-contact documentation for new users.
Contains:
- Installation instructions
- Quickstart guide
- Prerequisites and system requirements
- First-run walkthrough
Naming: kebab-case. Keep filenames intuitive for users (e.g., install.md, quickstart.md).
manual/guides/
Task-oriented how-to guides.
Contains:
- How to accomplish specific tasks
- Configuration guides for common scenarios
- Integration guides with external tools
- Workflow guides
Naming: how-to-VERB-NOUN.md pattern preferred (e.g., how-to-configure-auth.md).
manual/tutorials/
Step-by-step learning paths with progressive complexity.
Contains:
- Guided tutorials that build understanding
- Example-driven learning sequences
- Hands-on exercises
Naming: Number-prefixed for ordering (e.g., 01-basics.md, 02-advanced-config.md).
manual/reference/
User-facing command and configuration reference.
Contains:
- Command reference (user-oriented, not developer-oriented)
- Configuration option reference
- Supported formats, protocols, and integrations
Naming: Match the feature or command name.
manual/troubleshooting/
Problem-solving documentation.
Contains:
- FAQ
- Common errors and solutions
- Known issues and workarounds
- Diagnostic procedures
Naming: kebab-case (e.g., common-errors.md, faq.md).
---
Cross-Cutting Rules
1. Index files: Each subfolder should contain an index.md or README.md that lists and briefly describes its contents. 2. Internal links: Always use relative paths for cross-references within the same root. Use ../../manual/ or ../../docs/ for cross-root references. 3. No loose files: Every markdown file must live in the appropriate subfolder, not directly in docs/ or manual/ (except docs/README.md or manual/README.md). 4. Diagrams: Store diagrams next to the doc that references them in a diagrams/ subfolder, or use inline Mermaid blocks. 5. Naming conventions: All filenames use kebab-case. No spaces, no camelCase.
#!/usr/bin/env python3
"""
Documentation Audit Script
Performs a deterministic scan of project documentation to identify:
- Broken internal links
- Orphan docs (not linked from anywhere)
- Missing required folder structure
- Stale files (unchanged while sibling code changed)
- Empty or stub files
Usage:
python3 skills/doc-maintenance/scripts/doc_audit.py [--json] [--root PATH]
Options:
--json Output as JSON instead of markdown
--root Project root directory (default: current directory)
"""
import argparse
import json
import os
import re
import subprocess
import sys
import time
from collections import defaultdict
from dataclasses import asdict, dataclass, field
from pathlib import Path
# --- Configuration ---
DOCS_ROOT = "docs"
MANUAL_ROOT = "manual"
README = "README.md"
REQUIRED_DOCS_FOLDERS = [
"architecture",
"development",
"plans",
"reviews",
"testing",
"reports",
"security",
"api",
"reference",
"ideas",
"archive",
]
REQUIRED_MANUAL_FOLDERS = [
"getting-started",
"guides",
"tutorials",
"reference",
"troubleshooting",
]
# Flag a doc as stale when the project's non-doc subtree has accumulated this
# many commits since the doc was last touched. Tunable via CLI flag.
# A pure absolute-age check (e.g., "doc unchanged for 90 days") is too noisy —
# stable architecture docs get flagged even when their referenced code is
# also stable. Relative-to-code-churn surfaces the docs that are *actually*
# at risk of being out of date.
STALE_CODE_COMMITS_THRESHOLD = 20
STUB_LINE_THRESHOLD = 3
# Regex for markdown links: [text](path) — ignores URLs and anchors
MD_LINK_PATTERN = re.compile(r"\[([^\]]*)\]\(([^)]+)\)")
# --- Data Structures ---
@dataclass
class Finding:
category: str # broken_link, orphan, missing_structure, stale, stub
severity: str # P0, P1, P2, P3, P4
file_path: str
description: str
details: dict = field(default_factory=dict)
# --- Utilities ---
def get_project_root(root_override=None):
"""Determine project root from git or override."""
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):
"""Find all markdown files under docs/, manual/, and README.md."""
files = []
for search_root in [root / DOCS_ROOT, root / MANUAL_ROOT]:
if search_root.exists():
files.extend(search_root.rglob("*.md"))
readme = root / README
if readme.exists():
files.append(readme)
return sorted(files)
def extract_md_links(filepath):
"""Extract markdown links from a file. Returns list of (line_num, text, target)."""
links = []
try:
content = filepath.read_text(encoding="utf-8", errors="replace")
except OSError:
return links
for i, line in enumerate(content.splitlines(), 1):
for match in MD_LINK_PATTERN.finditer(line):
target = match.group(2)
# Skip URLs, anchors, and mailto
if target.startswith(("http://", "https://", "#", "mailto:")):
continue
# Strip anchor from local paths
target = target.split("#")[0]
if target:
links.append((i, match.group(1), target))
return links
def git_last_modified_ts(filepath, root):
"""Get last git modification timestamp (Unix epoch) of a file."""
try:
result = subprocess.run(
["git", "log", "-1", "--format=%ct", "--", str(filepath.relative_to(root))],
capture_output=True,
text=True,
cwd=root,
check=True,
)
timestamp = result.stdout.strip()
if not timestamp:
return None
return int(timestamp)
except (subprocess.CalledProcessError, FileNotFoundError, ValueError):
return None
def git_last_modified_days(filepath, root):
"""Get days since last git modification (kept for backward compat)."""
ts = git_last_modified_ts(filepath, root)
if ts is None:
return None
return int((time.time() - ts) / 86400)
def code_commits_since(timestamp, root):
"""Count commits to non-markdown files since the given Unix timestamp.
Approximates whether the project's code has churned since a doc was last
touched. Excludes doc directories and markdown files so the count
reflects code activity, not co-evolving doc commits.
"""
if timestamp is None:
return 0
try:
result = subprocess.run(
[
"git", "log",
f"--since=@{timestamp}",
"--format=%H",
"--",
".",
":(exclude)docs",
":(exclude)manual",
":(exclude)*.md",
":(exclude)**/*.md",
],
capture_output=True,
text=True,
cwd=root,
check=True,
)
if not result.stdout.strip():
return 0
return len(result.stdout.strip().splitlines())
except (subprocess.CalledProcessError, FileNotFoundError):
return 0
def file_line_count(filepath):
"""Count non-empty lines in a file."""
try:
content = filepath.read_text(encoding="utf-8", errors="replace")
return sum(1 for line in content.splitlines() if line.strip())
except OSError:
return 0
# --- Checks ---
def check_broken_links(root, md_files):
"""Find markdown links pointing to nonexistent files."""
findings = []
for md_file in md_files:
links = extract_md_links(md_file)
for line_num, text, target in links:
# Resolve relative to the file's directory
resolved = (md_file.parent / target).resolve()
if not resolved.exists():
rel_path = str(md_file.relative_to(root))
findings.append(
Finding(
category="broken_link",
severity="P1",
file_path=rel_path,
description=f"Link to '{target}' on line {line_num} points to missing file",
details={"line": line_num, "link_text": text, "target": target},
)
)
return findings
def check_orphan_docs(root, md_files):
"""Find docs not linked from any other doc."""
# Build set of all link targets (resolved to absolute paths)
linked_targets = set()
for md_file in md_files:
links = extract_md_links(md_file)
for _, _, target in links:
resolved = (md_file.parent / target).resolve()
linked_targets.add(resolved)
# Check which docs are never targeted
findings = []
for md_file in md_files:
# Skip README.md — it's the root, not expected to be linked to
if md_file == root / README:
continue
# Skip index files — they're navigation, not content targets
if md_file.name.lower() in ("index.md", "readme.md"):
continue
if md_file.resolve() not in linked_targets:
rel_path = str(md_file.relative_to(root))
findings.append(
Finding(
category="orphan",
severity="P3",
file_path=rel_path,
description="File is not linked from any other document",
)
)
return findings
def check_missing_structure(root):
"""Check for missing required folders."""
findings = []
docs_dir = root / DOCS_ROOT
manual_dir = root / MANUAL_ROOT
# Check docs/ subfolders
if docs_dir.exists():
for folder in REQUIRED_DOCS_FOLDERS:
if not (docs_dir / folder).exists():
findings.append(
Finding(
category="missing_structure",
severity="P3",
file_path=f"docs/{folder}/",
description=f"Required folder docs/{folder}/ does not exist",
)
)
else:
findings.append(
Finding(
category="missing_structure",
severity="P2",
file_path="docs/",
description="docs/ directory does not exist",
)
)
# Check manual/ subfolders
if manual_dir.exists():
for folder in REQUIRED_MANUAL_FOLDERS:
if not (manual_dir / folder).exists():
findings.append(
Finding(
category="missing_structure",
severity="P3",
file_path=f"manual/{folder}/",
description=f"Required folder manual/{folder}/ does not exist",
)
)
else:
findings.append(
Finding(
category="missing_structure",
severity="P2",
file_path="manual/",
description="manual/ directory does not exist",
)
)
return findings
def check_stale_docs(root, md_files):
"""Find docs likely stale relative to project code churn.
A doc is flagged stale if the project's non-doc subtree has accumulated
>= STALE_CODE_COMMITS_THRESHOLD commits since the doc was last touched.
This is strictly better than absolute-age checks: a stable architecture
doc on a stable subtree won't get flagged just because it's old, while
a doc on a heavily-churning subtree gets flagged even if recent.
"""
findings = []
for md_file in md_files:
doc_ts = git_last_modified_ts(md_file, root)
if doc_ts is None:
continue
code_commits = code_commits_since(doc_ts, root)
if code_commits >= STALE_CODE_COMMITS_THRESHOLD:
days = int((time.time() - doc_ts) / 86400)
rel_path = str(md_file.relative_to(root))
findings.append(
Finding(
category="stale",
severity="P4",
file_path=rel_path,
description=(
f"{code_commits} code commits since doc last touched "
f"({days}d ago) — verify doc still matches current code"
),
details={
"days_since_modified": days,
"code_commits_since": code_commits,
},
)
)
return findings
def check_stub_files(root, md_files):
"""Find files with very little content."""
findings = []
for md_file in md_files:
line_count = file_line_count(md_file)
if line_count <= STUB_LINE_THRESHOLD:
rel_path = str(md_file.relative_to(root))
findings.append(
Finding(
category="stub",
severity="P3",
file_path=rel_path,
description=f"File has only {line_count} non-empty line(s) — likely a stub",
details={"line_count": line_count},
)
)
return findings
# --- Report Generation ---
def generate_markdown_report(findings, root):
"""Generate a markdown-formatted audit report."""
lines = [
"# Documentation Audit Report",
"",
f"**Project root:** `{root}`",
f"**Total findings:** {len(findings)}",
"",
]
if not findings:
lines.append("No issues found. Documentation is in good shape.")
return "\n".join(lines)
# Summary by category
by_category = defaultdict(list)
for f in findings:
by_category[f.category].append(f)
lines.append("## Summary")
lines.append("")
lines.append("| Category | Count |")
lines.append("|----------|-------|")
for cat in ["broken_link", "orphan", "missing_structure", "stale", "stub"]:
if cat in by_category:
lines.append(f"| {cat} | {len(by_category[cat])} |")
lines.append("")
# Summary by severity
by_severity = defaultdict(list)
for f in findings:
by_severity[f.severity].append(f)
lines.append("## By Severity")
lines.append("")
for sev in ["P0", "P1", "P2", "P3", "P4"]:
if sev not in by_severity:
continue
lines.append(f"### {sev}")
lines.append("")
lines.append("| File | Category | Description |")
lines.append("|------|----------|-------------|")
for f in by_severity[sev]:
lines.append(f"| `{f.file_path}` | {f.category} | {f.description} |")
lines.append("")
return "\n".join(lines)
def generate_json_report(findings, root):
"""Generate a JSON-formatted audit report."""
report = {
"project_root": str(root),
"total_findings": len(findings),
"findings": [asdict(f) for f in findings],
}
return json.dumps(report, indent=2)
# --- Main ---
def main():
parser = argparse.ArgumentParser(description="Documentation audit tool")
parser.add_argument("--json", action="store_true", help="Output as JSON")
parser.add_argument("--root", type=str, default=None, help="Project root path")
args = parser.parse_args()
root = get_project_root(args.root)
md_files = find_markdown_files(root)
print(f"Scanning {len(md_files)} markdown files in {root}...", file=sys.stderr)
# Run all checks
findings = []
findings.extend(check_broken_links(root, md_files))
findings.extend(check_orphan_docs(root, md_files))
findings.extend(check_missing_structure(root))
findings.extend(check_stale_docs(root, md_files))
findings.extend(check_stub_files(root, md_files))
# Sort by severity then category
severity_order = {"P0": 0, "P1": 1, "P2": 2, "P3": 3, "P4": 4}
findings.sort(key=lambda f: (severity_order.get(f.severity, 9), f.category, f.file_path))
# Output
if args.json:
print(generate_json_report(findings, root))
else:
print(generate_markdown_report(findings, root))
# Exit code: non-zero if P0 or P1 findings exist
has_critical = any(f.severity in ("P0", "P1") for f in findings)
sys.exit(1 if has_critical else 0)
if __name__ == "__main__":
main()