|
| 1 | +# afdocs |
| 2 | + |
| 3 | +Test your documentation site against the [Agent-Friendly Documentation Spec](https://agentdocsspec.com). |
| 4 | + |
| 5 | +Agents don't use docs like humans. They hit truncation limits, get walls of CSS instead of content, can't follow cross-host redirects, and don't know about quality-of-life improvements like `llms.txt` or `.md` docs pages that would make life swell. Maybe this is because the industry has lacked guidance - until now. |
| 6 | + |
| 7 | +afdocs runs 19 checks across 7 categories to evaluate how well your docs serve agent consumers. |
| 8 | + |
| 9 | +## Quick start |
| 10 | + |
| 11 | +```bash |
| 12 | +npx afdocs check https://docs.example.com |
| 13 | +``` |
| 14 | + |
| 15 | +Example output: |
| 16 | + |
| 17 | +``` |
| 18 | +Agent-Friendly Docs Check: https://react.dev |
| 19 | +
|
| 20 | +llms-txt |
| 21 | + ✓ llms-txt-exists: llms.txt found at 1 location(s) |
| 22 | + ✓ llms-txt-valid: llms.txt follows the proposed structure |
| 23 | + ✓ llms-txt-size: llms.txt is 14,347 characters (under 50,000 threshold) |
| 24 | + ✓ llms-txt-links-resolve: All 50 tested links resolve (177 total links) |
| 25 | + ✓ llms-txt-links-markdown: 50/50 links point to markdown content (100%) |
| 26 | +
|
| 27 | +Summary |
| 28 | + 5 passed, 14 skipped (19 total) |
| 29 | +``` |
| 30 | + |
| 31 | +## Install |
| 32 | + |
| 33 | +```bash |
| 34 | +npm install afdocs |
| 35 | +``` |
| 36 | + |
| 37 | +## CLI usage |
| 38 | + |
| 39 | +```bash |
| 40 | +# Run all checks |
| 41 | +afdocs check https://docs.example.com |
| 42 | + |
| 43 | +# Run specific checks |
| 44 | +afdocs check https://docs.example.com --checks llms-txt-exists,llms-txt-valid,llms-txt-size |
| 45 | + |
| 46 | +# JSON output |
| 47 | +afdocs check https://docs.example.com --format json |
| 48 | + |
| 49 | +# Adjust thresholds |
| 50 | +afdocs check https://docs.example.com --pass-threshold 30000 --fail-threshold 80000 |
| 51 | +``` |
| 52 | + |
| 53 | +### Options |
| 54 | + |
| 55 | +| Option | Default | Description | |
| 56 | +|--------|---------|-------------| |
| 57 | +| `--format <format>` | `text` | Output format: `text` or `json` | |
| 58 | +| `--checks <ids>` | all | Comma-separated list of check IDs | |
| 59 | +| `--max-concurrency <n>` | `3` | Maximum concurrent HTTP requests | |
| 60 | +| `--request-delay <ms>` | `200` | Delay between requests | |
| 61 | +| `--max-links <n>` | `50` | Maximum links to test in link checks | |
| 62 | +| `--pass-threshold <n>` | `50000` | Size pass threshold (characters) | |
| 63 | +| `--fail-threshold <n>` | `100000` | Size fail threshold (characters) | |
| 64 | + |
| 65 | +### Exit codes |
| 66 | + |
| 67 | +- `0` if all checks pass or warn |
| 68 | +- `1` if any check fails |
| 69 | + |
| 70 | +## Programmatic API |
| 71 | + |
| 72 | +```ts |
| 73 | +import { runChecks, createContext, getCheck } from 'afdocs'; |
| 74 | + |
| 75 | +// Run all checks |
| 76 | +const report = await runChecks('https://docs.example.com'); |
| 77 | + |
| 78 | +// Run a single check |
| 79 | +const ctx = createContext('https://docs.example.com'); |
| 80 | +const check = getCheck('llms-txt-exists')!; |
| 81 | +const result = await check.run(ctx); |
| 82 | +``` |
| 83 | + |
| 84 | +## Test helpers |
| 85 | + |
| 86 | +afdocs includes vitest helpers so you can add agent-friendliness checks to your docs site's test suite. |
| 87 | + |
| 88 | +### Config-driven |
| 89 | + |
| 90 | +Create `agent-docs.config.yml`: |
| 91 | + |
| 92 | +```yaml |
| 93 | +url: https://docs.example.com |
| 94 | +checks: |
| 95 | + - llms-txt-exists |
| 96 | + - llms-txt-valid |
| 97 | + - llms-txt-size |
| 98 | +``` |
| 99 | +
|
| 100 | +Then in your test file: |
| 101 | +
|
| 102 | +```ts |
| 103 | +import { describeAgentDocs } from 'afdocs/helpers'; |
| 104 | + |
| 105 | +describeAgentDocs(); |
| 106 | +``` |
| 107 | + |
| 108 | +This reads the config and generates one test assertion covering all specified checks. |
| 109 | + |
| 110 | +### Direct imports |
| 111 | + |
| 112 | +```ts |
| 113 | +import { createContext, getCheck } from 'afdocs'; |
| 114 | +import { describe, it, expect } from 'vitest'; |
| 115 | + |
| 116 | +describe('agent-friendliness', () => { |
| 117 | + it('has a valid llms.txt', async () => { |
| 118 | + const ctx = createContext('https://docs.example.com'); |
| 119 | + const check = getCheck('llms-txt-exists')!; |
| 120 | + const result = await check.run(ctx); |
| 121 | + expect(result.status).toBe('pass'); |
| 122 | + }); |
| 123 | +}); |
| 124 | +``` |
| 125 | + |
| 126 | +## Checks |
| 127 | + |
| 128 | +19 checks across 7 categories. Checks marked with * are not yet implemented and will return `skip`. |
| 129 | + |
| 130 | +### Category 1: llms.txt |
| 131 | + |
| 132 | +| Check | Description | |
| 133 | +|-------|-------------| |
| 134 | +| `llms-txt-exists` | Whether `llms.txt` is discoverable at candidate locations | |
| 135 | +| `llms-txt-valid` | Whether `llms.txt` follows the llmstxt.org structure | |
| 136 | +| `llms-txt-size` | Whether `llms.txt` fits within agent truncation limits | |
| 137 | +| `llms-txt-links-resolve` | Whether URLs in `llms.txt` return 200 | |
| 138 | +| `llms-txt-links-markdown` | Whether URLs in `llms.txt` point to markdown content | |
| 139 | + |
| 140 | +### Category 2: Markdown Availability |
| 141 | + |
| 142 | +| Check | Description | |
| 143 | +|-------|-------------| |
| 144 | +| `markdown-url-support` * | Whether `.md` URL variants return markdown | |
| 145 | +| `content-negotiation` * | Whether the server honors `Accept: text/markdown` | |
| 146 | + |
| 147 | +### Category 3: Page Size and Truncation Risk |
| 148 | + |
| 149 | +| Check | Description | |
| 150 | +|-------|-------------| |
| 151 | +| `page-size-markdown` * | Character count when served as markdown | |
| 152 | +| `page-size-html` * | Character count of HTML and post-conversion size | |
| 153 | +| `content-start-position` * | How far into the response actual content begins | |
| 154 | + |
| 155 | +### Category 4: Content Structure |
| 156 | + |
| 157 | +| Check | Description | |
| 158 | +|-------|-------------| |
| 159 | +| `tabbed-content-serialization` * | Whether tabbed content creates oversized output | |
| 160 | +| `section-header-quality` * | Whether headers in tabbed sections include context | |
| 161 | +| `markdown-code-fence-validity` * | Whether markdown has unclosed code fences | |
| 162 | + |
| 163 | +### Category 5: URL Stability and Redirects |
| 164 | + |
| 165 | +| Check | Description | |
| 166 | +|-------|-------------| |
| 167 | +| `http-status-codes` * | Whether error pages return correct status codes | |
| 168 | +| `redirect-behavior` * | Whether redirects are same-host HTTP redirects | |
| 169 | + |
| 170 | +### Category 6: Agent Discoverability Directives |
| 171 | + |
| 172 | +| Check | Description | |
| 173 | +|-------|-------------| |
| 174 | +| `llms-txt-directive` * | Whether pages include a directive pointing to `llms.txt` | |
| 175 | + |
| 176 | +### Category 7: Observability and Content Health |
| 177 | + |
| 178 | +| Check | Description | |
| 179 | +|-------|-------------| |
| 180 | +| `llms-txt-freshness` * | Whether `llms.txt` reflects current site state | |
| 181 | +| `markdown-content-parity` * | Whether markdown and HTML versions match | |
| 182 | +| `cache-header-hygiene` * | Whether cache headers allow timely updates | |
| 183 | + |
| 184 | +## Check dependencies |
| 185 | + |
| 186 | +Some checks depend on others. If a dependency doesn't pass, the dependent check is skipped automatically. |
| 187 | + |
| 188 | +- `llms-txt-valid`, `llms-txt-size`, `llms-txt-links-resolve`, `llms-txt-links-markdown` require `llms-txt-exists` |
| 189 | +- `page-size-markdown` requires `markdown-url-support` or `content-negotiation` |
| 190 | +- `section-header-quality` requires `tabbed-content-serialization` |
| 191 | +- `markdown-code-fence-validity` requires `markdown-url-support` or `content-negotiation` |
| 192 | +- `llms-txt-freshness` requires `llms-txt-exists` |
| 193 | +- `markdown-content-parity` requires `markdown-url-support` or `content-negotiation` |
| 194 | + |
| 195 | +## Responsible use |
| 196 | + |
| 197 | +afdocs makes HTTP requests to the sites it checks. It enforces delays between requests (200ms default), caps concurrent connections, and honors `Retry-After` headers. The goal is to help documentation teams improve agent accessibility, not to load-test their infrastructure. |
| 198 | + |
| 199 | +## License |
| 200 | + |
| 201 | +MIT |
0 commit comments