Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .agents/skills/afdocs-audit/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Each issue includes:
- `status` — `fail` or `warn`
- `message` — Human-readable description
- `fix` — Suggested fix from the AFDocs spec
- `details` — Original check diagnostics, including affected URLs and fetch errors when the CLI supplies them; also retained for every check in `all_results`

### v0.6 compatibility checks and reliability

Expand All @@ -76,6 +77,10 @@ effect records this condition. The `dynamic-content-rendered-statically` interac
effect is recorded when multiple size, serialization, parity, or pagination signals
co-occur; it is diagnostic and does not add another score penalty.

Reported fetch errors, rate limiting, or discovery warnings also make the scan partial,
even when bot protection passes. Exclude these results from regression comparisons and
baselines, and inspect the retained check details before attempting remediation.

### Known exceptions

Before reporting, cross-reference every issue against the known exceptions in `references/known-exceptions.md`. Classify each issue into exactly one bucket:
Expand Down
24 changes: 22 additions & 2 deletions .agents/skills/afdocs-audit/scripts/afdocs_audit.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -663,10 +663,23 @@ function scanReliability(results) {
message: 'Sustained automated fetching was interrupted, so multi-page findings may not represent the complete site.',
};
}
const interruptedChecks = results.filter((result) =>
result.details?.fetchErrors > 0 ||
result.details?.rateLimited > 0 ||
result.details?.discoveryWarnings?.length > 0
);
if (interruptedChecks.length > 0) {
return {
status: 'partial',
reason: 'fetch-or-discovery-errors',
affected_checks: interruptedChecks.map((result) => result.id),
message: 'Fetch failures, rate limiting, or discovery warnings interrupted this scan; exclude it from regression comparisons and baselines.',
};
}
return {
status: 'complete',
reason: null,
message: 'No bot-protection interference was observed during this scan.',
message: 'No bot-protection interference, fetch failures, rate limiting, or discovery warnings were reported during this scan.',
};
}

Expand Down Expand Up @@ -716,6 +729,7 @@ export function buildReport(raw, supplementalResults = []) {
status: r.status,
message: r.message,
fix: r.details?.fix || r.fix || null,
details: r.details ?? null,
}));

return {
Expand All @@ -738,7 +752,13 @@ export function buildReport(raw, supplementalResults = []) {
])
),
issues,
all_results: results.map((r) => ({ id: r.id, category: r.category, status: r.status, message: r.message })),
all_results: results.map((r) => ({
id: r.id,
category: r.category,
status: r.status,
message: r.message,
details: r.details ?? null,
})),
scan_reliability: scanReliability(results),
interaction_effects: interactionEffects(results),
};
Expand Down
42 changes: 42 additions & 0 deletions .agents/skills/afdocs-audit/scripts/afdocs_audit.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,48 @@ test('uses the native CLI score after afdocs supplies every v0.6 check', () => {
assert.equal(report.score_method, 'native afdocs CLI score');
});

test('retains page-level diagnostics and excludes fetch-interrupted scans from complete baselines', () => {
Comment thread
warp-agent-staging[bot] marked this conversation as resolved.
Resolved
const result = {
id: 'markdown-content-parity',
category: 'observability',
status: 'pass',
message: 'Compared available pages; one failed to fetch',
details: {
fetchErrors: 1,
rateLimited: 0,
pageResults: [{ url: 'https://docs.example.com/page/', status: 'pass' }],
},
};
const report = buildReport({ score: 90, results: [result] });

assert.equal(report.score, 90);
assert.equal(report.scan_reliability.status, 'partial');
assert.deepEqual(report.scan_reliability.affected_checks, [result.id]);
assert.deepEqual(report.all_results[0].details, result.details);

const warning = buildReport({ score: 90, results: [{ ...result, status: 'warn' }] });
assert.deepEqual(warning.issues[0].details, result.details);
});

test('treats rate limiting and discovery warnings as partial without interpreting intentional 404 tests as fetch errors', () => {
for (const details of [{ rateLimited: 1 }, { discoveryWarnings: ['Sitemap fetch failed'] }]) {
const report = buildReport({ score: 90, results: [{ ...legacyResult, details }] });
assert.equal(report.scan_reliability.status, 'partial');
}

const report = buildReport({
score: 90,
results: [{
...legacyResult,
details: {
fetchErrors: 0, rateLimited: 0, discoveryWarnings: [],
pageResults: [{ url: 'https://docs.example.com/nonexistent/', status: 404 }],
},
}],
});
assert.equal(report.scan_reliability.status, 'complete');
});

test('treats a valid Link-header continuation as visible at the start of the response', async () => {
const headers = { link: '<https://docs.example.com/page.md?cursor=next>; rel="next"' };
assert.deepEqual(extractContinuation('First page', headers), [
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ jobs:
- name: Test Markdown serialization
run: npm run test:docs-markdown

- name: Test AFDocs audit
run: node --test .agents/skills/afdocs-audit/scripts/afdocs_audit.test.mjs

- name: Test tracking redirect middleware
run: npm run test:tracking-redirect

Expand Down
5 changes: 5 additions & 0 deletions src/integrations/docs-markdown-integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,11 @@ function normalizeTabPanels(root) {
}

function sanitizeRoot(root) {
// Starlight excludes file trees from search, but their examples belong in Markdown.
for (const tree of root.querySelectorAll('starlight-file-tree')) {
tree.removeAttribute('data-pagefind-ignore');
}

for (const selector of [
'script',
'style',
Expand Down
21 changes: 21 additions & 0 deletions src/integrations/docs-markdown-integration.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,27 @@ test('includes inert agent-only templates in generated Markdown', () => {
assert.doesNotMatch(markdown, /Search-only chrome\./);
});

test('preserves search-excluded file trees, directory nesting, and file comments', () => {
const html = createPage(`<p>Example project structure:</p>
<starlight-file-tree data-pagefind-ignore="true">
<ul><li class="directory"><details open>
<summary><span class="tree-entry"><span class="sr-only">Directory</span>project/</span></summary>
<ul><li class="directory"><details>
<summary><span class="tree-entry">api/</span></summary>
<ul><li class="file"><span class="tree-entry">AGENTS.md <span class="comment">API-specific rules</span></span></li></ul>
</details></li>
<li class="file"><span class="tree-entry">AGENTS.md <span class="comment">Project-wide rules</span></span></li></ul>
</details></li></ul>
</starlight-file-tree>
<p data-pagefind-ignore>Search-only chrome.</p><p>Following guidance.</p>`);
const markdown = convertHtmlToMarkdown(html);

assert.match(markdown, /- {3}project\/[\s\S]*\n {4}- {3}api\/[\s\S]*\n {8}- {3}AGENTS\.md API-specific rules/);
assert.match(markdown, /\n {4}- {3}AGENTS\.md Project-wide rules/);
assert.match(markdown, /Following guidance\./);
assert.doesNotMatch(markdown, /Directory|Search-only chrome|starlight-file-tree|<details/);
});

test('reconstructs Expressive Code lines and fence metadata', () => {
const html = createPage(`
<div class="expressive-code">
Expand Down
Loading