{"id":51417784,"url":"https://github.com/didrod205/docsanity","last_synced_at":"2026-07-04T21:30:40.086Z","repository":{"id":368145589,"uuid":"1258832337","full_name":"didrod205/docsanity","owner":"didrod205","description":"One command for docs-site health: broken links, orphan pages, duplicate titles/descriptions, readability \u0026 Markdown structure — unified into one score. Local, deterministic, no API key. Docusaurus/Astro/Nextra/Hugo.","archived":false,"fork":false,"pushed_at":"2026-06-29T07:24:10.000Z","size":53,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-29T09:17:11.911Z","etag":null,"topics":["astro","broken-link-checker","cli","docs","docs-linter","documentation","docusaurus","link-checker","markdown","mdx","nextra","orphan-pages","readability","seo","static-site-generator","typescript"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/didrod205.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"didrod205"}},"created_at":"2026-06-04T00:53:39.000Z","updated_at":"2026-06-29T07:24:14.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/didrod205/docsanity","commit_stats":null,"previous_names":["didrod205/docsanity"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/didrod205/docsanity","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/didrod205%2Fdocsanity","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/didrod205%2Fdocsanity/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/didrod205%2Fdocsanity/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/didrod205%2Fdocsanity/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/didrod205","download_url":"https://codeload.github.com/didrod205/docsanity/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/didrod205%2Fdocsanity/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35136712,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-04T02:00:05.987Z","response_time":113,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["astro","broken-link-checker","cli","docs","docs-linter","documentation","docusaurus","link-checker","markdown","mdx","nextra","orphan-pages","readability","seo","static-site-generator","typescript"],"created_at":"2026-07-04T21:30:39.297Z","updated_at":"2026-07-04T21:30:40.065Z","avatar_url":"https://github.com/didrod205.png","language":"TypeScript","funding_links":["https://github.com/sponsors/didrod205"],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# 🩺 docsanity\n\n### One command for docs-site health — broken links, orphan pages, SEO, readability \u0026 structure in a single score.\n\n[![npm version](https://img.shields.io/npm/v/docsanity.svg?color=success)](https://www.npmjs.com/package/docsanity)\n[![CI](https://github.com/didrod205/docsanity/actions/workflows/ci.yml/badge.svg)](https://github.com/didrod205/docsanity/actions/workflows/ci.yml)\n[![types](https://img.shields.io/npm/types/docsanity.svg)](https://www.npmjs.com/package/docsanity)\n[![license](https://img.shields.io/npm/l/docsanity.svg)](./LICENSE)\n\n\u003c/div\u003e\n\nYour docs site looks fine — until a reader hits a 404 from a link you moved, lands\non a page Google shows with a *duplicate* title, or bounces off a wall of\ngrad-school prose. These problems live **between** files (which page links where,\nwhich titles repeat, what's orphaned), so single-file linters miss them and no one\nnotices until it ships.\n\n**docsanity audits a whole docs directory in one pass** and unifies four health\ndimensions into one score and one report — **100% locally, no API key:**\n\n- 🔗 **Links \u0026 references** — broken relative links, dead `#anchors`, missing\n  images, and **orphan pages** nothing links to.\n- 🔎 **SEO \u0026 frontmatter** — missing/oversized `title` \u0026 `description`, plus\n  **site-wide duplicate titles and descriptions** across every page.\n- 📖 **Readability** — a per-page reading grade; flags pages that read too hard.\n- ♿ **Structure \u0026 a11y** — single H1, no skipped heading levels, image alt text,\n  fenced code blocks that declare a language.\n\n```bash\nnpx docsanity scan ./docs\n```\n\n```\nbroken.md  50/100 (F)\n  ✗ L5     Link target not found: \"./nope.md\"            links.broken-file-link\n  ✗ L5     Anchor \"#does-not-exist\" not found in index.md links.broken-cross-anchor\n  ⚠ L7     Image has no alt text: ./diagram.png           structure.image-alt\norphan.md  88/100 (B)\n  ⚠        Orphan page — nothing links here               links.orphan-document\n  ⚠        Duplicate title — also used by 1 other page(s) seo.duplicate-title\n\nOverall  82/100 (B) · 5 page(s), 4 error(s), 10 warning(s)\n```\n\n---\n\n## Why a suite (and not four separate tools)?\n\nThe valuable checks need the **whole-site view**, which only a unified pass can\ngive you:\n\n- **Orphan pages** and **broken cross-file anchors** require the full page graph.\n- **Duplicate titles/descriptions** require comparing every page to every other.\n- A single **score + grade** you can gate in CI means one number to watch, not four\n  tools to wire up and reconcile.\n\ndocsanity builds the page graph once and runs every dimension over it, then folds\neverything into a per-page and per-dimension report. Under the hood it leverages\nthe focused engines [`linklint`](https://www.npmjs.com/package/@didrod2539/linklint)\n(link graph) and [`readlevel`](https://www.npmjs.com/package/@didrod2539/readlevel)\n(readability), and adds the SEO-frontmatter and structure checks that tie them into\na docs-site audit.\n\nWhy not just ask an LLM to \"check my docs\"? It can't hold 300 interlinked files in\ncontext, it can't run in CI deterministically, and \"did this link break\" is a graph\nlookup, not a guess.\n\n## Install\n\n```bash\n# run it now, no install\nnpx docsanity scan ./docs\n\n# or add it\nnpm install -g docsanity      # global CLI\nnpm install -D docsanity      # CI dependency\n```\n\nNode ≥ 18. Works with Docusaurus, Astro, Nextra, Hugo, Jekyll, VitePress — any\nMarkdown/MDX docs tree.\n\n## Quick start\n\n```bash\ndocsanity scan ./docs                       # audit a folder\ndocsanity scan ./docs --min-score 85        # CI gate: fail under 85\ndocsanity scan ./docs --md docs-report.md   # write a Markdown report\ndocsanity scan ./docs --disable readability # turn a dimension off\ndocsanity init                              # write docsanity.config.json\n```\n\nSee [`examples/sample-report.md`](./examples/sample-report.md) and\n[`examples/sample-report.json`](./examples/sample-report.json) for full reports.\n\n## Real scenarios\n\n**1. CI gate for your docs.** A PR that moves a page and forgets to fix the inbound\nlinks — or ships a new page with no description — fails the build:\n\n```yaml\n# .github/workflows/docs.yml\n- run: npx docsanity scan ./docs --min-score 90 --md docs-report.md\n```\n\n**2. Pre-launch audit of an inherited site.** Point it at a docs tree you didn't\nwrite and instantly see the orphans, duplicate titles and dead anchors that\naccumulated over time — one report, ranked by page score.\n\n**3. Keep AI answer engines happy.** Clear, well-structured, well-linked pages are\nwhat answer engines quote. Fix the readability and structure findings to make your\ndocs the source they cite.\n\n## What each dimension checks\n\n| Dimension | Checks |\n| --------- | ------ |\n| **Links \u0026 references** | broken relative links, dead `#anchors`, broken cross-file anchors, missing images, undefined references, **orphan pages** |\n| **SEO \u0026 frontmatter** | required `title`/`description`, length windows, H1-as-title fallback, **site-wide duplicate titles \u0026 descriptions** |\n| **Readability** | Flesch–Kincaid / Gunning Fog / SMOG average grade per page, flagged against a configurable ceiling |\n| **Structure \u0026 a11y** | single H1, no skipped heading levels, image alt text, code-fence language tags |\n\nScoring is transparent: each finding is a weighted error / warning / info; pages\nroll up to a 0–100 score and an A–F grade, averaged into the overall.\n\n## Configuration\n\n`docsanity init` writes `docsanity.config.json`:\n\n```jsonc\n{\n  \"extensions\": [\".md\", \".mdx\", \".markdown\"],\n  \"requireFrontmatter\": [\"title\", \"description\"],\n  \"descriptionMin\": 50,\n  \"descriptionMax\": 160,\n  \"titleMax\": 60,\n  \"maxGrade\": 14,\n  \"minWordsForReadability\": 80,\n  \"disable\": [],            // e.g. [\"readability\"]\n  \"ignore\": [],             // rule ids, e.g. [\"structure.code-language\"]\n  \"minScore\": 0             // CI gate threshold\n}\n```\n\n## Library API\n\n```ts\nimport { analyzeDocs } from \"docsanity\";\n\nconst report = analyzeDocs(root, inputs, config, {\n  version: \"1.0.0\",\n  generatedAt: new Date().toISOString(),\n});\nfor (const page of report.pages) {\n  console.log(page.path, page.score, page.findings.length);\n}\n```\n\nAlso exported: `parsePage`, `parseFrontmatter`, `parseMarkdown`, `seoChecks`,\n`structureChecks`, `readabilityCheck`, `linkFindings`, `DEFAULT_CONFIG`, and types.\n\n## Roadmap\n\n- More frontmatter conventions (Hugo/Jekyll specifics, custom required schemas).\n- HTML docs input (not just Markdown/MDX).\n- Per-rule severity overrides and inline ignore comments.\n- `--baseline` to fail only on *new* issues vs a committed snapshot.\n- A web playground (drop a zip of docs, get the report — nothing uploaded).\n\n## 💖 Sponsor\n\ndocsanity is free and MIT-licensed, built and maintained in spare time. If it\ncaught a broken link before your readers did, please consider supporting it:\n\n- ⭐ **Star this repo** — the simplest free way to help others find it.\n- 🍋 **[Sponsor via Lemon Squeezy](https://elab-studio.lemonsqueezy.com/checkout/buy/5d059b89-51d0-456b-b33a-ed56994f7010)** — one-time or recurring.\n\n## License\n\n[MIT](./LICENSE) © docsanity contributors\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdidrod205%2Fdocsanity","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdidrod205%2Fdocsanity","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdidrod205%2Fdocsanity/lists"}