afdocs 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -23
- package/dist/checks/authentication/auth-alternative-access.js +109 -6
- package/dist/checks/authentication/auth-alternative-access.js.map +1 -1
- package/dist/checks/authentication/auth-gate-detection.js +8 -3
- package/dist/checks/authentication/auth-gate-detection.js.map +1 -1
- package/dist/checks/content-structure/markdown-code-fence-validity.js +1 -1
- package/dist/checks/content-structure/markdown-code-fence-validity.js.map +1 -1
- package/dist/checks/content-structure/section-header-quality.js +240 -6
- package/dist/checks/content-structure/section-header-quality.js.map +1 -1
- package/dist/checks/content-structure/tabbed-content-serialization.js +200 -5
- package/dist/checks/content-structure/tabbed-content-serialization.js.map +1 -1
- package/dist/checks/index.d.ts +1 -0
- package/dist/checks/index.d.ts.map +1 -1
- package/dist/checks/index.js +1 -0
- package/dist/checks/index.js.map +1 -1
- package/dist/checks/llms-txt/llms-txt-exists.js +17 -10
- package/dist/checks/llms-txt/llms-txt-exists.js.map +1 -1
- package/dist/checks/observability/llms-txt-freshness.d.ts +24 -1
- package/dist/checks/observability/llms-txt-freshness.d.ts.map +1 -1
- package/dist/checks/observability/llms-txt-freshness.js +391 -5
- package/dist/checks/observability/llms-txt-freshness.js.map +1 -1
- package/dist/checks/observability/markdown-content-parity.js +599 -5
- package/dist/checks/observability/markdown-content-parity.js.map +1 -1
- package/dist/checks/page-size/content-start-position.js +3 -7
- package/dist/checks/page-size/content-start-position.js.map +1 -1
- package/dist/checks/page-size/page-size-html.js +4 -8
- package/dist/checks/page-size/page-size-html.js.map +1 -1
- package/dist/checks/page-size/rendering-strategy.d.ts +2 -0
- package/dist/checks/page-size/rendering-strategy.d.ts.map +1 -0
- package/dist/checks/page-size/rendering-strategy.js +154 -0
- package/dist/checks/page-size/rendering-strategy.js.map +1 -0
- package/dist/cli/commands/check.d.ts.map +1 -1
- package/dist/cli/commands/check.js +9 -0
- package/dist/cli/commands/check.js.map +1 -1
- package/dist/cli/formatters/text.d.ts.map +1 -1
- package/dist/cli/formatters/text.js +20 -3
- package/dist/cli/formatters/text.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +4 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/constants.d.ts +2 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +3 -0
- package/dist/constants.js.map +1 -1
- package/dist/helpers/detect-rendering.d.ts +31 -0
- package/dist/helpers/detect-rendering.d.ts.map +1 -0
- package/dist/helpers/detect-rendering.js +85 -0
- package/dist/helpers/detect-rendering.js.map +1 -0
- package/dist/helpers/detect-tabs.d.ts +12 -0
- package/dist/helpers/detect-tabs.d.ts.map +1 -0
- package/dist/helpers/detect-tabs.js +309 -0
- package/dist/helpers/detect-tabs.js.map +1 -0
- package/dist/helpers/fetch-page.d.ts +8 -0
- package/dist/helpers/fetch-page.d.ts.map +1 -0
- package/dist/helpers/fetch-page.js +20 -0
- package/dist/helpers/fetch-page.js.map +1 -0
- package/dist/helpers/get-page-urls.d.ts +9 -0
- package/dist/helpers/get-page-urls.d.ts.map +1 -1
- package/dist/helpers/get-page-urls.js +153 -21
- package/dist/helpers/get-page-urls.js.map +1 -1
- package/dist/helpers/index.d.ts +5 -0
- package/dist/helpers/index.d.ts.map +1 -1
- package/dist/helpers/index.js +3 -0
- package/dist/helpers/index.js.map +1 -1
- package/dist/helpers/to-md-urls.d.ts +4 -0
- package/dist/helpers/to-md-urls.d.ts.map +1 -1
- package/dist/helpers/to-md-urls.js +13 -0
- package/dist/helpers/to-md-urls.js.map +1 -1
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +3 -1
- package/dist/runner.js.map +1 -1
- package/dist/types.d.ts +20 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -7,12 +7,12 @@ Test your documentation site against the [Agent-Friendly Documentation Spec](htt
|
|
|
7
7
|
|
|
8
8
|
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.
|
|
9
9
|
|
|
10
|
-
afdocs runs
|
|
10
|
+
afdocs runs 22 checks across 8 categories to evaluate how well your docs serve agent consumers.
|
|
11
11
|
|
|
12
12
|
> **Status: Early development (0.x)**
|
|
13
13
|
> This project is under active development. Check IDs, CLI flags, and output formats may change between minor versions. Feel free to try it out, but don't build automation against specific output until 1.0.
|
|
14
14
|
>
|
|
15
|
-
> Implements [spec v0.1
|
|
15
|
+
> Implements [spec v0.2.1](https://agentdocsspec.com/spec) (2026-03-15).
|
|
16
16
|
|
|
17
17
|
## Quick start
|
|
18
18
|
|
|
@@ -43,7 +43,7 @@ Authentication
|
|
|
43
43
|
✓ auth-gate-detection: All 50 sampled pages are publicly accessible
|
|
44
44
|
|
|
45
45
|
Summary
|
|
46
|
-
9 passed, 3 failed,
|
|
46
|
+
9 passed, 3 failed, 10 skipped (22 total)
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
## Install
|
|
@@ -75,12 +75,34 @@ afdocs check https://docs.example.com --pass-threshold 30000 --fail-threshold 80
|
|
|
75
75
|
| `--format <format>` | `text` | Output format: `text` or `json` |
|
|
76
76
|
| `-v, --verbose` | | Show per-page details for checks with issues |
|
|
77
77
|
| `--checks <ids>` | all | Comma-separated list of check IDs |
|
|
78
|
+
| `--sampling <strategy>` | `random` | URL sampling strategy (see below) |
|
|
78
79
|
| `--max-concurrency <n>` | `3` | Maximum concurrent HTTP requests |
|
|
79
80
|
| `--request-delay <ms>` | `200` | Delay between requests |
|
|
80
81
|
| `--max-links <n>` | `50` | Maximum links to test in link checks |
|
|
81
82
|
| `--pass-threshold <n>` | `50000` | Size pass threshold (characters) |
|
|
82
83
|
| `--fail-threshold <n>` | `100000` | Size fail threshold (characters) |
|
|
83
84
|
|
|
85
|
+
### Sampling strategies
|
|
86
|
+
|
|
87
|
+
By default, afdocs discovers pages from your site (via `llms.txt`, sitemap, or both) and randomly samples up to `--max-links` pages to check. The `--sampling` flag gives you control over how that sample is selected.
|
|
88
|
+
|
|
89
|
+
| Strategy | Behavior |
|
|
90
|
+
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
91
|
+
| `random` | Shuffle discovered URLs and take the first N. Fast and broad, but results vary between runs. |
|
|
92
|
+
| `deterministic` | Sort discovered URLs alphabetically, then pick every Nth URL for an even spread. Produces the same sample on repeated runs as long as the URL set is stable. |
|
|
93
|
+
| `none` | Skip discovery entirely. Only check the URL you pass on the command line. |
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Reproducible runs for CI or iteration (same pages every time)
|
|
97
|
+
afdocs check https://docs.example.com --sampling deterministic
|
|
98
|
+
|
|
99
|
+
# Check a single page without any discovery
|
|
100
|
+
afdocs check https://docs.example.com/api/auth --sampling none
|
|
101
|
+
|
|
102
|
+
# Check a single page with specific checks
|
|
103
|
+
afdocs check https://docs.example.com/api/auth --sampling none --checks page-size-html,redirect-behavior
|
|
104
|
+
```
|
|
105
|
+
|
|
84
106
|
### Exit codes
|
|
85
107
|
|
|
86
108
|
- `0` if all checks pass or warn
|
|
@@ -144,7 +166,7 @@ describe('agent-friendliness', () => {
|
|
|
144
166
|
|
|
145
167
|
## Checks
|
|
146
168
|
|
|
147
|
-
|
|
169
|
+
22 checks across 8 categories.
|
|
148
170
|
|
|
149
171
|
### Category 1: llms.txt
|
|
150
172
|
|
|
@@ -165,19 +187,20 @@ describe('agent-friendliness', () => {
|
|
|
165
187
|
|
|
166
188
|
### Category 3: Page Size and Truncation Risk
|
|
167
189
|
|
|
168
|
-
| Check | Description
|
|
169
|
-
| ------------------------ |
|
|
170
|
-
| `
|
|
171
|
-
| `page-size-
|
|
172
|
-
| `
|
|
190
|
+
| Check | Description |
|
|
191
|
+
| ------------------------ | --------------------------------------------------------------- |
|
|
192
|
+
| `rendering-strategy` | Whether pages contain server-rendered content or are SPA shells |
|
|
193
|
+
| `page-size-markdown` | Character count when served as markdown |
|
|
194
|
+
| `page-size-html` | Character count of HTML and post-conversion size |
|
|
195
|
+
| `content-start-position` | How far into the response actual content begins |
|
|
173
196
|
|
|
174
197
|
### Category 4: Content Structure
|
|
175
198
|
|
|
176
|
-
| Check
|
|
177
|
-
|
|
|
178
|
-
| `tabbed-content-serialization`
|
|
179
|
-
| `section-header-quality`
|
|
180
|
-
| `markdown-code-fence-validity`
|
|
199
|
+
| Check | Description |
|
|
200
|
+
| ------------------------------ | -------------------------------------------------- |
|
|
201
|
+
| `tabbed-content-serialization` | Whether tabbed content creates oversized output |
|
|
202
|
+
| `section-header-quality` | Whether headers in tabbed sections include context |
|
|
203
|
+
| `markdown-code-fence-validity` | Whether markdown has unclosed code fences |
|
|
181
204
|
|
|
182
205
|
### Category 5: URL Stability and Redirects
|
|
183
206
|
|
|
@@ -194,18 +217,18 @@ describe('agent-friendliness', () => {
|
|
|
194
217
|
|
|
195
218
|
### Category 7: Observability and Content Health
|
|
196
219
|
|
|
197
|
-
| Check
|
|
198
|
-
|
|
|
199
|
-
| `llms-txt-freshness`
|
|
200
|
-
| `markdown-content-parity`
|
|
201
|
-
| `cache-header-hygiene`
|
|
220
|
+
| Check | Description |
|
|
221
|
+
| ------------------------- | ---------------------------------------------- |
|
|
222
|
+
| `llms-txt-freshness` | Whether `llms.txt` reflects current site state |
|
|
223
|
+
| `markdown-content-parity` | Whether markdown and HTML versions match |
|
|
224
|
+
| `cache-header-hygiene` | Whether cache headers allow timely updates |
|
|
202
225
|
|
|
203
226
|
### Category 8: Authentication and Access
|
|
204
227
|
|
|
205
|
-
| Check
|
|
206
|
-
|
|
|
207
|
-
| `auth-gate-detection`
|
|
208
|
-
| `auth-alternative-access`
|
|
228
|
+
| Check | Description |
|
|
229
|
+
| ------------------------- | -------------------------------------------------------------------- |
|
|
230
|
+
| `auth-gate-detection` | Whether documentation pages require authentication to access content |
|
|
231
|
+
| `auth-alternative-access` | Whether auth-gated sites provide alternative access paths for agents |
|
|
209
232
|
|
|
210
233
|
## Check dependencies
|
|
211
234
|
|
|
@@ -1,17 +1,120 @@
|
|
|
1
1
|
import { registerCheck } from '../registry.js';
|
|
2
|
-
async function check(
|
|
2
|
+
async function check(ctx) {
|
|
3
|
+
const id = 'auth-alternative-access';
|
|
4
|
+
const category = 'authentication';
|
|
5
|
+
// Read auth-gate-detection result; skip if it didn't run or docs are all public
|
|
6
|
+
const authResult = ctx.previousResults.get('auth-gate-detection');
|
|
7
|
+
if (!authResult) {
|
|
8
|
+
return {
|
|
9
|
+
id,
|
|
10
|
+
category,
|
|
11
|
+
status: 'skip',
|
|
12
|
+
message: 'auth-gate-detection did not run',
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
if (authResult.status === 'pass') {
|
|
16
|
+
return {
|
|
17
|
+
id,
|
|
18
|
+
category,
|
|
19
|
+
status: 'skip',
|
|
20
|
+
message: 'All docs pages are publicly accessible; no alternative access paths needed',
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
if (authResult.status === 'skip' || authResult.status === 'error') {
|
|
24
|
+
return {
|
|
25
|
+
id,
|
|
26
|
+
category,
|
|
27
|
+
status: 'skip',
|
|
28
|
+
message: `auth-gate-detection ${authResult.status === 'error' ? 'errored' : 'was skipped'}; cannot assess alternative access`,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
// Auth-gate-detection returned warn or fail — look for alternative access paths
|
|
32
|
+
const authDetails = (authResult.details ?? {});
|
|
33
|
+
const gatedCount = (authDetails.authRequired ?? 0) +
|
|
34
|
+
(authDetails.softAuthGate ?? 0) +
|
|
35
|
+
(authDetails.authRedirect ?? 0);
|
|
36
|
+
const accessibleCount = authDetails.accessible ?? 0;
|
|
37
|
+
const testedCount = authDetails.testedPages ?? 0;
|
|
38
|
+
const detectedPaths = [];
|
|
39
|
+
// 1. Check for public llms.txt
|
|
40
|
+
const llmsResult = ctx.previousResults.get('llms-txt-exists');
|
|
41
|
+
if (llmsResult?.status === 'pass' || llmsResult?.status === 'warn') {
|
|
42
|
+
detectedPaths.push({
|
|
43
|
+
type: 'public-llms-txt',
|
|
44
|
+
description: 'Site serves a public llms.txt file, giving agents a navigational index even though docs pages are gated',
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
// 2. Check for publicly accessible markdown
|
|
48
|
+
const mdUrlResult = ctx.previousResults.get('markdown-url-support');
|
|
49
|
+
const cnResult = ctx.previousResults.get('content-negotiation');
|
|
50
|
+
if (mdUrlResult?.status === 'pass' || mdUrlResult?.status === 'warn') {
|
|
51
|
+
detectedPaths.push({
|
|
52
|
+
type: 'public-markdown',
|
|
53
|
+
description: 'Some pages serve markdown via .md URLs, providing agent-readable content without authentication',
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
else if (cnResult?.status === 'pass' || cnResult?.status === 'warn') {
|
|
57
|
+
detectedPaths.push({
|
|
58
|
+
type: 'public-markdown',
|
|
59
|
+
description: 'Some pages serve markdown via content negotiation, providing agent-readable content without authentication',
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
// 3. Check for partially accessible pages (from auth-gate-detection itself)
|
|
63
|
+
if (accessibleCount > 0 && gatedCount > 0) {
|
|
64
|
+
const pct = Math.round((accessibleCount / testedCount) * 100);
|
|
65
|
+
detectedPaths.push({
|
|
66
|
+
type: 'partial-public-access',
|
|
67
|
+
description: `${accessibleCount} of ${testedCount} tested pages (${pct}%) are publicly accessible without authentication`,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
// Determine status
|
|
71
|
+
const manualOnlyNote = 'Some access paths cannot be detected automatically: bundled SDK docs, CLI doc commands, and MCP servers';
|
|
72
|
+
let status;
|
|
73
|
+
let message;
|
|
74
|
+
if (detectedPaths.length === 0) {
|
|
75
|
+
status = 'fail';
|
|
76
|
+
message = `No alternative access paths detected for ${gatedCount} auth-gated pages. ${manualOnlyNote}`;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
// Pass if we found a full-content path (llms.txt + markdown, or most pages accessible).
|
|
80
|
+
// Warn if we only found partial paths (llms.txt alone is just an index, not content).
|
|
81
|
+
const hasContentPath = detectedPaths.some((p) => p.type === 'public-markdown');
|
|
82
|
+
const hasHighAccessibility = accessibleCount > 0 && testedCount > 0 && accessibleCount / testedCount >= 0.5;
|
|
83
|
+
if (hasContentPath || hasHighAccessibility) {
|
|
84
|
+
status = 'pass';
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
status = 'warn';
|
|
88
|
+
}
|
|
89
|
+
const pathSummary = detectedPaths.map((p) => p.type).join(', ');
|
|
90
|
+
message =
|
|
91
|
+
status === 'pass'
|
|
92
|
+
? `Alternative access detected (${pathSummary}) for site with ${gatedCount} auth-gated pages`
|
|
93
|
+
: `Partial alternative access detected (${pathSummary}) for site with ${gatedCount} auth-gated pages. ${manualOnlyNote}`;
|
|
94
|
+
}
|
|
3
95
|
return {
|
|
4
|
-
id
|
|
5
|
-
category
|
|
6
|
-
status
|
|
7
|
-
message
|
|
96
|
+
id,
|
|
97
|
+
category,
|
|
98
|
+
status,
|
|
99
|
+
message,
|
|
100
|
+
details: {
|
|
101
|
+
gatedPages: gatedCount,
|
|
102
|
+
accessiblePages: accessibleCount,
|
|
103
|
+
testedPages: testedCount,
|
|
104
|
+
detectedPaths,
|
|
105
|
+
manualVerificationNeeded: [
|
|
106
|
+
'Bundled documentation (docs shipped in package/SDK)',
|
|
107
|
+
'CLI-based doc access (e.g. `yourproduct docs search "topic"`)',
|
|
108
|
+
'MCP server providing doc access through tool calls',
|
|
109
|
+
],
|
|
110
|
+
},
|
|
8
111
|
};
|
|
9
112
|
}
|
|
10
113
|
registerCheck({
|
|
11
114
|
id: 'auth-alternative-access',
|
|
12
115
|
category: 'authentication',
|
|
13
116
|
description: 'Whether an auth-gated documentation site provides alternative access paths for agents',
|
|
14
|
-
dependsOn: [
|
|
117
|
+
dependsOn: [],
|
|
15
118
|
run: check,
|
|
16
119
|
});
|
|
17
120
|
//# sourceMappingURL=auth-alternative-access.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-alternative-access.js","sourceRoot":"","sources":["../../../src/checks/authentication/auth-alternative-access.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"auth-alternative-access.js","sourceRoot":"","sources":["../../../src/checks/authentication/auth-alternative-access.ts"],"names":[],"mappings":"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"}
|
|
@@ -25,11 +25,16 @@ function detectLoginForm(body) {
|
|
|
25
25
|
if (sample.includes('<input') && sample.includes('type="password"')) {
|
|
26
26
|
return 'Contains password input field';
|
|
27
27
|
}
|
|
28
|
-
// Check page title for login indicators
|
|
28
|
+
// Check page title for login indicators.
|
|
29
|
+
// Only match titles that suggest the page IS a login form, not pages that
|
|
30
|
+
// mention login as a topic (e.g. "unable to login" in a knowledge base article).
|
|
31
|
+
// We require the login keyword to appear at the start or after a separator.
|
|
29
32
|
const titleMatch = /<title[^>]*>(.*?)<\/title>/i.exec(sample);
|
|
30
33
|
if (titleMatch) {
|
|
31
|
-
const title = titleMatch[1].toLowerCase();
|
|
32
|
-
if (
|
|
34
|
+
const title = titleMatch[1].toLowerCase().trim();
|
|
35
|
+
if (/^(sign\s*in|log\s*in)\b/.test(title) ||
|
|
36
|
+
/[|\-–—:]\s*(sign\s*in|log\s*in)\s*$/i.test(title) ||
|
|
37
|
+
/^authenticate\b/.test(title)) {
|
|
33
38
|
return `Page title suggests login: "${titleMatch[1].trim()}"`;
|
|
34
39
|
}
|
|
35
40
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-gate-detection.js","sourceRoot":"","sources":["../../../src/checks/authentication/auth-gate-detection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAexE,MAAM,WAAW,GAAG;IAClB,UAAU;IACV,WAAW;IACX,2BAA2B;IAC3B,qBAAqB;IACrB,sBAAsB;IACtB,MAAM;IACN,MAAM;IACN,OAAO;IACP,QAAQ;CACT,CAAC;AAEF,SAAS,WAAW,CAAC,GAAW;IAC9B,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;QACrD,OAAO,WAAW,CAAC,IAAI,CACrB,CAAC,MAAM,EAAE,EAAE,CACT,QAAQ,KAAK,MAAM,IAAI,QAAQ,CAAC,QAAQ,CAAC,GAAG,GAAG,MAAM,CAAC,IAAI,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,CACxF,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,SAAS,eAAe,CAAC,IAAY;IACnC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;IAElD,IAAI,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC;QACpE,OAAO,+BAA+B,CAAC;IACzC,CAAC;IAED,
|
|
1
|
+
{"version":3,"file":"auth-gate-detection.js","sourceRoot":"","sources":["../../../src/checks/authentication/auth-gate-detection.ts"],"names":[],"mappings":"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"}
|
|
@@ -104,7 +104,7 @@ registerCheck({
|
|
|
104
104
|
id: 'markdown-code-fence-validity',
|
|
105
105
|
category: 'content-structure',
|
|
106
106
|
description: 'Whether markdown contains unclosed code fences',
|
|
107
|
-
dependsOn: [
|
|
107
|
+
dependsOn: [],
|
|
108
108
|
run: check,
|
|
109
109
|
});
|
|
110
110
|
//# sourceMappingURL=markdown-code-fence-validity.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markdown-code-fence-validity.js","sourceRoot":"","sources":["../../../src/checks/content-structure/markdown-code-fence-validity.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"markdown-code-fence-validity.js","sourceRoot":"","sources":["../../../src/checks/content-structure/markdown-code-fence-validity.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,17 +1,251 @@
|
|
|
1
|
+
import { parse } from 'node-html-parser';
|
|
1
2
|
import { registerCheck } from '../registry.js';
|
|
2
|
-
|
|
3
|
+
const MD_HEADING_RE = /^#{1,6}\s+(.+)$/gm;
|
|
4
|
+
/**
|
|
5
|
+
* Extract header text from content that may be HTML, markdown, or a mix (MDX).
|
|
6
|
+
* Tries HTML parsing first, then falls back to markdown heading regex.
|
|
7
|
+
*/
|
|
8
|
+
function extractHeaders(content) {
|
|
9
|
+
const headers = [];
|
|
10
|
+
// HTML headers
|
|
11
|
+
const root = parse(content);
|
|
12
|
+
const htmlHeaders = root.querySelectorAll('h1, h2, h3, h4, h5, h6');
|
|
13
|
+
for (const h of htmlHeaders) {
|
|
14
|
+
const text = h.textContent.trim();
|
|
15
|
+
if (text.length > 0)
|
|
16
|
+
headers.push(text);
|
|
17
|
+
}
|
|
18
|
+
// Markdown headers (## Heading)
|
|
19
|
+
let match;
|
|
20
|
+
while ((match = MD_HEADING_RE.exec(content)) !== null) {
|
|
21
|
+
const text = match[1].trim();
|
|
22
|
+
if (text.length > 0)
|
|
23
|
+
headers.push(text);
|
|
24
|
+
}
|
|
25
|
+
return headers;
|
|
26
|
+
}
|
|
27
|
+
async function check(ctx) {
|
|
28
|
+
const id = 'section-header-quality';
|
|
29
|
+
const category = 'content-structure';
|
|
30
|
+
const tabResult = ctx.previousResults.get('tabbed-content-serialization');
|
|
31
|
+
if (!tabResult || tabResult.status === 'skip') {
|
|
32
|
+
return {
|
|
33
|
+
id,
|
|
34
|
+
category,
|
|
35
|
+
status: 'skip',
|
|
36
|
+
message: 'Skipped: tabbed-content-serialization did not run',
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const tabbedPages = tabResult.details?.tabbedPages ?? [];
|
|
40
|
+
const pagesWithGroups = tabbedPages.filter((p) => p.tabGroups && p.tabGroups.length > 0);
|
|
41
|
+
if (pagesWithGroups.length === 0) {
|
|
42
|
+
return {
|
|
43
|
+
id,
|
|
44
|
+
category,
|
|
45
|
+
status: 'pass',
|
|
46
|
+
message: 'No tabbed content found; header quality check not applicable',
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const analyses = [];
|
|
50
|
+
// Track unique headers per analysis for cross-group pass
|
|
51
|
+
const analysisHeaderSets = [];
|
|
52
|
+
for (const page of pagesWithGroups) {
|
|
53
|
+
for (const group of page.tabGroups) {
|
|
54
|
+
if (group.panels.length < 2)
|
|
55
|
+
continue;
|
|
56
|
+
// Extract headers from each panel
|
|
57
|
+
const panelHeaders = group.panels.map((panel) => ({
|
|
58
|
+
label: panel.label,
|
|
59
|
+
headers: extractHeaders(panel.html),
|
|
60
|
+
}));
|
|
61
|
+
// Count how many times each header text appears across panels
|
|
62
|
+
const headerCounts = new Map();
|
|
63
|
+
const uniqueHeaders = new Set();
|
|
64
|
+
for (const ph of panelHeaders) {
|
|
65
|
+
for (const h of ph.headers) {
|
|
66
|
+
const lower = h.toLowerCase();
|
|
67
|
+
headerCounts.set(lower, (headerCounts.get(lower) ?? 0) + 1);
|
|
68
|
+
uniqueHeaders.add(lower);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const allHeaders = panelHeaders.flatMap((ph) => ph.headers);
|
|
72
|
+
let genericCount = 0;
|
|
73
|
+
let contextualCount = 0;
|
|
74
|
+
for (const ph of panelHeaders) {
|
|
75
|
+
for (const h of ph.headers) {
|
|
76
|
+
const lower = h.toLowerCase();
|
|
77
|
+
const appearsInMultiple = (headerCounts.get(lower) ?? 0) >= 2;
|
|
78
|
+
// A header is contextual if it includes the panel label or is unique
|
|
79
|
+
const includesLabel = ph.label != null && lower.includes(ph.label.toLowerCase());
|
|
80
|
+
if (includesLabel || !appearsInMultiple) {
|
|
81
|
+
contextualCount++;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
genericCount++;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const totalHeaders = allHeaders.length;
|
|
89
|
+
const hasGenericMajority = totalHeaders > 0 && genericCount > totalHeaders / 2;
|
|
90
|
+
analysisHeaderSets.push(uniqueHeaders);
|
|
91
|
+
analyses.push({
|
|
92
|
+
url: page.url,
|
|
93
|
+
framework: group.framework,
|
|
94
|
+
totalHeaders,
|
|
95
|
+
genericHeaders: genericCount,
|
|
96
|
+
contextualHeaders: contextualCount,
|
|
97
|
+
hasGenericMajority,
|
|
98
|
+
hasCrossGroupGeneric: false,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Cross-group analysis: detect identical headers repeated across separate tab groups
|
|
103
|
+
// on the same page without variant context (e.g. "Build a MongoDB Search Query"
|
|
104
|
+
// appearing in 7 driver-specific tab groups).
|
|
105
|
+
let crossGroupGenericGroupCount = 0;
|
|
106
|
+
let crossGroupTotalGroupCount = 0;
|
|
107
|
+
const crossGroupRepeatedHeaders = [];
|
|
108
|
+
for (const page of pagesWithGroups) {
|
|
109
|
+
if (page.tabGroups.length < 2)
|
|
110
|
+
continue;
|
|
111
|
+
// Collect all panel labels and unique headers per group
|
|
112
|
+
const allLabels = new Set();
|
|
113
|
+
const perGroup = [];
|
|
114
|
+
for (const group of page.tabGroups) {
|
|
115
|
+
const headers = new Set();
|
|
116
|
+
for (const panel of group.panels) {
|
|
117
|
+
if (panel.label)
|
|
118
|
+
allLabels.add(panel.label.toLowerCase());
|
|
119
|
+
for (const h of extractHeaders(panel.html))
|
|
120
|
+
headers.add(h.toLowerCase());
|
|
121
|
+
}
|
|
122
|
+
perGroup.push(headers);
|
|
123
|
+
}
|
|
124
|
+
// Count how many groups each header appears in
|
|
125
|
+
const headerGroupCount = new Map();
|
|
126
|
+
for (const hs of perGroup) {
|
|
127
|
+
for (const h of hs)
|
|
128
|
+
headerGroupCount.set(h, (headerGroupCount.get(h) ?? 0) + 1);
|
|
129
|
+
}
|
|
130
|
+
// A header is cross-group generic if it appears in 2+ groups and doesn't
|
|
131
|
+
// include any panel label (i.e. lacks variant context)
|
|
132
|
+
const crossGenericSet = new Set();
|
|
133
|
+
for (const [header, count] of headerGroupCount) {
|
|
134
|
+
if (count >= 2 && ![...allLabels].some((l) => header.includes(l))) {
|
|
135
|
+
crossGenericSet.add(header);
|
|
136
|
+
crossGroupRepeatedHeaders.push({ url: page.url, header, groupCount: count });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Count groups affected by cross-group generic headers
|
|
140
|
+
for (const hs of perGroup) {
|
|
141
|
+
if (hs.size === 0)
|
|
142
|
+
continue;
|
|
143
|
+
crossGroupTotalGroupCount++;
|
|
144
|
+
if ([...hs].some((h) => crossGenericSet.has(h)))
|
|
145
|
+
crossGroupGenericGroupCount++;
|
|
146
|
+
}
|
|
147
|
+
// Update individual analyses with cross-group flag
|
|
148
|
+
if (crossGenericSet.size > 0) {
|
|
149
|
+
for (let i = 0; i < analyses.length; i++) {
|
|
150
|
+
if (analyses[i].url !== page.url)
|
|
151
|
+
continue;
|
|
152
|
+
if ([...analysisHeaderSets[i]].some((h) => crossGenericSet.has(h))) {
|
|
153
|
+
analyses[i].hasCrossGroupGeneric = true;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (analyses.length === 0 && crossGroupTotalGroupCount === 0) {
|
|
159
|
+
return {
|
|
160
|
+
id,
|
|
161
|
+
category,
|
|
162
|
+
status: 'pass',
|
|
163
|
+
message: 'Tab groups have fewer than 2 panels; header quality check not applicable',
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
const groupsWithGenericMajority = analyses.filter((a) => a.hasGenericMajority).length;
|
|
167
|
+
const groupsWithHeaders = analyses.filter((a) => a.totalHeaders > 0).length;
|
|
168
|
+
// If no tab panels contain any section headers, we can't evaluate quality
|
|
169
|
+
if (groupsWithHeaders === 0 && crossGroupTotalGroupCount === 0) {
|
|
170
|
+
return {
|
|
171
|
+
id,
|
|
172
|
+
category,
|
|
173
|
+
status: 'skip',
|
|
174
|
+
message: `${pagesWithGroups.length} page(s) with tabs found, but no section headers inside tab panels to evaluate`,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
// Identify affected pages: pages where any group has within-group or cross-group issues
|
|
178
|
+
const pagesWithWithinGroupIssues = new Set(analyses.filter((a) => a.hasGenericMajority).map((a) => a.url));
|
|
179
|
+
const pagesWithCrossGroupIssues = new Set(crossGroupRepeatedHeaders.map((h) => h.url));
|
|
180
|
+
const affectedPages = new Set([...pagesWithWithinGroupIssues, ...pagesWithCrossGroupIssues]);
|
|
181
|
+
// Count pages where we actually found headers to evaluate
|
|
182
|
+
const pagesWithHeaders = new Set(analyses.filter((a) => a.totalHeaders > 0).map((a) => a.url));
|
|
183
|
+
// Scoring: use group-level ratios for fine-grained thresholds
|
|
184
|
+
// Within-group: ratio of groups-with-headers that have majority-generic
|
|
185
|
+
let withinStatus = 'pass';
|
|
186
|
+
if (groupsWithHeaders > 0) {
|
|
187
|
+
const wRatio = groupsWithGenericMajority / groupsWithHeaders;
|
|
188
|
+
if (wRatio > 0.5)
|
|
189
|
+
withinStatus = 'fail';
|
|
190
|
+
else if (wRatio > 0.25)
|
|
191
|
+
withinStatus = 'warn';
|
|
192
|
+
}
|
|
193
|
+
// Cross-group: ratio of groups on multi-group pages that have cross-group generics
|
|
194
|
+
let crossGroupStatus = 'pass';
|
|
195
|
+
if (crossGroupTotalGroupCount > 0) {
|
|
196
|
+
const cRatio = crossGroupGenericGroupCount / crossGroupTotalGroupCount;
|
|
197
|
+
if (cRatio > 0.5)
|
|
198
|
+
crossGroupStatus = 'fail';
|
|
199
|
+
else if (cRatio > 0.25)
|
|
200
|
+
crossGroupStatus = 'warn';
|
|
201
|
+
}
|
|
202
|
+
// Combined status: worst of both
|
|
203
|
+
const statusRank = { pass: 0, skip: 0, warn: 1, fail: 2, error: 2 };
|
|
204
|
+
const status = statusRank[crossGroupStatus] > statusRank[withinStatus] ? crossGroupStatus : withinStatus;
|
|
205
|
+
// Build a page-oriented message for docs teams
|
|
206
|
+
let message;
|
|
207
|
+
if (affectedPages.size === 0) {
|
|
208
|
+
message = `${pagesWithHeaders.size} page(s) with tab headers checked; headers include variant context`;
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
// Find the most-repeated cross-group header for a concrete example
|
|
212
|
+
const worstHeader = crossGroupRepeatedHeaders.length > 0
|
|
213
|
+
? [...crossGroupRepeatedHeaders].sort((a, b) => b.groupCount - a.groupCount)[0]
|
|
214
|
+
: null;
|
|
215
|
+
const pageSummary = `${affectedPages.size} of ${pagesWithHeaders.size} page(s) with tab headers ` +
|
|
216
|
+
`don't distinguish between variants`;
|
|
217
|
+
if (worstHeader) {
|
|
218
|
+
message = `${pageSummary} (e.g. "${worstHeader.header}" repeats across ${worstHeader.groupCount} tab groups)`;
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
message = pageSummary;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
3
224
|
return {
|
|
4
|
-
id
|
|
5
|
-
category
|
|
6
|
-
status
|
|
7
|
-
message
|
|
225
|
+
id,
|
|
226
|
+
category,
|
|
227
|
+
status,
|
|
228
|
+
message,
|
|
229
|
+
details: {
|
|
230
|
+
pagesWithTabs: pagesWithGroups.length,
|
|
231
|
+
pagesAffected: affectedPages.size,
|
|
232
|
+
totalGroupsAnalyzed: analyses.length,
|
|
233
|
+
groupsWithHeaders,
|
|
234
|
+
groupsWithGenericMajority,
|
|
235
|
+
crossGroupGenericGroupCount,
|
|
236
|
+
crossGroupTotalGroupCount,
|
|
237
|
+
crossGroupRepeatedHeaders,
|
|
238
|
+
analyses,
|
|
239
|
+
},
|
|
8
240
|
};
|
|
9
241
|
}
|
|
10
242
|
registerCheck({
|
|
11
243
|
id: 'section-header-quality',
|
|
12
244
|
category: 'content-structure',
|
|
13
245
|
description: 'Whether headers in tabbed sections include variant context',
|
|
14
|
-
|
|
246
|
+
// No hard dependency: we read from previousResults if available,
|
|
247
|
+
// but the check handles missing data gracefully (returns skip).
|
|
248
|
+
dependsOn: [],
|
|
15
249
|
run: check,
|
|
16
250
|
});
|
|
17
251
|
//# sourceMappingURL=section-header-quality.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"section-header-quality.js","sourceRoot":"","sources":["../../../src/checks/content-structure/section-header-quality.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"section-header-quality.js","sourceRoot":"","sources":["../../../src/checks/content-structure/section-header-quality.ts"],"names":[],"mappings":"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"}
|