Documentation Linter Guide
Date: 2026-01-17
Status: ✅ Active
Overview
The documentation linter automatically checks that all markdown (.md) files are:
- Organized - Located in allowed directories per documentation structure guidelines
- Non-duplicated - Exist in only one location (single source of truth principle)
This prevents rogue documentation files from being scattered throughout the repository and catches unintended copies that create maintenance problems.
See .github/copilot-instructions.md for documentation organization guidelines.
Quick Start
Run the linter
# Check documentation organization
npm run lint:docs
# Strict mode (fails if issues found)
npm run lint:docs:strict
# Integrated with main linter
npm run lintWhat It Checks
The documentation linter validates that all .md files satisfy two requirements:
1. Location Check
All files must be in allowed locations:
✅ Root-level files (allowed)
README.md- Main project READMECHANGELOG.md- Root changelog (synced withdocs/CHANGELOG.md)
✅ Directory patterns (allowed)
docs/**/*.md- Primary documentation location (all user-facing docs).github/**/*.md- GitHub-specific documentation (workflows, actions, etc.)discord-bot/**/*.md- Discord bot documentation (if added)functions/**/*.md- Firebase Cloud Functions documentation (if added)
Script documentation should live in docs/engineering/guides/SCRIPTS_GUIDE.md (not in scripts/README.md).
❌ Not allowed
./*.md(except README.md and CHANGELOG.md)- Markdown files scattered in arbitrary directories
- Feature docs outside
docs/features/ - Engineering docs outside
docs/engineering/
2. Duplicate Files Check
All markdown files must be unique by filename - no file should exist in multiple locations with the same name. This enforces the "single source of truth" principle.
Examples of duplicates found:
README.mdin multiple directories → Keep only in appropriate locationCHANGELOG.mdin both root anddocs/→ Consolidate to one locationIMPLEMENTATION_SUMMARY.mdin 3+ locations → Consolidate and link to single source
When duplicates are found:
- Linter reports all locations where the file appears
- You must identify which is the authoritative version
- Delete duplicates and update cross-references
- Use links instead of copying content
Documentation Organization
When creating new documentation, follow these rules:
Feature Documentation
docs/features/{feature-name}/
├── QUICK_START.md # 5-minute quick start
├── {FEATURE_NAME}.md # Complete spec
├── IMPLEMENTATION.md # Developer guide (optional)
└── TESTING_GUIDE.md # QA procedures (optional)Examples: docs/features/wave/, docs/features/lantern-hub/, docs/features/profile/
Engineering Documentation
docs/engineering/{subdomain}/
└── {TOPIC}_{TYPE}.mdSubdomains:
architecture/- System design, tech stack, patterns, APIsdeployment/- Deployment guides, CI/CD, infrastructuresecurity/- Encryption, security architecture, incident responseguides/- Onboarding, troubleshooting, utility setuptesting/- Testing strategies, debugging guidesmobile/- Mobile-specific optimizations
Business & Governance
docs/business/
├── BUSINESS.md
├── PILOT_STRATEGY.md
├── COFOUNDER_FEEDBACK_POA.md
└── ...
docs/governance/
├── GOVERNANCE.md
├── GOVERNANCE_QUICK_REFERENCE.md
├── IMMUTABLE_RIGHTS.md
└── ...Handling Duplicates
When the linter detects duplicate filenames:
1. Identify the Source of Truth
The linter output shows all locations where a file appears:
📄 IMPLEMENTATION_SUMMARY.md
• docs/archive/worklog-historical/IMPLEMENTATION_SUMMARY.md
• docs/engineering/github/workflows/ai-triage/IMPLEMENTATION_SUMMARY.md
• docs/engineering/github/workflows/discord/IMPLEMENTATION_SUMMARY.mdDetermine which is the canonical/authoritative version.
2. Update Cross-References
If other files link to duplicates, update them to point to the source:
Before:
[See implementation](../../engineering/github/workflows/discord/IMPLEMENTATION_SUMMARY.md)After:
[See implementation](../../engineering/github/workflows/IMPLEMENTATION_SUMMARY.md)3. Delete Duplicate Files
rm docs/archive/worklog-historical/IMPLEMENTATION_SUMMARY.md
rm docs/engineering/github/workflows/discord/IMPLEMENTATION_SUMMARY.md4. Common Duplicate Patterns & Solutions
README.md in multiple directories
- Keep: Module-level READMEs (e.g.,
discord-bot/README.md) - Remove: Duplicates in subdirectories
- Rule: One README per module, none in subdirectories; scripts are documented in
docs/engineering/guides/SCRIPTS_GUIDE.md
CHANGELOG.md (Root vs docs/)
- Keep:
docs/CHANGELOG.mdas primary - Sync/Remove: Root version should mirror docs/ or be removed
- Best Practice: Single source in
docs/CHANGELOG.md
IMPLEMENTATION_SUMMARY.md
- Issue: Multiple copies document same thing differently
- Solution: Keep in one location (e.g., per feature area)
- Link: From other locations, use relative links instead
QUICK_START.md & DEPLOYMENT.md
- Rule: One per feature/module in appropriate
docs/subdirectory - Link: Use cross-references between docs instead of copying
Configuration
The linter is configured using .docs-linter-config.js which controls all linting behavior.
Main Configuration File
File: .docs-linter-config.js
module.exports = {
// Root-level files that are allowed
rootFiles: ['README.md'],
// Directory patterns where markdown is allowed
allowedPaths: [
'docs/**/*.md',
'.github/**/*.md',
// ... more patterns
],
// Directories to completely ignore
excludePatterns: [
'node_modules/**/*.md',
'.git/**/*.md',
'dist/**/*.md',
// ... more patterns
],
// Files allowed to exist in multiple locations (duplicate exclusions)
duplicateExclusions: [
'README.md', // Per-module README files are expected
'CHANGELOG.md', // Per-directory changelogs are acceptable
'.gitignore',
'.prettierignore',
'.eslintignore',
],
// Error reporting modes
strictMode: false,
showSuggestions: true,
};Script Implementation
File: scripts/lint-docs.js
- Loads configuration from
.docs-linter-config.js - Applies exclusions when checking for duplicates
- Falls back to safe defaults if config is missing
- Provides helpful error messages with resolution steps
Modifying Configuration
To change allowed locations or duplicate exclusions:
Edit
.docs-linter-config.js- Add/remove paths from
allowedPaths - Add/remove filenames from
duplicateExclusions - Update exclusion patterns as needed
- Add/remove paths from
Test changes
bashnpm run lint:docsDocument in changelog
bashnpm run changelog:consolidate # or update docs/CHANGELOG.md
Exclusion Lists
Directory Exclusions
These directories are automatically ignored by the linter:
node_modules/**/*.md- Dependencies.git/**/*.md- Git historydist/**/*.md- Build artifactsbuild/**/*.md- Build output.next/**/*.md- Next.js artifacts.github/workflows/test-data/**/*.md- Test data
Duplicate Exclusions
These files are allowed to exist in multiple locations:
README.md- Per-module documentationCHANGELOG.md- Per-directory change logs.gitignore- Per-directory git ignore rules.prettierignore- Per-directory prettier config.eslintignore- Per-directory eslint config
Adding New Allowed Paths
If you need to allow markdown files in a new location (e.g., for a new module):
Edit
.docs-linter-config.js:javascriptallowedPaths: [ 'docs/**/*.md', '.github/**/*.md', 'new-module/**/*.md', // ← Add here ]Test the change
bashnpm run lint:docsDocument in changelog
markdown- Add support for new-module documentation in linter
Adding Duplicate Exclusions
If you have a file that should exist in multiple locations:
Edit
.docs-linter-config.js:javascriptduplicateExclusions: [ 'README.md', 'CHANGELOG.md', 'MY_FILE.md', // ← Add here ]Test the change
bashnpm run lint:docsDocument why this file legitimately has duplicates
Integration with Main Linter
The documentation linter is automatically included when you run:
npm run lintIt runs before ESLint and reports organization issues alongside code linting issues.
Exit codes:
0- All documentation is properly organized and has no duplicates1- Issues found (if using--strictflag)
Exit Codes & Modes
Warning Mode (default)
npm run lint:docs
# Always exits with 0, even if issues found
# Use this for informational checksStrict Mode
npm run lint:docs:strict
# Exits with 1 if issues found
# Use this in CI/CD pipelines to enforce complianceExample Output
✅ Success
✅ All markdown files are in the correct locations!
✅ No duplicate markdown files found!❌ Organization Issue
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
⚠️ DOCUMENTATION ORGANIZATION ISSUE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Found 1 markdown file(s) outside allowed locations:
❌ API_GUIDE.md
📍 ALLOWED LOCATIONS:
Root-level:
• README.md
• CHANGELOG.md
Path patterns:
• docs/**/*.md
• .github/**/*.md
• discord-bot/**/*.md
• functions/**/*.md
📚 ORGANIZATION RULES:
• All user-facing documentation → docs/
• Feature docs → docs/features/{feature-name}/
• Engineering docs → docs/engineering/{subdomain}/
• Business docs → docs/business/
• Governance docs → docs/governance/
• GitHub workflow docs → .github/workflows/docs/
• Script documentation → docs/engineering/guides/SCRIPTS_GUIDE.md (no README in scripts/)
See docs/DOCS_INDEX.md and .github/copilot-instructions.md for details.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━❌ Duplicate Files
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
⚠️ DUPLICATE MARKDOWN FILES DETECTED
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Documentation should exist in only one location (single source of truth):
📄 README.md
• .github/workflows/README.md
• README.md
• discord-bot/README.md
📄 IMPLEMENTATION_SUMMARY.md
• docs/engineering/github/workflows/ai-triage/IMPLEMENTATION_SUMMARY.md
• docs/engineering/github/workflows/discord/IMPLEMENTATION_SUMMARY.md
• docs/features/frens/IMPLEMENTATION_SUMMARY.md
🎯 RESOLUTION:
1. Identify which copy is the "source of truth"
2. Move or delete the duplicate copies
3. Update links in other files to point to the single source
4. Consider using VitePress with proper linking instead of copying
📚 BEST PRACTICES:
✓ Keep README.md files in module/package directories
✓ Keep detailed docs in appropriate docs/ subdirectories
✓ Link between docs using relative paths (no copying)
✓ Use VitePress for cross-referencing between docs
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━Fix Examples
Move documentation into docs/
Before:
lantern_app/
├── API_GUIDE.md ❌ Not allowed
├── INTEGRATION.md ❌ Not allowed
└── docs/
└── (proper location)After:
lantern_app/
└── docs/
└── engineering/
├── API_GUIDE.md ✅ Allowed
└── INTEGRATION.md ✅ AllowedRelocate feature documentation
Before:
lantern_app/
├── WAVE_FEATURES.md ❌ Not allowedAfter:
lantern_app/
└── docs/
└── features/
└── wave/
└── WAVE_FEATURES.md ✅ Allowed (rename to feature-appropriate name)Troubleshooting
"Documentation file X is not allowed"
Solution: Move the file to the appropriate docs/ subdirectory per the organization rules above.
"I need markdown files in a new location"
Solution: Update scripts/lint-docs.js with the new pattern and document it in docs/CHANGELOG.md.
Linter is too strict
Solution: Use warning mode for informational checks:
npm run lint:docs # Warning mode (doesn't fail)
npm run lint:docs:strict # Strict mode (fails on issues)Related Documentation
- Documentation Guidelines - Full documentation standards
- DOCS_INDEX.md - Complete documentation index
- CHANGELOG.md - Project changelog
- CONTRIBUTING.md - Contribution guidelines
See Also
- Changelog Workflow - How to maintain CHANGELOG.md
- Code Linting Guide - Main linting practices (if exists)