1#!/usr/bin/env node2// Regenerates references/{scanner-patterns,candidates}.md from lib/{scanners,gates}/*3// metadata. The .mjs files are the source of truth; check-docs-fresh.mjs blocks4// PRs where the regenerated output diverges from what's checked in.56import { writeFile } from 'node:fs/promises';7import { fileURLToPath } from 'node:url';8import { dirname, join } from 'node:path';9import { scanners } from '../lib/scanners/index.mjs';10import
23 console.error('[build-docs] wrote scanner-patterns.md + candidates.md');
24}
25
26function renderScanners() {
27 const sorted = scanners.slice().sort((a, b) => a.metadata.id.localeCompare(b.metadata.id));
28 let out = GENERATED_BANNER + '# Scanner patterns\n\n';
29 out += 'AST/grep-style scanners run in parallel with metric-driven investigation. They find known anti-patterns. Findings on cold-path or unmappable files are dropped unless the scanner declares `trafficIndependent: true`.\n\n';
30 out += `Total scanners: ${sorted.length}.\n\n`;
31 out += '## Patterns\n\n';
32 for (const s of sorted) {
33 const m = s.metadata;
34 out += `### \`${m.id}\` — ${m.title}\n\n`;
35 out += `- **Severity**: ${m.severity}\n`;
36 out += `- **Billing dimension**: ${m.billingDimension}\n`;
37 out += `- **Traffic-independent**: ${m.trafficIndependent ? 'yes (cold-path findings survive the doctrine drop)' : 'no (cold-path findings get dropped)'}\n\n`;
38 out += `**Description.** ${m.description}\n\n`;
39 out += `**Fix.** ${m.fix}\n\n`;
40 if (m.citations?.length) {
41 out += `**Citations:**\n${m.citations.map((c) => `- \`${c}\``).join('\n')}\n\n`;
42 }
43 out += '---\n\n';
44 }
45 return trimTrailingBlankLine(out);
46}
47
48function renderCandidates() {
49 const sorted = gates.slice().sort((a, b) => a.metadata.id.localeCompare(b.metadata.id));
50 let out = GENERATED_BANNER + '# Candidate gates\n\n';
51 out += 'The deterministic threshold expressions that turn observability signals into investigation candidates. Pure JS, no LLM. Thresholds live in `lib/gates/*.mjs`.\n\n';