@goflag/cli 0.2.5 → 0.2.7
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 +76 -14
- package/dist/{chunk-5FO2Q3II.js → chunk-UOKYUKHQ.js} +172 -20
- package/dist/chunk-UOKYUKHQ.js.map +1 -0
- package/dist/cli.js +500 -8
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +30 -4
- package/dist/index.js +1 -1
- package/package.json +4 -2
- package/rules.json +517 -0
- package/dist/chunk-5FO2Q3II.js.map +0 -1
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/report/diff.ts","../src/report/render-diff.ts","../src/lib/runner/dev-server.ts","../src/cli-args.ts","../src/cli.ts"],"sourcesContent":["/**\n * Baseline comparison.\n *\n * Every finding already carries a `fingerprint` id derived from the *identity*\n * of the problem rather than its transient details, and page URLs are\n * normalised to origin-independent routes — so the same defect fingerprints\n * identically on localhost, staging and production. That machinery was built\n * for exactly this and had no consumer.\n *\n * What it changes is what the tool *is*. Without a baseline the CI gate is\n * binary: any finding fails, which is unusable on a site that is not clean\n * yet, so teams either never turn it on or learn to ignore it. With one, the\n * gate becomes \"no *new* findings\" — a promise a real codebase can keep while\n * the backlog is worked down.\n *\n * Pure function of two reports. No I/O; the CLI reads the baseline file.\n */\n\nimport type { Severity } from \"../lib/core/types\";\nimport { DEFAULT_PROFILE } from \"../lib/rules/profiles\";\nimport type { GoflagReport } from \"./types\";\n\n/** Which part of the report a finding came from. */\nexport type FindingKind =\n \"brokenLink\" | \"unreachablePage\" | \"translationHole\" | \"reciprocity\" | \"seo\" | \"site\";\n\n/** One finding, flattened out of its report section for comparison. */\nexport interface DiffEntry {\n id: string;\n kind: FindingKind;\n /**\n * Severity used for the exit-code decision. Rule findings carry their own;\n * the rest are mapped to match the verdict logic in `build.ts` — a broken\n * link or an unreachable page is an error, a translation gap is a warning.\n */\n severity: Severity;\n /** One-line description, for the rendered diff. */\n summary: string;\n /** Page the finding sits on, when it has one. */\n pageUrl?: string;\n}\n\nexport interface ReportDiff {\n /** Where, when, and under which rule profile the baseline was taken. */\n baseline: { url: string; finishedAt: string; profile: string };\n /**\n * Set when this run was judged under a different profile than the baseline\n * was captured under. Not an error — comparing `spec-only` against a\n * `default` baseline is legitimate, it is just not like-for-like, and\n * \"0 new findings\" means less than it appears. So it is reported loudly\n * and gates nothing: silently diffing across two policies is the failure\n * mode worth preventing.\n */\n profileMismatch?: { baseline: string; current: string };\n /** Findings present now and absent from the baseline — what should fail CI. */\n added: DiffEntry[];\n /** Findings in the baseline that are gone. Worth showing: progress is data too. */\n resolved: DiffEntry[];\n /** Findings present in both. */\n unchanged: number;\n}\n\n/** Flatten every finding collection into one comparable list. */\nexport function collectFindings(report: GoflagReport): DiffEntry[] {\n const out: DiffEntry[] = [];\n\n for (const link of report.brokenLinks) {\n out.push({\n id: link.id,\n kind: \"brokenLink\",\n severity: \"error\",\n summary: `${link.verdict} link → ${link.href}`,\n pageUrl: link.pageUrl,\n });\n }\n\n for (const page of report.unreachablePages) {\n out.push({\n id: page.id,\n kind: \"unreachablePage\",\n severity: \"error\",\n summary: `${page.status || \"network error\"} on ${page.url}`,\n pageUrl: page.url,\n });\n }\n\n for (const hole of report.missingTranslations.holes) {\n out.push({\n id: hole.id,\n kind: \"translationHole\",\n severity: \"warning\",\n summary: `${hole.route} missing ${hole.missingLocales.join(\", \")}`,\n });\n }\n\n for (const issue of report.missingTranslations.reciprocity) {\n out.push({\n id: issue.id,\n kind: \"reciprocity\",\n severity: \"warning\",\n summary: `${issue.code} on ${issue.url}`,\n pageUrl: issue.url,\n });\n }\n\n for (const issue of report.seoIssues) {\n out.push({\n id: issue.id,\n kind: \"seo\",\n severity: issue.severity,\n summary: `${issue.ruleId} on ${issue.pageUrl}`,\n pageUrl: issue.pageUrl,\n });\n }\n\n for (const issue of report.siteIssues) {\n out.push({\n id: issue.id,\n kind: \"site\",\n severity: issue.severity,\n summary: `${issue.ruleId} on ${issue.pageUrl}`,\n pageUrl: issue.pageUrl,\n });\n }\n\n return out;\n}\n\n/** Deterministic ordering so a rendered diff is stable and reviewable. */\nconst SEVERITY_RANK: Record<Severity, number> = { error: 0, warning: 1, info: 2 };\n\nfunction sortEntries(entries: DiffEntry[]): DiffEntry[] {\n return [...entries].sort(\n (a, b) =>\n SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity] ||\n a.kind.localeCompare(b.kind) ||\n a.summary.localeCompare(b.summary),\n );\n}\n\n/** Total findings the site currently carries, new and known alike. */\nexport function totalFindings(report: GoflagReport): number {\n return collectFindings(report).length;\n}\n\n/** Compare a fresh report against a stored baseline. */\nexport function diffReports(baseline: GoflagReport, current: GoflagReport): ReportDiff {\n const before = new Map(collectFindings(baseline).map((e) => [e.id, e]));\n const now = new Map(collectFindings(current).map((e) => [e.id, e]));\n\n const added: DiffEntry[] = [];\n let unchanged = 0;\n for (const [id, entry] of now) {\n if (before.has(id)) unchanged += 1;\n else added.push(entry);\n }\n\n const resolved: DiffEntry[] = [];\n for (const [id, entry] of before) {\n if (!now.has(id)) resolved.push(entry);\n }\n\n // A baseline stored before profiles existed has no `profile`; the behaviour\n // it captured is what `default` still does, so that is the honest reading.\n const baselineProfile = baseline.profile ?? DEFAULT_PROFILE;\n const currentProfile = current.profile ?? DEFAULT_PROFILE;\n\n return {\n baseline: { url: baseline.url, finishedAt: baseline.finishedAt, profile: baselineProfile },\n added: sortEntries(added),\n resolved: sortEntries(resolved),\n unchanged,\n ...(baselineProfile === currentProfile\n ? {}\n : { profileMismatch: { baseline: baselineProfile, current: currentProfile } }),\n };\n}\n\n/**\n * Exit code under `--regressions-only`: fail on findings that are *new* at or\n * above the threshold, or on a debt budget being exceeded.\n *\n * Deliberately blind to the overall verdict, unlike `exitCode`. A site with a\n * hundred known problems and no new ones passes — that is what the mode is\n * for, and why it has to be asked for by name.\n *\n * `maxDebt` is the counterweight. Gating on regressions alone lets a backlog\n * sit untouched forever behind a passing build; a ceiling you lower as you fix\n * is the only part of this design that makes the debt actually go down.\n */\nexport function diffExitCode(\n diff: ReportDiff,\n failOn: \"warning\" | \"error\" | \"never\",\n debt?: { total: number; max?: number },\n): number {\n if (debt?.max !== undefined && debt.total > debt.max) return 1;\n if (failOn === \"never\") return 0;\n const threshold = failOn === \"error\" ? 0 : 1;\n const blocking = diff.added.filter((e) => SEVERITY_RANK[e.severity] <= threshold);\n return blocking.length > 0 ? 1 : 0;\n}\n","/**\n * Render a `ReportDiff` for a terminal.\n *\n * Deliberately leads with what is new, because that is what fails the build\n * and what the reader has to act on. Resolved findings come second and are\n * kept — a run that only ever shows problems gives no signal that the backlog\n * is moving, which is how a gate stops being read.\n *\n * Pure function of the diff; no I/O.\n */\n\nimport type { Severity } from \"../lib/core/types\";\nimport type { DiffEntry, ReportDiff } from \"./diff\";\n\ninterface RenderOptions {\n color?: boolean;\n /** Injected clock, so the rendered age is testable. */\n now?: number;\n}\n\nconst ANSI = {\n reset: \"\\x1b[0m\",\n bold: \"\\x1b[1m\",\n dim: \"\\x1b[2m\",\n red: \"\\x1b[31m\",\n green: \"\\x1b[32m\",\n yellow: \"\\x1b[33m\",\n cyan: \"\\x1b[36m\",\n};\n\nfunction paint(enabled: boolean) {\n return (code: string, text: string) => (enabled ? `${code}${text}${ANSI.reset}` : text);\n}\n\nfunction severityTag(c: (code: string, t: string) => string, severity: Severity): string {\n if (severity === \"error\") return c(ANSI.red, \"error\");\n if (severity === \"warning\") return c(ANSI.yellow, \"warn \");\n return c(ANSI.dim, \"info \");\n}\n\n/** How many entries to list before collapsing into a \"+N more\" line. */\nconst LIST_LIMIT = 20;\n\nfunction listEntries(\n entries: DiffEntry[],\n marker: string,\n markerColor: string,\n c: (code: string, t: string) => string,\n): string[] {\n const lines: string[] = [];\n for (const entry of entries.slice(0, LIST_LIMIT)) {\n // A broken link's identity is (page, target): the same dead URL referenced\n // from four pages is four findings, and without the page they render as\n // four identical lines that look like a rendering bug.\n const where =\n entry.pageUrl && !entry.summary.includes(entry.pageUrl)\n ? c(ANSI.dim, ` on ${entry.pageUrl}`)\n : \"\";\n lines.push(\n ` ${c(markerColor, marker)} ${severityTag(c, entry.severity)} ${c(ANSI.dim, entry.kind)} ${entry.summary}${where}`,\n );\n }\n if (entries.length > LIST_LIMIT) {\n lines.push(` ${c(ANSI.dim, `… +${entries.length - LIST_LIMIT} more`)}`);\n }\n return lines;\n}\n\n/** Whole days between the baseline timestamp and now, or null if unparseable. */\nfunction baselineAgeDays(finishedAt: string, now: number): number | null {\n const taken = Date.parse(finishedAt);\n if (Number.isNaN(taken)) return null;\n return Math.max(0, Math.floor((now - taken) / 86_400_000));\n}\n\nexport function renderDiffTerminal(diff: ReportDiff, options: RenderOptions = {}): string {\n const c = paint(options.color ?? false);\n const lines: string[] = [];\n const debt = diff.unchanged;\n\n // Never green, and never the word \"clean\". This mode passes builds on sites\n // with known defects; a green flag would say the opposite of what happened,\n // and a reader who learns to see green here stops reading the number beside\n // it. The debt is the headline, not a footnote.\n const verdict =\n diff.added.length > 0\n ? c(ANSI.red, \"REGRESSION\")\n : debt > 0\n ? c(ANSI.yellow, \"REGRESSION GATE\")\n : c(ANSI.green, \"CLEAN\");\n\n const age = baselineAgeDays(diff.baseline.finishedAt, options.now ?? Date.now());\n\n lines.push(\"\");\n lines.push(`${c(ANSI.bold, \"goflag\")} ${c(ANSI.dim, \"--regressions-only\")}`);\n lines.push(\n `${verdict} ${diff.added.length} new · ` +\n (debt > 0\n ? c(ANSI.bold, `${debt} known findings NOT gating this build`)\n : c(ANSI.dim, \"no known findings\")) +\n (diff.resolved.length > 0 ? c(ANSI.dim, ` · ${diff.resolved.length} resolved`) : \"\"),\n );\n lines.push(\n c(\n ANSI.dim,\n `baseline ${diff.baseline.url} — taken ${diff.baseline.finishedAt}` +\n (age === null ? \"\" : age === 0 ? \" (today)\" : ` (${age} day${age === 1 ? \"\" : \"s\"} ago)`),\n ),\n );\n // Loud, not dim: a cross-profile diff still prints \"0 new\", and that number\n // means something different from what the reader assumes.\n if (diff.profileMismatch) {\n lines.push(\n c(\n ANSI.yellow,\n `note: baseline was captured under profile \\`${diff.profileMismatch.baseline}\\`, ` +\n `this run used \\`${diff.profileMismatch.current}\\` — the two are not like-for-like.`,\n ),\n );\n }\n lines.push(\"\");\n\n if (diff.added.length > 0) {\n lines.push(c(ANSI.bold, \"New findings\"));\n lines.push(...listEntries(diff.added, \"+\", ANSI.red, c));\n lines.push(\"\");\n }\n\n if (diff.resolved.length > 0) {\n lines.push(c(ANSI.bold, \"Resolved\"));\n lines.push(...listEntries(diff.resolved, \"-\", ANSI.green, c));\n lines.push(\"\");\n }\n\n if (diff.added.length === 0 && diff.resolved.length === 0) {\n lines.push(\n c(\n ANSI.dim,\n debt > 0\n ? `Nothing moved since the baseline — ${debt} findings still open. Run without --regressions-only to see them.`\n : \"Nothing moved since the baseline.\",\n ),\n );\n lines.push(\"\");\n }\n\n return lines.join(\"\\n\");\n}\n","/**\n * Boot-and-audit support for `--start`.\n *\n * goflag was only ever able to audit a deployed site. That makes it a post-hoc\n * auditor, and post-hoc auditors do not get run: by the time a finding appears,\n * the regression already shipped. `--start` closes that gap — boot the app,\n * wait until it answers, audit `localhost`, tear it down — so the same checks\n * can gate a merge instead of documenting a mistake.\n *\n * Deliberately dumb about readiness: rather than parsing a framework's stdout\n * for \"ready in 1.2s\" (every framework words it differently, and the wording\n * changes between minor versions), it polls the URL goflag is about to audit.\n * If that URL answers, the audit can proceed — which is the only definition of\n * \"ready\" that actually matters here.\n */\n\nimport { spawn, type ChildProcess } from \"node:child_process\";\n\nexport interface StartedServer {\n /** Terminate the process group and resolve once it is gone. */\n stop: () => Promise<void>;\n}\n\nexport interface StartServerOptions {\n /** Shell command to run, e.g. `pnpm start`. */\n command: string;\n /**\n * Directory to run it in. Defaults to the process's own, which is right for\n * the usual `cd my-site && npx goflag …` — but not for a monorepo audited\n * from its root, where the app lives a few directories down.\n */\n cwd?: string;\n /** URL polled until it answers; normally the audit entry point. */\n url: string;\n /** Give up after this long. Defaults to 60s. */\n timeoutMs?: number;\n /** Poll interval. Defaults to 250ms. */\n intervalMs?: number;\n /** Called with each stdout/stderr chunk, for `--verbose`. */\n onOutput?: (chunk: string) => void;\n /** Accept self-signed TLS while polling (a local https dev server). */\n allowInsecureTls?: boolean;\n}\n\n/** True once the URL returns any HTTP response at all. */\nasync function answers(url: string, allowInsecureTls: boolean): Promise<boolean> {\n const previous = process.env.NODE_TLS_REJECT_UNAUTHORIZED;\n if (allowInsecureTls) process.env.NODE_TLS_REJECT_UNAUTHORIZED = \"0\";\n try {\n // Any status counts: a 404 on the entry path still proves the server is\n // up, and the audit itself is what judges the response.\n await fetch(url, { redirect: \"manual\" });\n return true;\n } catch {\n return false;\n } finally {\n if (allowInsecureTls) {\n if (previous === undefined) delete process.env.NODE_TLS_REJECT_UNAUTHORIZED;\n else process.env.NODE_TLS_REJECT_UNAUTHORIZED = previous;\n }\n }\n}\n\nfunction terminate(child: ChildProcess): Promise<void> {\n return new Promise((resolve) => {\n if (child.exitCode !== null || child.signalCode !== null) {\n resolve();\n return;\n }\n\n // Kill the whole group: `pnpm start` spawns the real server as a child,\n // and signalling only the wrapper orphans a process holding the port.\n const killGroup = (signal: NodeJS.Signals) => {\n try {\n if (child.pid) process.kill(-child.pid, signal);\n else child.kill(signal);\n } catch {\n // Already dead, or the group is gone — nothing left to do.\n }\n };\n\n const force = setTimeout(() => killGroup(\"SIGKILL\"), 5_000);\n child.once(\"exit\", () => {\n clearTimeout(force);\n resolve();\n });\n killGroup(\"SIGTERM\");\n });\n}\n\n/**\n * Spawn `command` and resolve once `url` answers.\n *\n * Rejects — after stopping the process — when the command exits early or the\n * timeout expires. Both are hard failures: auditing a server that never came\n * up would report a site-wide outage as a hundred SEO findings.\n */\nexport async function startServer(options: StartServerOptions): Promise<StartedServer> {\n const timeoutMs = options.timeoutMs ?? 60_000;\n const intervalMs = options.intervalMs ?? 250;\n const allowInsecureTls = options.allowInsecureTls === true;\n\n const cwd = options.cwd ?? process.cwd();\n const child = spawn(options.command, {\n cwd,\n shell: true,\n // Own process group, so `terminate` can take the whole tree down.\n detached: true,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n let exited: { code: number | null; signal: NodeJS.Signals | null } | undefined;\n child.once(\"exit\", (code, signal) => {\n exited = { code, signal };\n });\n\n const tail: string[] = [];\n const record = (chunk: Buffer) => {\n const text = chunk.toString();\n options.onOutput?.(text);\n // Keep a short tail so a boot failure can be reported with its own words\n // instead of a bare \"command exited\".\n tail.push(text);\n if (tail.length > 40) tail.shift();\n };\n child.stdout?.on(\"data\", record);\n child.stderr?.on(\"data\", record);\n\n const server: StartedServer = { stop: () => terminate(child) };\n const deadline = Date.now() + timeoutMs;\n\n for (;;) {\n if (exited) {\n await server.stop();\n const why = exited.signal ? `signal ${exited.signal}` : `exit code ${exited.code}`;\n // Naming the directory is most of the diagnosis: a command that runs\n // fine by hand and dies here has almost always been started somewhere\n // other than where its package.json lives.\n throw new Error(\n `--start command exited before ${options.url} answered (${why}).\\n` +\n ` command: ${options.command}\\n` +\n ` in: ${cwd}\\n` +\n `${tail.join(\"\").trimEnd()}`,\n );\n }\n if (await answers(options.url, allowInsecureTls)) return server;\n if (Date.now() >= deadline) {\n await server.stop();\n throw new Error(\n `--start command did not serve ${options.url} within ${timeoutMs}ms.\\n` +\n ` command: ${options.command}\\n` +\n ` in: ${cwd}\\n` +\n `Raise --start-timeout, check the command listens on that URL, or point ` +\n `--start-cwd at the directory it should run in.`,\n );\n }\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n }\n}\n","/**\n * CLI argument parsing — split out from `cli.ts` so it can be unit-tested\n * without importing the module's top-level `main()` side effect.\n *\n * `parseArgs` is a pure function of `argv` (plus the ambient TTY / NO_COLOR\n * signals used only for the default color choice). It throws a plain `Error`\n * with a user-facing message on malformed input; the CLI turns that into an\n * exit-code-2 with the help text.\n */\n\nimport { PROFILE_NAMES, PROFILES } from \"./lib/rules/profiles\";\nimport type { AuditOptions, FailOn } from \"./report/build\";\nimport type { LogMode } from \"./report/logger\";\n\n/** `--profile` names and what each one means, indented for the help block. */\nconst PROFILE_HELP = PROFILE_NAMES.map(\n (name) => ` ${name} — ${PROFILES[name]!.description}`,\n).join(\"\\n\");\n\nexport const HELP = `goflag — audit a site for broken links, missing translations, and SEO metadata\n\nUsage:\n goflag <url> [options]\n\nOptions:\n --json Print the JSON report to stdout (nothing else).\n --summary, -s Roll findings up (dedup by link/rule/code). Pairs\n with --json for a compact, agent-friendly payload.\n --report <file> Write the (full) JSON report to <file>.\n --depth <n> Crawl depth (0 = entry page only). Default: 2.\n --max-pages <n> Hard cap on pages crawled. Default: 200.\n --coverage <mode> How to choose which pages to audit.\n \"structural\" (default when a sitemap is found) keeps\n every standalone page and samples families of pages\n built from one template. \"all\" audits everything the\n sitemap lists, up to --max-pages.\n --include <glob> Only crawl paths matching <glob> (repeatable).\n --exclude <glob> Skip paths matching <glob> (repeatable).\n --locales <list> Comma-separated locales the site serves, e.g.\n \"fr,en,pt-br\". Authoritative: overrides what the\n sitemap and crawl suggest, and makes a locale the\n site does not serve yet show up as missing.\n --ignore-holes <glob> Route (locale-free) that is deliberately not\n translated everywhere, so its gaps are not reported\n as missing translations (repeatable).\n --no-sitemap Do not discover the sitemap; crawl from <url> only.\n Discovery is on by default because link-only crawling\n cannot find locales a site never links to.\n --profile <name> Policy overlay on the rule set — it changes how\n loudly a rule fires, never what it observes:\n${PROFILE_HELP}\n --conformance Report EVERY rule's status on every page (pass /\n fail / warn / na), not just the violations. Answers\n \"where do we stand against the catalog?\"; pairs with\n --json.\n --advisories Attach the prose rules — the judgment calls goflag\n refuses to fake — each with the observed facts an\n agent needs to judge it. Never affects the gate.\n --fail-on <level> Exit 1 at or above this severity: warning (default),\n error, or never.\n --regressions-only Weaken the gate: fail only on findings that are NEW\n relative to --baseline. Known findings stop blocking\n the build — a green run no longer means a clean site.\n Requires --baseline.\n --baseline <file> Stored report to compare against. Use with\n --regressions-only.\n --update-baseline Write this run to --baseline and exit 0, instead of\n judging against it. Use it to capture a baseline, or\n to accept findings you have decided to live with —\n it says what it accepted.\n --max-debt <n> Fail when the site carries more than <n> findings in\n total, whether new or known. Lower it as you fix, to\n stop a baseline from fossilising.\n --start <cmd> Boot <cmd>, wait for <url> to answer, audit, then\n stop it. Use to gate a merge on the built app before\n it ships.\n --start-cwd <dir> Directory to run --start in. Defaults to the current\n one; set it when auditing a monorepo package from the\n repository root.\n --start-timeout <ms> How long to wait for --start to answer. Default: 60000.\n --no-external Do not probe off-origin (external) links.\n --static Static HTML only; never launch headless Chromium.\n --allow-insecure-tls Accept self-signed / invalid TLS (localhost, tunnels).\n --timeout <ms> Per-request timeout in ms. Default: 8000.\n --verbose, -V Log every page as it is analyzed.\n --quiet, -q Suppress the live progress output.\n --no-color Disable coloured output.\n -h, --help Show this help.\n -v, --version Show the version.\n\nExit codes: 0 clean, 1 findings found, 2 fatal error.`;\n\nexport interface ParsedArgs {\n url?: string;\n json: boolean;\n summary: boolean;\n report?: string;\n color: boolean;\n help: boolean;\n version: boolean;\n /** Live-progress verbosity for the logger. */\n logMode: LogMode;\n /** Severity at or above which the process exits 1. */\n failOn: FailOn;\n /** Path to a stored report to compare against (`--baseline`). */\n baseline?: string;\n /** Gate on new findings only, letting known ones through. */\n regressionsOnly: boolean;\n /** Write the run to `--baseline` rather than judging against it. */\n updateBaseline: boolean;\n /** Hard ceiling on total findings, new or known. */\n maxDebt?: number;\n /** Command to boot before auditing, and stop afterwards (`--start`). */\n start?: string;\n /** How long to wait for `--start` to answer, in ms. */\n startTimeoutMs: number;\n /** Directory to run the `--start` command in. */\n startCwd?: string;\n options: AuditOptions;\n}\n\nconst FAIL_ON_LEVELS: readonly FailOn[] = [\"warning\", \"error\", \"never\"];\n\nexport function parseArgs(argv: string[]): ParsedArgs {\n const parsed: ParsedArgs = {\n json: false,\n summary: false,\n color: process.stdout.isTTY === true && !process.env.NO_COLOR,\n help: false,\n version: false,\n logMode: \"compact\",\n failOn: \"warning\",\n regressionsOnly: false,\n updateBaseline: false,\n startTimeoutMs: 60_000,\n options: { include: [], exclude: [] },\n };\n\n const next = (i: number, flag: string): string => {\n const value = argv[i + 1];\n if (value === undefined) throw new Error(`${flag} requires a value`);\n return value;\n };\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n switch (arg) {\n case \"-h\":\n case \"--help\":\n parsed.help = true;\n break;\n case \"-v\":\n case \"--version\":\n parsed.version = true;\n break;\n case \"--json\":\n parsed.json = true;\n break;\n case \"-s\":\n case \"--summary\":\n parsed.summary = true;\n break;\n case \"--no-color\":\n parsed.color = false;\n break;\n case \"-V\":\n case \"--verbose\":\n parsed.logMode = \"verbose\";\n break;\n case \"-q\":\n case \"--quiet\":\n parsed.logMode = \"quiet\";\n break;\n case \"--static\":\n parsed.options.static = true;\n break;\n case \"--allow-insecure-tls\":\n parsed.options.allowInsecureTls = true;\n break;\n case \"--no-external\":\n parsed.options.checkExternal = false;\n break;\n case \"--no-sitemap\":\n parsed.options.noSitemap = true;\n break;\n case \"--conformance\":\n parsed.options.conformance = true;\n break;\n case \"--advisories\":\n parsed.options.advisories = true;\n break;\n case \"--ignore-holes\":\n parsed.options.ignoreHoles = [...(parsed.options.ignoreHoles ?? []), next(i, arg)];\n i++;\n break;\n case \"--locales\": {\n // Split on commas so `--locales fr,en` and repeated flags both work.\n const value = next(i, arg);\n const tags = value\n .split(\",\")\n .map((t) => t.trim())\n .filter(Boolean);\n if (tags.length === 0) throw new Error(`${arg} expects at least one locale`);\n parsed.options.locales = [...(parsed.options.locales ?? []), ...tags];\n i++;\n break;\n }\n case \"--profile\": {\n const value = next(i, arg);\n // Validated here, not in `runAudit`: a mistyped policy should cost\n // an argument-parse error, not a crawl.\n if (!PROFILE_NAMES.includes(value)) {\n throw new Error(`${arg} expects one of: ${PROFILE_NAMES.join(\", \")}`);\n }\n parsed.options.profile = value;\n i++;\n break;\n }\n case \"--fail-on\": {\n const value = next(i, arg) as FailOn;\n if (!FAIL_ON_LEVELS.includes(value)) {\n throw new Error(`${arg} expects one of: ${FAIL_ON_LEVELS.join(\", \")}`);\n }\n parsed.failOn = value;\n i++;\n break;\n }\n case \"--start\":\n parsed.start = next(i, arg);\n i++;\n break;\n case \"--start-timeout\":\n parsed.startTimeoutMs = toInt(next(i, arg), arg);\n i++;\n break;\n case \"--start-cwd\":\n parsed.startCwd = next(i, arg);\n i++;\n break;\n case \"--report\":\n parsed.report = next(i, arg);\n i++;\n break;\n case \"--baseline\":\n parsed.baseline = next(i, arg);\n i++;\n break;\n case \"--update-baseline\":\n parsed.updateBaseline = true;\n break;\n case \"--regressions-only\":\n parsed.regressionsOnly = true;\n break;\n case \"--max-debt\":\n parsed.maxDebt = toInt(next(i, arg), arg);\n i++;\n break;\n case \"--depth\":\n parsed.options.depth = toInt(next(i, arg), arg);\n i++;\n break;\n case \"--coverage\": {\n const mode = next(i, arg);\n if (mode !== \"structural\" && mode !== \"all\") {\n throw new Error(`--coverage takes \"structural\" or \"all\", not ${JSON.stringify(mode)}`);\n }\n parsed.options.coverage = mode;\n i += 1;\n break;\n }\n case \"--max-pages\":\n parsed.options.maxPages = toInt(next(i, arg), arg);\n i++;\n break;\n case \"--timeout\":\n parsed.options.timeoutMs = toInt(next(i, arg), arg);\n i++;\n break;\n case \"--include\":\n parsed.options.include!.push(next(i, arg));\n i++;\n break;\n case \"--exclude\":\n parsed.options.exclude!.push(next(i, arg));\n i++;\n break;\n default:\n if (arg && arg.startsWith(\"-\")) throw new Error(`unknown option: ${arg}`);\n if (arg && !parsed.url) parsed.url = arg;\n else if (arg) throw new Error(`unexpected argument: ${arg}`);\n }\n }\n\n // Each flag is meaningless without the other, and guessing which one the\n // caller meant would silently change how strict the build is.\n if (parsed.updateBaseline && !parsed.baseline) {\n throw new Error(\"--update-baseline needs a --baseline <file> to write to\");\n }\n // Capturing a baseline is not gating against one, so the explicit opt-in that\n // --regressions-only exists to force does not apply.\n if (parsed.baseline && !parsed.regressionsOnly && !parsed.updateBaseline) {\n throw new Error(\n \"--baseline weakens the gate, so it must be requested explicitly: add --regressions-only\",\n );\n }\n if (parsed.regressionsOnly && !parsed.baseline) {\n throw new Error(\"--regressions-only needs a --baseline <file> to compare against\");\n }\n\n return parsed;\n}\n\nfunction toInt(value: string, flag: string): number {\n const n = Number(value);\n if (!Number.isInteger(n) || n < 0) throw new Error(`${flag} expects a non-negative integer`);\n return n;\n}\n","/**\n * goflag CLI.\n *\n * goflag <url> [options]\n *\n * Crawls a site and reports broken links, missing translation pages, and\n * missing/misconfigured SEO metadata. The JSON report is the source of\n * truth; the terminal output is rendered from it.\n *\n * Exit codes:\n * 0 clean (green flag)\n * 1 findings present (yellow/red flag) — useful as a CI gate\n * 2 fatal (bad URL, unexpected error)\n */\n\nimport { runAudit, exitCode } from \"./report/build\";\nimport { diffReports, diffExitCode, totalFindings } from \"./report/diff\";\nimport { renderDiffTerminal } from \"./report/render-diff\";\nimport { startServer, type StartedServer } from \"./lib/runner/dev-server\";\nimport { renderTerminal } from \"./report/render-terminal\";\nimport { renderSummaryTerminal } from \"./report/render-summary\";\nimport { summarize } from \"./report/summarize\";\nimport { Logger } from \"./report/logger\";\nimport { HELP, parseArgs, type ParsedArgs } from \"./cli-args\";\n\nasync function readVersion(): Promise<string> {\n try {\n const { readFileSync } = await import(\"node:fs\");\n const { fileURLToPath } = await import(\"node:url\");\n const { dirname, join } = await import(\"node:path\");\n const here = dirname(fileURLToPath(import.meta.url));\n const pkg = JSON.parse(readFileSync(join(here, \"..\", \"package.json\"), \"utf8\"));\n return typeof pkg.version === \"string\" ? pkg.version : \"0.0.0\";\n } catch {\n return \"0.0.0\";\n }\n}\n\n/**\n * Write JSON to a path, creating the directory it names.\n *\n * `writeFileSync` does not, and the one moment that matters is the first: a\n * repository adopting the gate runs `--update-baseline --baseline\n * .goflag/baseline.json` with no `.goflag/` yet, and the command that the\n * runbook documents as step one is the only one that fails. The audit had\n * already run at that point, so the failure also threw away several minutes of\n * crawling.\n */\nasync function writeJson(path: string, value: unknown): Promise<void> {\n const { mkdirSync, writeFileSync } = await import(\"node:fs\");\n const { dirname } = await import(\"node:path\");\n\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, `${JSON.stringify(value, null, 2)}\\n`, \"utf8\");\n}\n\nasync function main(): Promise<number> {\n let args: ParsedArgs;\n try {\n args = parseArgs(process.argv.slice(2));\n } catch (err) {\n process.stderr.write(`goflag: ${(err as Error).message}\\n\\n${HELP}\\n`);\n return 2;\n }\n\n if (args.help) {\n process.stdout.write(`${HELP}\\n`);\n return 0;\n }\n if (args.version) {\n process.stdout.write(`${await readVersion()}\\n`);\n return 0;\n }\n if (!args.url) {\n process.stderr.write(`goflag: missing <url>\\n\\n${HELP}\\n`);\n return 2;\n }\n\n // Progress always goes to stderr, so stdout (JSON / rendered report)\n // stays clean and pipeable. Colour follows the stderr TTY.\n const logger = new Logger({\n stream: process.stderr,\n mode: args.logMode,\n color: process.stderr.isTTY === true && !process.env.NO_COLOR,\n });\n\n // `--start` owns a child process for the whole audit; it must be torn down\n // on every exit path, including a failed audit, or CI leaks a held port.\n let server: StartedServer | undefined;\n let report;\n try {\n if (args.start) {\n logger.note(`goflag: starting \\`${args.start}\\` …`);\n server = await startServer({\n command: args.start,\n cwd: args.startCwd,\n url: args.url,\n timeoutMs: args.startTimeoutMs,\n allowInsecureTls: args.options.allowInsecureTls,\n onOutput: args.logMode === \"verbose\" ? (chunk) => process.stderr.write(chunk) : undefined,\n });\n }\n logger.note(`goflag: auditing ${args.url} …`);\n report = await runAudit(args.url, { ...args.options, onProgress: logger.onProgress });\n logger.stop();\n } catch (err) {\n logger.stop();\n process.stderr.write(`goflag: ${(err as Error).message}\\n`);\n return 2;\n } finally {\n await server?.stop();\n }\n\n // A sitemap that timed out is not a site without one. The crawl seeds from\n // the sitemap when there is one, so losing it drops the audit to link\n // following and it sees a fraction of the pages — measured on\n // openfinanceguide, 46 instead of 600, and 5 findings instead of 279.\n //\n // Reporting that as a result is how a gate goes red on nothing, or green on\n // 8% of a site. Neither is worth having, so this is fatal: the run could not\n // ask the question it was asked to ask.\n const sitemap = report.diagnostics.sitemap;\n if (sitemap?.unreachable) {\n process.stderr.write(\n `goflag: the sitemap could not be fetched — ${sitemap.unreachable}\\n` +\n `goflag: this run crawled links instead, so it saw a different site ` +\n `from one that reads the sitemap, and its findings are not comparable.\\n` +\n `goflag: retry, or pass --no-sitemap to audit by crawling on purpose.\\n`,\n );\n return 2;\n }\n\n // A baseline changes the question from \"is this site clean?\" to \"did this\n // change make it worse?\" — the only one a site with a backlog can answer.\n if (args.baseline) {\n const { existsSync, readFileSync } = await import(\"node:fs\");\n // Capturing a baseline for the first time is the one case where the file\n // is legitimately absent. Everywhere else a missing baseline is a typo,\n // and continuing would turn it into a green build.\n if (existsSync(args.baseline) || !args.updateBaseline) {\n try {\n const baseline = JSON.parse(readFileSync(args.baseline, \"utf8\"));\n report.diff = diffReports(baseline, report);\n } catch (err) {\n process.stderr.write(\n `goflag: could not read baseline ${args.baseline}: ${(err as Error).message}\\n`,\n );\n return 2;\n }\n }\n }\n\n // Writing the baseline is accepting everything in it. The one thing this\n // must not do is accept it quietly: a counter that drops without explanation\n // reads as \"the problem went away\".\n if (args.updateBaseline && args.baseline) {\n await writeJson(args.baseline, report);\n const total = totalFindings(report);\n if (report.diff) {\n const { added, resolved } = report.diff;\n process.stdout.write(\n `goflag: baseline updated — ${added.length} newly accepted, ${resolved.length} resolved, ` +\n `${total} findings now grandfathered in ${args.baseline}\\n`,\n );\n if (added.length > 0) {\n process.stdout.write(`${renderDiffTerminal(report.diff, { color: args.color })}\\n`);\n }\n } else {\n process.stdout.write(\n `goflag: baseline captured — ${total} findings grandfathered in ${args.baseline}\\n`,\n );\n }\n process.stdout.write(\n `goflag: set --max-debt ${total} to stop that number growing, and lower it as you fix.\\n`,\n );\n return 0;\n }\n\n if (args.report) {\n // The report file is always the full report — the source of truth a\n // baseline/diff can rely on, regardless of the --summary view choice.\n await writeJson(args.report, report);\n process.stderr.write(`goflag: report written to ${args.report}\\n`);\n }\n\n // In baseline mode the diff *is* the answer. Printing the full report first\n // buries \"nothing changed\" under a hundred known findings — the reader stops\n // looking, which is precisely the failure the baseline exists to prevent.\n // The complete report stays available via --json and --report.\n if (report.diff && !args.json) {\n process.stdout.write(`${renderDiffTerminal(report.diff, { color: args.color })}\\n`);\n const total = totalFindings(report);\n if (args.maxDebt !== undefined && total > args.maxDebt) {\n process.stderr.write(\n `goflag: ${total} findings exceeds the --max-debt budget of ${args.maxDebt}.\\n`,\n );\n }\n return diffExitCode(report.diff, args.failOn, { total, max: args.maxDebt });\n }\n\n if (args.summary) {\n const summary = summarize(report);\n process.stdout.write(\n args.json\n ? `${JSON.stringify(summary, null, 2)}\\n`\n : `${renderSummaryTerminal(summary, { color: args.color })}\\n`,\n );\n } else {\n process.stdout.write(\n args.json\n ? `${JSON.stringify(report, null, 2)}\\n`\n : `${renderTerminal(report, { color: args.color })}\\n`,\n );\n }\n\n const total = totalFindings(report);\n if (args.maxDebt !== undefined && total > args.maxDebt) {\n process.stderr.write(\n `goflag: ${total} findings exceeds the --max-debt budget of ${args.maxDebt}.\\n`,\n );\n return 1;\n }\n\n if (report.diff) {\n return diffExitCode(report.diff, args.failOn, { total, max: args.maxDebt });\n }\n\n return exitCode(report, args.failOn);\n}\n\nmain()\n .then((code) => process.exit(code))\n .catch((err) => {\n process.stderr.write(`goflag: unexpected error: ${(err as Error).stack ?? err}\\n`);\n process.exit(2);\n });\n"],"mappings":"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","names":["total"]}
|
|
1
|
+
{"version":3,"sources":["../src/report/diff.ts","../src/report/render-diff.ts","../src/lib/runner/dev-server.ts","../src/cli-args.ts","../src/lib/rules/sources/index.ts","../src/lib/rules/catalog.ts","../src/cli.ts"],"sourcesContent":["/**\n * Baseline comparison.\n *\n * Every finding already carries a `fingerprint` id derived from the *identity*\n * of the problem rather than its transient details, and page URLs are\n * normalised to origin-independent routes — so the same defect fingerprints\n * identically on localhost, staging and production. That machinery was built\n * for exactly this and had no consumer.\n *\n * What it changes is what the tool *is*. Without a baseline the CI gate is\n * binary: any finding fails, which is unusable on a site that is not clean\n * yet, so teams either never turn it on or learn to ignore it. With one, the\n * gate becomes \"no *new* findings\" — a promise a real codebase can keep while\n * the backlog is worked down.\n *\n * Pure function of two reports. No I/O; the CLI reads the baseline file.\n */\n\nimport type { Severity } from \"../lib/core/types\";\nimport { DEFAULT_PROFILE } from \"../lib/rules/profiles\";\nimport type { GoflagReport } from \"./types\";\n\n/** Which part of the report a finding came from. */\nexport type FindingKind =\n \"brokenLink\" | \"unreachablePage\" | \"translationHole\" | \"reciprocity\" | \"seo\" | \"site\";\n\n/** One finding, flattened out of its report section for comparison. */\nexport interface DiffEntry {\n id: string;\n kind: FindingKind;\n /**\n * Severity used for the exit-code decision. Rule findings carry their own;\n * the rest are mapped to match the verdict logic in `build.ts` — a broken\n * link or an unreachable page is an error, a translation gap is a warning.\n */\n severity: Severity;\n /** One-line description, for the rendered diff. */\n summary: string;\n /** Page the finding sits on, when it has one. */\n pageUrl?: string;\n}\n\nexport interface ReportDiff {\n /** Where, when, and under which rule profile the baseline was taken. */\n baseline: { url: string; finishedAt: string; profile: string };\n /**\n * Set when this run was judged under a different profile than the baseline\n * was captured under. Not an error — comparing `spec-only` against a\n * `default` baseline is legitimate, it is just not like-for-like, and\n * \"0 new findings\" means less than it appears. So it is reported loudly\n * and gates nothing: silently diffing across two policies is the failure\n * mode worth preventing.\n */\n profileMismatch?: { baseline: string; current: string };\n /** Findings present now and absent from the baseline — what should fail CI. */\n added: DiffEntry[];\n /** Findings in the baseline that are gone. Worth showing: progress is data too. */\n resolved: DiffEntry[];\n /** Findings present in both. */\n unchanged: number;\n}\n\n/** Flatten every finding collection into one comparable list. */\nexport function collectFindings(report: GoflagReport): DiffEntry[] {\n const out: DiffEntry[] = [];\n\n for (const link of report.brokenLinks) {\n out.push({\n id: link.id,\n kind: \"brokenLink\",\n severity: \"error\",\n summary: `${link.verdict} link → ${link.href}`,\n pageUrl: link.pageUrl,\n });\n }\n\n for (const page of report.unreachablePages) {\n out.push({\n id: page.id,\n kind: \"unreachablePage\",\n severity: \"error\",\n summary: `${page.status || \"network error\"} on ${page.url}`,\n pageUrl: page.url,\n });\n }\n\n for (const hole of report.missingTranslations.holes) {\n out.push({\n id: hole.id,\n kind: \"translationHole\",\n severity: \"warning\",\n summary: `${hole.route} missing ${hole.missingLocales.join(\", \")}`,\n });\n }\n\n for (const issue of report.missingTranslations.reciprocity) {\n out.push({\n id: issue.id,\n kind: \"reciprocity\",\n severity: \"warning\",\n summary: `${issue.code} on ${issue.url}`,\n pageUrl: issue.url,\n });\n }\n\n for (const issue of report.seoIssues) {\n out.push({\n id: issue.id,\n kind: \"seo\",\n severity: issue.severity,\n summary: `${issue.ruleId} on ${issue.pageUrl}`,\n pageUrl: issue.pageUrl,\n });\n }\n\n for (const issue of report.siteIssues) {\n out.push({\n id: issue.id,\n kind: \"site\",\n severity: issue.severity,\n summary: `${issue.ruleId} on ${issue.pageUrl}`,\n pageUrl: issue.pageUrl,\n });\n }\n\n return out;\n}\n\n/** Deterministic ordering so a rendered diff is stable and reviewable. */\nconst SEVERITY_RANK: Record<Severity, number> = { error: 0, warning: 1, info: 2 };\n\nfunction sortEntries(entries: DiffEntry[]): DiffEntry[] {\n return [...entries].sort(\n (a, b) =>\n SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity] ||\n a.kind.localeCompare(b.kind) ||\n a.summary.localeCompare(b.summary),\n );\n}\n\n/** Total findings the site currently carries, new and known alike. */\nexport function totalFindings(report: GoflagReport): number {\n return collectFindings(report).length;\n}\n\n/** Compare a fresh report against a stored baseline. */\nexport function diffReports(baseline: GoflagReport, current: GoflagReport): ReportDiff {\n const before = new Map(collectFindings(baseline).map((e) => [e.id, e]));\n const now = new Map(collectFindings(current).map((e) => [e.id, e]));\n\n const added: DiffEntry[] = [];\n let unchanged = 0;\n for (const [id, entry] of now) {\n if (before.has(id)) unchanged += 1;\n else added.push(entry);\n }\n\n const resolved: DiffEntry[] = [];\n for (const [id, entry] of before) {\n if (!now.has(id)) resolved.push(entry);\n }\n\n // A baseline stored before profiles existed has no `profile`; the behaviour\n // it captured is what `default` still does, so that is the honest reading.\n const baselineProfile = baseline.profile ?? DEFAULT_PROFILE;\n const currentProfile = current.profile ?? DEFAULT_PROFILE;\n\n return {\n baseline: { url: baseline.url, finishedAt: baseline.finishedAt, profile: baselineProfile },\n added: sortEntries(added),\n resolved: sortEntries(resolved),\n unchanged,\n ...(baselineProfile === currentProfile\n ? {}\n : { profileMismatch: { baseline: baselineProfile, current: currentProfile } }),\n };\n}\n\n/**\n * Exit code under `--regressions-only`: fail on findings that are *new* at or\n * above the threshold, or on a debt budget being exceeded.\n *\n * Deliberately blind to the overall verdict, unlike `exitCode`. A site with a\n * hundred known problems and no new ones passes — that is what the mode is\n * for, and why it has to be asked for by name.\n *\n * `maxDebt` is the counterweight. Gating on regressions alone lets a backlog\n * sit untouched forever behind a passing build; a ceiling you lower as you fix\n * is the only part of this design that makes the debt actually go down.\n */\nexport function diffExitCode(\n diff: ReportDiff,\n failOn: \"warning\" | \"error\" | \"never\",\n debt?: { total: number; max?: number },\n): number {\n if (debt?.max !== undefined && debt.total > debt.max) return 1;\n if (failOn === \"never\") return 0;\n const threshold = failOn === \"error\" ? 0 : 1;\n const blocking = diff.added.filter((e) => SEVERITY_RANK[e.severity] <= threshold);\n return blocking.length > 0 ? 1 : 0;\n}\n","/**\n * Render a `ReportDiff` for a terminal.\n *\n * Deliberately leads with what is new, because that is what fails the build\n * and what the reader has to act on. Resolved findings come second and are\n * kept — a run that only ever shows problems gives no signal that the backlog\n * is moving, which is how a gate stops being read.\n *\n * Pure function of the diff; no I/O.\n */\n\nimport type { Severity } from \"../lib/core/types\";\nimport type { DiffEntry, ReportDiff } from \"./diff\";\n\ninterface RenderOptions {\n color?: boolean;\n /** Injected clock, so the rendered age is testable. */\n now?: number;\n}\n\nconst ANSI = {\n reset: \"\\x1b[0m\",\n bold: \"\\x1b[1m\",\n dim: \"\\x1b[2m\",\n red: \"\\x1b[31m\",\n green: \"\\x1b[32m\",\n yellow: \"\\x1b[33m\",\n cyan: \"\\x1b[36m\",\n};\n\nfunction paint(enabled: boolean) {\n return (code: string, text: string) => (enabled ? `${code}${text}${ANSI.reset}` : text);\n}\n\nfunction severityTag(c: (code: string, t: string) => string, severity: Severity): string {\n if (severity === \"error\") return c(ANSI.red, \"error\");\n if (severity === \"warning\") return c(ANSI.yellow, \"warn \");\n return c(ANSI.dim, \"info \");\n}\n\n/** How many entries to list before collapsing into a \"+N more\" line. */\nconst LIST_LIMIT = 20;\n\nfunction listEntries(\n entries: DiffEntry[],\n marker: string,\n markerColor: string,\n c: (code: string, t: string) => string,\n): string[] {\n const lines: string[] = [];\n for (const entry of entries.slice(0, LIST_LIMIT)) {\n // A broken link's identity is (page, target): the same dead URL referenced\n // from four pages is four findings, and without the page they render as\n // four identical lines that look like a rendering bug.\n const where =\n entry.pageUrl && !entry.summary.includes(entry.pageUrl)\n ? c(ANSI.dim, ` on ${entry.pageUrl}`)\n : \"\";\n lines.push(\n ` ${c(markerColor, marker)} ${severityTag(c, entry.severity)} ${c(ANSI.dim, entry.kind)} ${entry.summary}${where}`,\n );\n }\n if (entries.length > LIST_LIMIT) {\n lines.push(` ${c(ANSI.dim, `… +${entries.length - LIST_LIMIT} more`)}`);\n }\n return lines;\n}\n\n/** Whole days between the baseline timestamp and now, or null if unparseable. */\nfunction baselineAgeDays(finishedAt: string, now: number): number | null {\n const taken = Date.parse(finishedAt);\n if (Number.isNaN(taken)) return null;\n return Math.max(0, Math.floor((now - taken) / 86_400_000));\n}\n\nexport function renderDiffTerminal(diff: ReportDiff, options: RenderOptions = {}): string {\n const c = paint(options.color ?? false);\n const lines: string[] = [];\n const debt = diff.unchanged;\n\n // Never green, and never the word \"clean\". This mode passes builds on sites\n // with known defects; a green flag would say the opposite of what happened,\n // and a reader who learns to see green here stops reading the number beside\n // it. The debt is the headline, not a footnote.\n const verdict =\n diff.added.length > 0\n ? c(ANSI.red, \"REGRESSION\")\n : debt > 0\n ? c(ANSI.yellow, \"REGRESSION GATE\")\n : c(ANSI.green, \"CLEAN\");\n\n const age = baselineAgeDays(diff.baseline.finishedAt, options.now ?? Date.now());\n\n lines.push(\"\");\n lines.push(`${c(ANSI.bold, \"goflag\")} ${c(ANSI.dim, \"--regressions-only\")}`);\n lines.push(\n `${verdict} ${diff.added.length} new · ` +\n (debt > 0\n ? c(ANSI.bold, `${debt} known findings NOT gating this build`)\n : c(ANSI.dim, \"no known findings\")) +\n (diff.resolved.length > 0 ? c(ANSI.dim, ` · ${diff.resolved.length} resolved`) : \"\"),\n );\n lines.push(\n c(\n ANSI.dim,\n `baseline ${diff.baseline.url} — taken ${diff.baseline.finishedAt}` +\n (age === null ? \"\" : age === 0 ? \" (today)\" : ` (${age} day${age === 1 ? \"\" : \"s\"} ago)`),\n ),\n );\n // Loud, not dim: a cross-profile diff still prints \"0 new\", and that number\n // means something different from what the reader assumes.\n if (diff.profileMismatch) {\n lines.push(\n c(\n ANSI.yellow,\n `note: baseline was captured under profile \\`${diff.profileMismatch.baseline}\\`, ` +\n `this run used \\`${diff.profileMismatch.current}\\` — the two are not like-for-like.`,\n ),\n );\n }\n lines.push(\"\");\n\n if (diff.added.length > 0) {\n lines.push(c(ANSI.bold, \"New findings\"));\n lines.push(...listEntries(diff.added, \"+\", ANSI.red, c));\n lines.push(\"\");\n }\n\n if (diff.resolved.length > 0) {\n lines.push(c(ANSI.bold, \"Resolved\"));\n lines.push(...listEntries(diff.resolved, \"-\", ANSI.green, c));\n lines.push(\"\");\n }\n\n if (diff.added.length === 0 && diff.resolved.length === 0) {\n lines.push(\n c(\n ANSI.dim,\n debt > 0\n ? `Nothing moved since the baseline — ${debt} findings still open. Run without --regressions-only to see them.`\n : \"Nothing moved since the baseline.\",\n ),\n );\n lines.push(\"\");\n }\n\n return lines.join(\"\\n\");\n}\n","/**\n * Boot-and-audit support for `--start`.\n *\n * goflag was only ever able to audit a deployed site. That makes it a post-hoc\n * auditor, and post-hoc auditors do not get run: by the time a finding appears,\n * the regression already shipped. `--start` closes that gap — boot the app,\n * wait until it answers, audit `localhost`, tear it down — so the same checks\n * can gate a merge instead of documenting a mistake.\n *\n * Deliberately dumb about readiness: rather than parsing a framework's stdout\n * for \"ready in 1.2s\" (every framework words it differently, and the wording\n * changes between minor versions), it polls the URL goflag is about to audit.\n * If that URL answers, the audit can proceed — which is the only definition of\n * \"ready\" that actually matters here.\n */\n\nimport { spawn, type ChildProcess } from \"node:child_process\";\n\nexport interface StartedServer {\n /** Terminate the process group and resolve once it is gone. */\n stop: () => Promise<void>;\n}\n\nexport interface StartServerOptions {\n /** Shell command to run, e.g. `pnpm start`. */\n command: string;\n /**\n * Directory to run it in. Defaults to the process's own, which is right for\n * the usual `cd my-site && npx goflag …` — but not for a monorepo audited\n * from its root, where the app lives a few directories down.\n */\n cwd?: string;\n /** URL polled until it answers; normally the audit entry point. */\n url: string;\n /** Give up after this long. Defaults to 60s. */\n timeoutMs?: number;\n /** Poll interval. Defaults to 250ms. */\n intervalMs?: number;\n /** Called with each stdout/stderr chunk, for `--verbose`. */\n onOutput?: (chunk: string) => void;\n /** Accept self-signed TLS while polling (a local https dev server). */\n allowInsecureTls?: boolean;\n}\n\n/** True once the URL returns any HTTP response at all. */\nasync function answers(url: string, allowInsecureTls: boolean): Promise<boolean> {\n const previous = process.env.NODE_TLS_REJECT_UNAUTHORIZED;\n if (allowInsecureTls) process.env.NODE_TLS_REJECT_UNAUTHORIZED = \"0\";\n try {\n // Any status counts: a 404 on the entry path still proves the server is\n // up, and the audit itself is what judges the response.\n await fetch(url, { redirect: \"manual\" });\n return true;\n } catch {\n return false;\n } finally {\n if (allowInsecureTls) {\n if (previous === undefined) delete process.env.NODE_TLS_REJECT_UNAUTHORIZED;\n else process.env.NODE_TLS_REJECT_UNAUTHORIZED = previous;\n }\n }\n}\n\nfunction terminate(child: ChildProcess): Promise<void> {\n return new Promise((resolve) => {\n if (child.exitCode !== null || child.signalCode !== null) {\n resolve();\n return;\n }\n\n // Kill the whole group: `pnpm start` spawns the real server as a child,\n // and signalling only the wrapper orphans a process holding the port.\n const killGroup = (signal: NodeJS.Signals) => {\n try {\n if (child.pid) process.kill(-child.pid, signal);\n else child.kill(signal);\n } catch {\n // Already dead, or the group is gone — nothing left to do.\n }\n };\n\n const force = setTimeout(() => killGroup(\"SIGKILL\"), 5_000);\n child.once(\"exit\", () => {\n clearTimeout(force);\n resolve();\n });\n killGroup(\"SIGTERM\");\n });\n}\n\n/**\n * Spawn `command` and resolve once `url` answers.\n *\n * Rejects — after stopping the process — when the command exits early or the\n * timeout expires. Both are hard failures: auditing a server that never came\n * up would report a site-wide outage as a hundred SEO findings.\n */\nexport async function startServer(options: StartServerOptions): Promise<StartedServer> {\n const timeoutMs = options.timeoutMs ?? 60_000;\n const intervalMs = options.intervalMs ?? 250;\n const allowInsecureTls = options.allowInsecureTls === true;\n\n const cwd = options.cwd ?? process.cwd();\n const child = spawn(options.command, {\n cwd,\n shell: true,\n // Own process group, so `terminate` can take the whole tree down.\n detached: true,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n let exited: { code: number | null; signal: NodeJS.Signals | null } | undefined;\n child.once(\"exit\", (code, signal) => {\n exited = { code, signal };\n });\n\n const tail: string[] = [];\n const record = (chunk: Buffer) => {\n const text = chunk.toString();\n options.onOutput?.(text);\n // Keep a short tail so a boot failure can be reported with its own words\n // instead of a bare \"command exited\".\n tail.push(text);\n if (tail.length > 40) tail.shift();\n };\n child.stdout?.on(\"data\", record);\n child.stderr?.on(\"data\", record);\n\n const server: StartedServer = { stop: () => terminate(child) };\n const deadline = Date.now() + timeoutMs;\n\n for (;;) {\n if (exited) {\n await server.stop();\n const why = exited.signal ? `signal ${exited.signal}` : `exit code ${exited.code}`;\n // Naming the directory is most of the diagnosis: a command that runs\n // fine by hand and dies here has almost always been started somewhere\n // other than where its package.json lives.\n throw new Error(\n `--start command exited before ${options.url} answered (${why}).\\n` +\n ` command: ${options.command}\\n` +\n ` in: ${cwd}\\n` +\n `${tail.join(\"\").trimEnd()}`,\n );\n }\n if (await answers(options.url, allowInsecureTls)) return server;\n if (Date.now() >= deadline) {\n await server.stop();\n throw new Error(\n `--start command did not serve ${options.url} within ${timeoutMs}ms.\\n` +\n ` command: ${options.command}\\n` +\n ` in: ${cwd}\\n` +\n `Raise --start-timeout, check the command listens on that URL, or point ` +\n `--start-cwd at the directory it should run in.`,\n );\n }\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n }\n}\n","/**\n * CLI argument parsing — split out from `cli.ts` so it can be unit-tested\n * without importing the module's top-level `main()` side effect.\n *\n * `parseArgs` is a pure function of `argv` (plus the ambient TTY / NO_COLOR\n * signals used only for the default color choice). It throws a plain `Error`\n * with a user-facing message on malformed input; the CLI turns that into an\n * exit-code-2 with the help text.\n */\n\nimport { PROFILE_NAMES, PROFILES } from \"./lib/rules/profiles\";\nimport type { AuditOptions, FailOn } from \"./report/build\";\nimport type { LogMode } from \"./report/logger\";\n\n/** `--profile` names and what each one means, indented for the help block. */\nconst PROFILE_HELP = PROFILE_NAMES.map(\n (name) => ` ${name} — ${PROFILES[name]!.description}`,\n).join(\"\\n\");\n\nexport const HELP = `goflag — audit a site for broken links, missing translations, and SEO metadata\n\nUsage:\n goflag <url> [options]\n goflag rules Print the rule catalogue as JSON and exit. No crawl\n and no network: every rule with its severity, its\n rigor and the documents it cites.\n\nOptions:\n --json Print the JSON report to stdout (nothing else).\n --summary, -s Roll findings up (dedup by link/rule/code). Pairs\n with --json for a compact, agent-friendly payload.\n --report <file> Write the (full) JSON report to <file>.\n --depth <n> How far to follow links out of each page (0 = follow\n none). Sitemap URLs are seeded regardless, so --depth 0\n alone is not \"entry page only\" — add --no-sitemap for\n that. Default: 2.\n --max-pages <n> Page budget for the crawl. A hard cap under\n --coverage all; under structural coverage the selection\n wins and the budget is max(<n>, selected + 5).\n Default: 200.\n --coverage <mode> How to choose which pages to audit.\n \"structural\" (default when a sitemap is found) keeps\n every standalone page and samples families of pages\n built from one template. \"all\" audits everything the\n sitemap lists, up to --max-pages.\n --include <glob> Only crawl paths matching <glob> (repeatable).\n --exclude <glob> Skip paths matching <glob> (repeatable).\n --locales <list> Comma-separated locales the site serves, e.g.\n \"fr,en,pt-br\". Unioned with what the sitemap shows,\n never substituted for it, and the only way to make a\n locale the site does not serve yet show up as missing.\n Also folds /en/… and /fr/… into one route family for\n structural coverage, so pass it on any locale-prefixed\n site.\n --ignore-holes <glob> Route (locale-free) that is deliberately not\n translated everywhere, so its gaps are not reported\n as missing translations (repeatable).\n --no-sitemap Do not discover the sitemap; crawl from <url> only.\n Discovery is on by default because link-only crawling\n cannot find locales a site never links to.\n --profile <name> Policy overlay on the rule set — it changes how\n loudly a rule fires, never what it observes:\n${PROFILE_HELP}\n --conformance Report EVERY rule's status on every page (pass /\n fail / warn / na), not just the violations. Answers\n \"where do we stand against the catalog?\"; pairs with\n --json.\n --advisories Attach the prose rules — the judgment calls goflag\n refuses to fake — each with the observed facts an\n agent needs to judge it. Never affects the gate.\n --fail-on <level> Exit 1 at or above this severity: warning (default),\n error, or never.\n --regressions-only Weaken the gate: fail only on findings that are NEW\n relative to --baseline. Known findings stop blocking\n the build — a green run no longer means a clean site.\n Requires --baseline.\n --baseline <file> Stored report to compare against. Use with\n --regressions-only.\n --update-baseline Write this run to --baseline and exit 0, instead of\n judging against it. Use it to capture a baseline, or\n to accept findings you have decided to live with —\n it says what it accepted.\n --max-debt <n> Fail when the site carries more than <n> findings in\n total, whether new or known. Lower it as you fix, to\n stop a baseline from fossilising.\n --start <cmd> Boot <cmd>, wait for <url> to answer, audit, then\n stop it. Use to gate a merge on the built app before\n it ships.\n --start-cwd <dir> Directory to run --start in. Defaults to the current\n one; set it when auditing a monorepo package from the\n repository root.\n --start-timeout <ms> How long to wait for --start to answer. Default: 60000.\n --no-external Do not probe off-origin (external) links.\n --static Static HTML only; never launch headless Chromium.\n --allow-insecure-tls Accept self-signed / invalid TLS (localhost, tunnels).\n --timeout <ms> Per-request timeout in ms, for page fetches and link\n probes alike. Unset, the defaults differ: 8000 for link\n probes, 15000 for page fetches.\n --verbose, -V Log every page as it is analyzed.\n --quiet, -q Suppress the live progress output.\n --no-color Disable coloured output.\n -h, --help Show this help.\n -v, --version Show the version.\n\nExit codes: 0 clean, 1 findings found, 2 fatal error.`;\n\nexport interface ParsedArgs {\n /**\n * A subcommand instead of an audit. Absent for the usual `goflag <url>`.\n *\n * `rules` prints the catalogue and exits without touching the network — the\n * one thing goflag can answer about itself rather than about a site.\n */\n command?: \"rules\";\n url?: string;\n json: boolean;\n summary: boolean;\n report?: string;\n color: boolean;\n help: boolean;\n version: boolean;\n /** Live-progress verbosity for the logger. */\n logMode: LogMode;\n /** Severity at or above which the process exits 1. */\n failOn: FailOn;\n /** Path to a stored report to compare against (`--baseline`). */\n baseline?: string;\n /** Gate on new findings only, letting known ones through. */\n regressionsOnly: boolean;\n /** Write the run to `--baseline` rather than judging against it. */\n updateBaseline: boolean;\n /** Hard ceiling on total findings, new or known. */\n maxDebt?: number;\n /** Command to boot before auditing, and stop afterwards (`--start`). */\n start?: string;\n /** How long to wait for `--start` to answer, in ms. */\n startTimeoutMs: number;\n /** Directory to run the `--start` command in. */\n startCwd?: string;\n options: AuditOptions;\n}\n\nconst FAIL_ON_LEVELS: readonly FailOn[] = [\"warning\", \"error\", \"never\"];\n\nexport function parseArgs(argv: string[]): ParsedArgs {\n const parsed: ParsedArgs = {\n json: false,\n summary: false,\n color: process.stdout.isTTY === true && !process.env.NO_COLOR,\n help: false,\n version: false,\n logMode: \"compact\",\n failOn: \"warning\",\n regressionsOnly: false,\n updateBaseline: false,\n startTimeoutMs: 60_000,\n options: { include: [], exclude: [] },\n };\n\n const next = (i: number, flag: string): string => {\n const value = argv[i + 1];\n if (value === undefined) throw new Error(`${flag} requires a value`);\n return value;\n };\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n switch (arg) {\n case \"-h\":\n case \"--help\":\n parsed.help = true;\n break;\n case \"-v\":\n case \"--version\":\n parsed.version = true;\n break;\n case \"--json\":\n parsed.json = true;\n break;\n case \"-s\":\n case \"--summary\":\n parsed.summary = true;\n break;\n case \"--no-color\":\n parsed.color = false;\n break;\n case \"-V\":\n case \"--verbose\":\n parsed.logMode = \"verbose\";\n break;\n case \"-q\":\n case \"--quiet\":\n parsed.logMode = \"quiet\";\n break;\n case \"--static\":\n parsed.options.static = true;\n break;\n case \"--allow-insecure-tls\":\n parsed.options.allowInsecureTls = true;\n break;\n case \"--no-external\":\n parsed.options.checkExternal = false;\n break;\n case \"--no-sitemap\":\n parsed.options.noSitemap = true;\n break;\n case \"--conformance\":\n parsed.options.conformance = true;\n break;\n case \"--advisories\":\n parsed.options.advisories = true;\n break;\n case \"--ignore-holes\":\n parsed.options.ignoreHoles = [...(parsed.options.ignoreHoles ?? []), next(i, arg)];\n i++;\n break;\n case \"--locales\": {\n // Split on commas so `--locales fr,en` and repeated flags both work.\n const value = next(i, arg);\n const tags = value\n .split(\",\")\n .map((t) => t.trim())\n .filter(Boolean);\n if (tags.length === 0) throw new Error(`${arg} expects at least one locale`);\n parsed.options.locales = [...(parsed.options.locales ?? []), ...tags];\n i++;\n break;\n }\n case \"--profile\": {\n const value = next(i, arg);\n // Validated here, not in `runAudit`: a mistyped policy should cost\n // an argument-parse error, not a crawl.\n if (!PROFILE_NAMES.includes(value)) {\n throw new Error(`${arg} expects one of: ${PROFILE_NAMES.join(\", \")}`);\n }\n parsed.options.profile = value;\n i++;\n break;\n }\n case \"--fail-on\": {\n const value = next(i, arg) as FailOn;\n if (!FAIL_ON_LEVELS.includes(value)) {\n throw new Error(`${arg} expects one of: ${FAIL_ON_LEVELS.join(\", \")}`);\n }\n parsed.failOn = value;\n i++;\n break;\n }\n case \"--start\":\n parsed.start = next(i, arg);\n i++;\n break;\n case \"--start-timeout\":\n parsed.startTimeoutMs = toInt(next(i, arg), arg);\n i++;\n break;\n case \"--start-cwd\":\n parsed.startCwd = next(i, arg);\n i++;\n break;\n case \"--report\":\n parsed.report = next(i, arg);\n i++;\n break;\n case \"--baseline\":\n parsed.baseline = next(i, arg);\n i++;\n break;\n case \"--update-baseline\":\n parsed.updateBaseline = true;\n break;\n case \"--regressions-only\":\n parsed.regressionsOnly = true;\n break;\n case \"--max-debt\":\n parsed.maxDebt = toInt(next(i, arg), arg);\n i++;\n break;\n case \"--depth\":\n parsed.options.depth = toInt(next(i, arg), arg);\n i++;\n break;\n case \"--coverage\": {\n const mode = next(i, arg);\n if (mode !== \"structural\" && mode !== \"all\") {\n throw new Error(`--coverage takes \"structural\" or \"all\", not ${JSON.stringify(mode)}`);\n }\n parsed.options.coverage = mode;\n i += 1;\n break;\n }\n case \"--max-pages\":\n parsed.options.maxPages = toInt(next(i, arg), arg);\n i++;\n break;\n case \"--timeout\":\n parsed.options.timeoutMs = toInt(next(i, arg), arg);\n i++;\n break;\n case \"--include\":\n parsed.options.include!.push(next(i, arg));\n i++;\n break;\n case \"--exclude\":\n parsed.options.exclude!.push(next(i, arg));\n i++;\n break;\n default:\n if (arg && arg.startsWith(\"-\")) throw new Error(`unknown option: ${arg}`);\n // `rules` is the one word that is a command rather than a URL. It is\n // accepted only in first position: `goflag https://x.test rules` is a\n // typo, and treating it as a command would audit nothing while looking\n // like it worked.\n if (arg === \"rules\" && i === 0) parsed.command = \"rules\";\n else if (arg && !parsed.url) parsed.url = arg;\n else if (arg) throw new Error(`unexpected argument: ${arg}`);\n }\n }\n\n // Each flag is meaningless without the other, and guessing which one the\n // caller meant would silently change how strict the build is.\n if (parsed.updateBaseline && !parsed.baseline) {\n throw new Error(\"--update-baseline needs a --baseline <file> to write to\");\n }\n // Capturing a baseline is not gating against one, so the explicit opt-in that\n // --regressions-only exists to force does not apply.\n if (parsed.baseline && !parsed.regressionsOnly && !parsed.updateBaseline) {\n throw new Error(\n \"--baseline weakens the gate, so it must be requested explicitly: add --regressions-only\",\n );\n }\n if (parsed.regressionsOnly && !parsed.baseline) {\n throw new Error(\"--regressions-only needs a --baseline <file> to compare against\");\n }\n\n return parsed;\n}\n\nfunction toInt(value: string, flag: string): number {\n const n = Number(value);\n if (!Number.isInteger(n) || n < 0) throw new Error(`${flag} expects a non-negative integer`);\n return n;\n}\n","/**\n * The source catalog — every authoritative reference a goflag rule may cite.\n *\n * Curated by hand (not scraped: automated spec-prose extraction is too\n * error-prone to trust) and seeded from `docs/rules-catalog-plan.md` §4.\n * Ordered as the plan orders them: web standards, then vendor / de-facto\n * specs, then practical references and folklore.\n *\n * Two validators keep this honest:\n * - `./validate.ts` (structural, offline) runs as a unit test on every\n * pipeline: unique ids, rigor present, URLs parse, dates are real,\n * quotes stay fair-use short.\n * - `scripts/validate-sources.ts` (network) confirms every URL still\n * resolves; wired to scheduled pipelines and to merge requests touching\n * this folder, because vendor URLs drift.\n *\n * When a URL is re-verified (or replaced after drifting), bump that entry's\n * `retrievedAt`.\n */\n\nimport type { Source } from \"./types\";\n\nexport type { Source, SourceRigor } from \"./types\";\n\n// ---------------------------------------------------------------------------\n// §4.1 Normative — web standards\n// ---------------------------------------------------------------------------\n\nconst NORMATIVE: readonly Source[] = [\n {\n id: \"whatwg-html-title\",\n publisher: \"WHATWG\",\n rigor: \"normative\",\n title: \"HTML Living Standard — the title element\",\n url: \"https://html.spec.whatwg.org/multipage/semantics.html\",\n anchor: \"the-title-element\",\n retrievedAt: \"2026-08-06\",\n note: \"Documents must have exactly one title element in the head, and its text should identify the document even out of context (e.g. in history or bookmarks).\",\n },\n {\n id: \"whatwg-html-meta\",\n publisher: \"WHATWG\",\n rigor: \"normative\",\n title: \"HTML Living Standard — the meta element\",\n url: \"https://html.spec.whatwg.org/multipage/semantics.html\",\n anchor: \"the-meta-element\",\n retrievedAt: \"2026-08-06\",\n note: \"Defines the syntax of meta: exactly one of name, http-equiv, charset or itemprop must be present, and name/http-equiv variants require a content attribute.\",\n },\n {\n id: \"whatwg-html-standard-metadata-names\",\n publisher: \"WHATWG\",\n rigor: \"normative\",\n title: \"HTML Living Standard — standard metadata names\",\n url: \"https://html.spec.whatwg.org/multipage/semantics.html\",\n anchor: \"standard-metadata-names\",\n retrievedAt: \"2026-08-06\",\n note: \"The registry of standard meta names — description, viewport, theme-color, robots, generator, … — and the conformance requirements for each (e.g. at most one meta description per document).\",\n },\n {\n id: \"whatwg-html-link\",\n publisher: \"WHATWG\",\n rigor: \"normative\",\n title: \"HTML Living Standard — the link element\",\n url: \"https://html.spec.whatwg.org/multipage/semantics.html\",\n anchor: \"the-link-element\",\n retrievedAt: \"2026-08-06\",\n note: \"Defines the syntax of link: a rel attribute (or itemprop) is required, and href must be a valid non-empty URL potentially surrounded by spaces.\",\n },\n {\n id: \"whatwg-html-link-types\",\n publisher: \"WHATWG\",\n rigor: \"normative\",\n title: \"HTML Living Standard — link types\",\n url: \"https://html.spec.whatwg.org/multipage/links.html\",\n anchor: \"linkTypes\",\n retrievedAt: \"2026-08-06\",\n note: \"The registry of rel keywords — canonical, alternate, icon, manifest, … — including which are allowed on link vs a/area, and the semantics of each (e.g. alternate + hreflang designates a translation).\",\n },\n {\n id: \"whatwg-html-lang\",\n publisher: \"WHATWG\",\n rigor: \"normative\",\n title: \"HTML Living Standard — the lang and xml:lang attributes\",\n url: \"https://html.spec.whatwg.org/multipage/dom.html\",\n anchor: \"the-lang-and-xml:lang-attributes\",\n retrievedAt: \"2026-08-06\",\n note: \"lang gives the language of the element's contents and must be a valid BCP 47 language tag or the empty string; authors are encouraged to set it on the root html element.\",\n },\n {\n id: \"whatwg-html-dir\",\n publisher: \"WHATWG\",\n rigor: \"normative\",\n title: \"HTML Living Standard — the dir attribute\",\n url: \"https://html.spec.whatwg.org/multipage/dom.html\",\n anchor: \"the-dir-attribute\",\n retrievedAt: \"2026-08-06\",\n note: \"dir declares text directionality: ltr, rtl, or auto. Governs whether an rtl-language page declares its direction explicitly.\",\n },\n {\n id: \"whatwg-url\",\n publisher: \"WHATWG\",\n rigor: \"normative\",\n title: \"URL Standard\",\n url: \"https://url.spec.whatwg.org/\",\n retrievedAt: \"2026-08-06\",\n note: \"How browsers actually parse URLs — the parsing/validity contract goflag applies when it checks that a canonical or alternate href is a valid, absolute URL.\",\n },\n {\n id: \"whatwg-encoding\",\n publisher: \"WHATWG\",\n rigor: \"normative\",\n title: \"Encoding Standard\",\n url: \"https://encoding.spec.whatwg.org/\",\n retrievedAt: \"2026-08-06\",\n note: \"Defines character encodings for the web; UTF-8 is the mandatory encoding for new content, which is what a meta charset declaration should say.\",\n },\n {\n id: \"ietf-rfc3986\",\n publisher: \"IETF\",\n rigor: \"normative\",\n title: \"RFC 3986 — URI Generic Syntax\",\n url: \"https://www.rfc-editor.org/rfc/rfc3986\",\n retrievedAt: \"2026-08-06\",\n note: \"The generic URI grammar: scheme, authority, path, query, fragment. The formal definition of what an absolute URL is.\",\n },\n {\n id: \"ietf-rfc3987\",\n publisher: \"IETF\",\n rigor: \"normative\",\n title: \"RFC 3987 — Internationalized Resource Identifiers (IRIs)\",\n url: \"https://www.rfc-editor.org/rfc/rfc3987\",\n retrievedAt: \"2026-08-06\",\n note: \"Extends URIs to non-ASCII characters and defines the IRI-to-URI mapping — relevant when localized sites use non-ASCII paths.\",\n },\n {\n id: \"ietf-bcp47\",\n publisher: \"IETF\",\n rigor: \"normative\",\n title: \"RFC 5646 / BCP 47 — Tags for Identifying Languages\",\n url: \"https://www.rfc-editor.org/rfc/rfc5646\",\n retrievedAt: \"2026-08-06\",\n note: \"The grammar of language tags used by lang and hreflang (language[-script][-region]…). Tags are case-insensitive; the familiar en-US casing is only a convention.\",\n },\n {\n id: \"ietf-rfc6596\",\n publisher: \"IETF\",\n rigor: \"normative\",\n title: \"RFC 6596 — The Canonical Link Relation\",\n url: \"https://www.rfc-editor.org/rfc/rfc6596\",\n retrievedAt: \"2026-08-06\",\n note: 'Defines rel=\"canonical\": the preferred IRI for duplicative content. The target must not be a 404 and should not vary per duplicate (no self-referencing loops through redirects).',\n },\n {\n id: \"ietf-rfc9309\",\n publisher: \"IETF\",\n rigor: \"normative\",\n title: \"RFC 9309 — Robots Exclusion Protocol\",\n url: \"https://www.rfc-editor.org/rfc/rfc9309\",\n retrievedAt: \"2026-08-06\",\n note: \"The robots.txt file format and matching rules: group selection by user-agent, longest-match precedence, allow wins ties, and how errors/unavailability must be treated.\",\n },\n {\n id: \"sitemaps-protocol\",\n publisher: \"sitemaps.org\",\n rigor: \"normative\",\n title: \"Sitemaps XML protocol\",\n url: \"https://www.sitemaps.org/protocol.html\",\n retrievedAt: \"2026-08-06\",\n note: \"The sitemap.xml schema: urlset/url/loc structure, optional lastmod/changefreq/priority, the 50,000-URL / 50 MB limits, and sitemap index files.\",\n },\n {\n id: \"w3c-appmanifest\",\n publisher: \"W3C\",\n rigor: \"normative\",\n title: \"Web Application Manifest\",\n url: \"https://www.w3.org/TR/appmanifest/\",\n retrievedAt: \"2026-08-06\",\n note: 'The manifest.json format linked via link rel=\"manifest\": members like name, icons, start_url, display, and how user agents process them.',\n },\n {\n id: \"w3c-wcag22\",\n publisher: \"W3C\",\n rigor: \"normative\",\n title: \"WCAG 2.2\",\n url: \"https://www.w3.org/TR/WCAG22/\",\n retrievedAt: \"2026-08-06\",\n note: \"Accessibility success criteria — language of page (3.1.1), non-text content / alt (1.1.1), contrast, … Reserved for the future a11y vertical.\",\n },\n {\n id: \"w3c-wai-aria12\",\n publisher: \"W3C\",\n rigor: \"normative\",\n title: \"WAI-ARIA 1.2\",\n url: \"https://www.w3.org/TR/wai-aria-1.2/\",\n retrievedAt: \"2026-08-06\",\n note: \"ARIA roles, states and properties. Reserved for the future a11y vertical.\",\n },\n {\n id: \"w3c-i18n-language-tags\",\n publisher: \"W3C\",\n rigor: \"guideline\",\n title: \"W3C i18n — Language tags in HTML and XML\",\n url: \"https://www.w3.org/International/articles/language-tags/\",\n retrievedAt: \"2026-08-06\",\n note: \"W3C i18n guidance on applying BCP 47 in practice: keep tags as short as possible, subtag order and casing conventions, when to include script or region.\",\n },\n];\n\n// ---------------------------------------------------------------------------\n// §4.2 Vendor / de-facto specs\n// ---------------------------------------------------------------------------\n\nconst VENDOR: readonly Source[] = [\n {\n id: \"ogp\",\n publisher: \"ogp.me\",\n rigor: \"vendor-spec\",\n title: \"The Open Graph protocol\",\n url: \"https://ogp.me/\",\n retrievedAt: \"2026-08-06\",\n note: \"The og:* vocabulary. og:title, og:type, og:image and og:url are required for every page that wants to be a rich object in the social graph; og:description and others are optional.\",\n },\n {\n id: \"x-cards\",\n publisher: \"X (Twitter)\",\n rigor: \"vendor-spec\",\n title: \"X Cards — about cards markup\",\n url: \"https://developer.x.com/en/docs/twitter-for-websites/cards/overview/abouts-cards\",\n retrievedAt: \"2026-08-06\",\n note: \"The twitter:* card vocabulary (twitter:card, twitter:title, …) and the fallback behavior: X's crawler falls back to og:* tags when twitter:* equivalents are absent. X has since removed most of this documentation (the URL now redirects to docs.x.com/overview) but the crawler behavior and tag names are unchanged — the tags stayed twitter:*, not x:*.\",\n },\n {\n id: \"google-canonicalization\",\n publisher: \"Google\",\n rigor: \"vendor-spec\",\n title: \"Consolidate duplicate URLs (canonicalization)\",\n url: \"https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls\",\n retrievedAt: \"2026-08-06\",\n note: 'How Google treats rel=\"canonical\": a strong hint (not a directive), absolute URLs recommended, don\\'t point to redirecting or 404 targets, one canonical per page.',\n },\n {\n id: \"google-robots-meta\",\n publisher: \"Google\",\n rigor: \"vendor-spec\",\n title: \"Robots meta tag and X-Robots-Tag\",\n url: \"https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag\",\n retrievedAt: \"2026-08-06\",\n note: \"The robots meta / X-Robots-Tag directives (noindex, nofollow, …), how meta and header directives combine, and that the most restrictive directive wins on conflict.\",\n },\n {\n id: \"google-robots-intro\",\n publisher: \"Google\",\n rigor: \"vendor-spec\",\n title: \"Introduction to robots.txt\",\n url: \"https://developers.google.com/search/docs/crawling-indexing/robots/intro\",\n retrievedAt: \"2026-08-06\",\n note: \"Google's robots.txt behavior on top of RFC 9309 — notably that robots.txt controls crawling, not indexing: a blocked URL can still be indexed from links, so noindex needs the page to be crawlable.\",\n },\n {\n id: \"google-hreflang\",\n publisher: \"Google\",\n rigor: \"vendor-spec\",\n title: \"Localized versions of your pages (hreflang)\",\n url: \"https://developers.google.com/search/docs/specialty/international/localized-versions\",\n retrievedAt: \"2026-08-06\",\n note: \"hreflang via link annotations, headers or sitemaps: every language version must list itself and all others (reciprocity), use absolute URLs, and x-default marks the unmatched-language fallback.\",\n },\n {\n id: \"google-title-link\",\n publisher: \"Google\",\n rigor: \"guideline\",\n title: \"Influence your title links in search results\",\n url: \"https://developers.google.com/search/docs/appearance/title-link\",\n retrievedAt: \"2026-08-06\",\n note: \"Write descriptive, concise, non-boilerplate titles unique to each page. Google may rewrite titles it considers poor; length itself is not a ranking factor.\",\n },\n {\n id: \"google-snippet\",\n publisher: \"Google\",\n rigor: \"guideline\",\n title: \"Control your snippets in search results\",\n url: \"https://developers.google.com/search/docs/appearance/snippet\",\n retrievedAt: \"2026-08-06\",\n note: \"Meta descriptions feed the search snippet but Google may substitute on-page text; write a unique, accurate summary per page rather than keyword stuffing.\",\n },\n {\n id: \"google-structured-data\",\n publisher: \"Google\",\n rigor: \"vendor-spec\",\n title: \"Introduction to structured data markup\",\n url: \"https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data\",\n retrievedAt: \"2026-08-06\",\n note: \"How Google consumes structured data (JSON-LD recommended) and what rich-result eligibility requires; the vocabulary itself is schema.org's.\",\n },\n {\n id: \"google-sitemaps\",\n publisher: \"Google\",\n rigor: \"guideline\",\n title: \"Build and submit a sitemap\",\n url: \"https://developers.google.com/search/docs/crawling-indexing/sitemaps/overview\",\n retrievedAt: \"2026-08-06\",\n note: \"Google's sitemap practice on top of the sitemaps.org protocol: when a sitemap helps, supported formats, and submission via robots.txt or Search Console.\",\n },\n {\n id: \"schema-org\",\n publisher: \"schema.org\",\n rigor: \"vendor-spec\",\n title: \"schema.org vocabulary\",\n url: \"https://schema.org/\",\n retrievedAt: \"2026-08-06\",\n note: \"The shared vocabulary (types and properties) that structured data — JSON-LD in particular — is written in.\",\n },\n {\n id: \"apple-web-apps\",\n publisher: \"Apple\",\n rigor: \"vendor-spec\",\n title: \"Configuring web applications (Safari Web Content Guide)\",\n url: \"https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/ConfiguringWebApplications/ConfiguringWebApplications.html\",\n retrievedAt: \"2026-08-06\",\n note: \"apple-touch-icon and the apple-mobile-web-app-* meta tags: how iOS picks home-screen icons and startup images. Archived but still the reference Apple points to.\",\n },\n {\n id: \"meta-og-sharing\",\n publisher: \"Meta\",\n rigor: \"vendor-spec\",\n title: \"Sharing — webmasters (Open Graph usage)\",\n url: \"https://developers.facebook.com/docs/sharing/webmasters/\",\n retrievedAt: \"2026-08-06\",\n note: \"How Meta's crawler builds link previews from og:* tags — including image size expectations (1200×630 recommended, 200×200 minimum) beyond what ogp.me specifies.\",\n },\n {\n id: \"bing-webmaster-guidelines\",\n publisher: \"Microsoft\",\n rigor: \"guideline\",\n title: \"Bing Webmaster Guidelines\",\n url: \"https://www.bing.com/webmasters/help/webmasters-guidelines-30fba23a\",\n retrievedAt: \"2026-08-06\",\n note: \"Bing's crawlability and content guidance — a cross-check that the catalog is not Google-only.\",\n },\n];\n\n// ---------------------------------------------------------------------------\n// §4.3 Practical references, guidelines & cross-check tools\n// ---------------------------------------------------------------------------\n\nconst PRACTICAL: readonly Source[] = [\n {\n id: \"mdn-meta-name\",\n publisher: \"MDN / Mozilla\",\n rigor: \"guideline\",\n title: \"MDN — <meta> name attribute values\",\n url: \"https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name\",\n retrievedAt: \"2026-08-06\",\n note: \"Practical reference for standard and de-facto meta names, browser support included.\",\n },\n {\n id: \"mdn-viewport\",\n publisher: \"MDN / Mozilla\",\n rigor: \"guideline\",\n title: \"MDN — Viewport meta tag\",\n url: \"https://developer.mozilla.org/en-US/docs/Web/HTML/Viewport_meta_tag\",\n retrievedAt: \"2026-08-06\",\n note: \"width=device-width, initial-scale=1 and friends: what the viewport meta controls and the accessibility pitfalls (never disable user scaling).\",\n },\n {\n id: \"mdn-link-rel\",\n publisher: \"MDN / Mozilla\",\n rigor: \"guideline\",\n title: \"MDN — rel attribute values\",\n url: \"https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel\",\n retrievedAt: \"2026-08-06\",\n note: \"Practical reference for rel values across link, a and form, with support notes.\",\n },\n {\n id: \"lighthouse-seo\",\n publisher: \"Google\",\n rigor: \"guideline\",\n title: \"Lighthouse — SEO audits\",\n url: \"https://developer.chrome.com/docs/lighthouse/seo/\",\n retrievedAt: \"2026-08-06\",\n note: \"The audit definitions Lighthouse ships (document has a title, has a meta description, valid hreflang, valid canonical, …) — the closest thing to an industry-standard checklist to mirror.\",\n },\n {\n id: \"axe-core-rules\",\n publisher: \"Deque\",\n rigor: \"guideline\",\n title: \"axe-core — rule descriptions\",\n url: \"https://github.com/dequelabs/axe-core/blob/develop/doc/rule-descriptions.md\",\n retrievedAt: \"2026-08-06\",\n note: \"The a11y rule catalog to cross-check against when the a11y vertical lands (html-has-lang, valid-lang, image-alt, …).\",\n },\n {\n id: \"w3c-nu-checker\",\n publisher: \"W3C\",\n rigor: \"guideline\",\n title: \"Nu HTML Checker\",\n url: \"https://validator.w3.org/nu/\",\n retrievedAt: \"2026-08-06\",\n note: \"The reference HTML validator — the cross-check for goflag's document-level syntax rules.\",\n },\n {\n id: \"moz-title-tag\",\n publisher: \"Moz\",\n rigor: \"heuristic\",\n title: \"Title tag best practices\",\n url: \"https://moz.com/learn/seo/title-tag\",\n retrievedAt: \"2026-08-06\",\n note: \"The ~50–60 character title window is display folklore, not spec: it approximates SERP truncation width. Ships as heuristic so agents weight it accordingly.\",\n },\n {\n id: \"moz-meta-description\",\n publisher: \"Moz\",\n rigor: \"heuristic\",\n title: \"Meta description best practices\",\n url: \"https://moz.com/learn/seo/meta-description\",\n retrievedAt: \"2026-08-06\",\n note: \"The ~50–160 character description window is display folklore, not spec: it approximates snippet truncation. Ships as heuristic so agents weight it accordingly.\",\n },\n];\n\n/**\n * Every source a rule may cite, in the plan's order: normative standards,\n * vendor / de-facto specs, then practical references.\n */\nexport const SOURCES: readonly Source[] = [...NORMATIVE, ...VENDOR, ...PRACTICAL];\n\n/** Look up a source by id. */\nexport function getSource(id: string): Source | undefined {\n return SOURCES.find((source) => source.id === id);\n}\n\n/** A source's full URL, fragment included — what docs and reports should link. */\nexport function sourceUrl(source: Source): string {\n return source.anchor ? `${source.url}#${source.anchor}` : source.url;\n}\n","/**\n * The rule catalogue, as data somebody else can read.\n *\n * Phase 3.3 of `docs/spec-and-lib-plan.md`, and it has a consumer that the\n * plan did not anticipate. `apps/website` renders the catalogue on\n * `/docs/rules`, and it cannot import this package — invariant I3 forbids\n * `apps/**` from reaching into `packages/**`, because the site must not depend\n * on either package's build. So it carries a hand-written mirror, and a mirror\n * drifts: the documentation audit of 2026-08-13 found a rule message wrong\n * since 0.2.2, a reciprocity code advertised that no branch can emit, and a\n * flag absent from the reference entirely. Exporting the catalogue removes the\n * class rather than correcting it once more.\n *\n * What this deliberately does **not** carry:\n *\n * - **the message a finding prints.** It is built at evaluation time from what\n * the page actually says (`Conflicting robots directives: ${conflicts}`), so\n * a static copy is a sample, not a fact. A consumer that wants one runs the\n * rule.\n * - **editorial prose.** The registry's `why` is the rationale a rule carries;\n * what a mistake *costs a reader* is writing, and belongs to whoever writes.\n * A consumer keys its own text by rule id and derives everything else.\n *\n * `rigor: null` on the cross-page rules is not an omission — `SiteRule` has no\n * `rigor` field yet (phase G). Emitting the gap is how it stops being\n * invisible.\n */\n\nimport { RULES } from \"./index\";\nimport { PROSE_RULES } from \"./prose\";\nimport { SITE_RULES } from \"./site-rules\";\nimport { getSource } from \"./sources\";\nimport type { Band, Rigor } from \"./types\";\nimport type { Severity } from \"../core/types\";\n\n/** Where a rule runs, which decides what it can see and what it may claim. */\nexport type RuleScope = \"page\" | \"site\" | \"prose\";\n\nexport interface CatalogFix {\n title: string;\n snippet: string;\n language: string;\n}\n\nexport interface CatalogRule {\n id: string;\n scope: RuleScope;\n /** Grouping key (`document`, `opengraph`, …). Absent on cross-page rules. */\n category?: string;\n /**\n * `null` on prose rules: they produce no verdict, so there is nothing for a\n * severity to describe, and inventing one would make a question look like a\n * finding.\n */\n severity: Severity | null;\n /** How the rule is evaluated. `prose` never evaluates at all. */\n kind: \"boolean\" | \"scored\" | \"prose\";\n /** One-line statement of the policy. */\n summary: string;\n /** Why honouring it matters, in the registry's own words. */\n why?: string;\n /** `null` on the cross-page rules, which have no rigor field yet. */\n rigor: Rigor | null;\n /** Ids into `sources`. Empty only where `rigor` is null, for the same reason. */\n sources: string[];\n /** Dotted paths into the observation model the rule reads. */\n reads?: string[];\n relates?: string[];\n /** What a passing page looks like, in one phrase. */\n expected?: string;\n fix?: CatalogFix;\n /** Scored rules only: the window each band covers. */\n bands?: Partial<Record<Band, [number, number]>>;\n /**\n * Scored rules only: the severity a finding takes per band.\n *\n * `severity` above carries the `acceptable` one, because that is the case a\n * page actually lands in when it misses the ideal window by a little — and a\n * catalogue that answered `null` there would make a real gating rule look\n * like it has no teeth.\n */\n severityByBand?: Record<\"acceptable\" | \"poor\", Severity>;\n /** Prose rules only: the question an agent is asked to judge. */\n prose?: string;\n}\n\nexport interface SourceDoc {\n id: string;\n publisher: string;\n title: string;\n rigor: string;\n url: string;\n}\n\nexport interface RuleCatalog {\n /**\n * The `@goflag/cli` version, stamped by the command that prints it.\n *\n * Absent from the committed `rules.json` on purpose: a file carrying the\n * version changes on every release, so the generated artefact would go stale\n * during `pnpm release` itself and turn the release commit red. The file\n * answers \"what does goflag check\"; the version answers \"which goflag\", and\n * only the caller knows that.\n */\n version?: string;\n counts: { page: number; site: number; prose: number };\n /** Only the documents the shipped rules cite, keyed by id. */\n sources: Record<string, SourceDoc>;\n rules: CatalogRule[];\n}\n\nfunction fixOf(fix: { title: string; snippet: string; language?: string } | undefined) {\n if (!fix) return undefined;\n return { title: fix.title, snippet: fix.snippet, language: fix.language ?? \"ts\" };\n}\n\n/**\n * Build the catalogue. Pure, deterministic, and ordered by id so a consumer\n * can diff two versions of it without sorting first — a generated file that\n * reorders itself is a generated file nobody will commit.\n */\nexport function buildRuleCatalog(version?: string): RuleCatalog {\n const rules: CatalogRule[] = [];\n\n for (const rule of RULES) {\n rules.push({\n id: rule.id,\n scope: \"page\",\n category: rule.category,\n severity: rule.kind === \"boolean\" ? rule.severity : rule.severityByBand.acceptable,\n kind: rule.kind,\n summary: rule.title,\n why: rule.why,\n rigor: rule.rigor,\n sources: [...rule.sources],\n reads: [...rule.reads],\n ...(rule.relates ? { relates: [...rule.relates] } : {}),\n ...(rule.expected ? { expected: rule.expected } : {}),\n ...(fixOf(rule.fix) ? { fix: fixOf(rule.fix) } : {}),\n ...(rule.kind === \"scored\"\n ? { bands: { ...rule.bands }, severityByBand: { ...rule.severityByBand } }\n : {}),\n });\n }\n\n for (const rule of SITE_RULES) {\n rules.push({\n id: rule.id,\n scope: \"site\",\n severity: rule.severity,\n kind: \"boolean\",\n summary: rule.summary,\n rigor: null,\n sources: [],\n });\n }\n\n for (const rule of PROSE_RULES) {\n rules.push({\n id: rule.id,\n scope: \"prose\",\n category: rule.category,\n severity: null,\n kind: \"prose\",\n summary: rule.title,\n why: rule.why,\n rigor: rule.rigor,\n sources: [...rule.sources],\n reads: [...rule.reads],\n prose: rule.prose,\n });\n }\n\n rules.sort((a, b) => a.id.localeCompare(b.id));\n\n // Only what the shipped rules cite. The source catalogue carries more,\n // seeded for rules that do not exist yet, and exporting those would advertise\n // coverage goflag does not have.\n const cited = new Set(rules.flatMap((r) => r.sources));\n const sources: Record<string, SourceDoc> = {};\n for (const id of [...cited].sort()) {\n const doc = getSource(id);\n if (doc) {\n sources[id] = {\n id: doc.id,\n publisher: doc.publisher,\n title: doc.title,\n rigor: doc.rigor,\n url: doc.url,\n };\n }\n }\n\n return {\n ...(version ? { version } : {}),\n counts: {\n page: RULES.length,\n site: SITE_RULES.length,\n prose: PROSE_RULES.length,\n },\n sources,\n rules,\n };\n}\n\n/** The exact bytes `rules.json` must contain, so a comparison is a string one. */\nexport function serialiseCatalog(catalog: RuleCatalog): string {\n return `${JSON.stringify(catalog, null, 2)}\\n`;\n}\n","/**\n * goflag CLI.\n *\n * goflag <url> [options]\n *\n * Crawls a site and reports broken links, missing translation pages, and\n * missing/misconfigured SEO metadata. The JSON report is the source of\n * truth; the terminal output is rendered from it.\n *\n * Exit codes:\n * 0 clean (green flag)\n * 1 findings present (yellow/red flag) — useful as a CI gate\n * 2 fatal (bad URL, unexpected error)\n */\n\nimport { runAudit, exitCode } from \"./report/build\";\nimport { diffReports, diffExitCode, totalFindings } from \"./report/diff\";\nimport { renderDiffTerminal } from \"./report/render-diff\";\nimport { startServer, type StartedServer } from \"./lib/runner/dev-server\";\nimport { renderTerminal } from \"./report/render-terminal\";\nimport { renderSummaryTerminal } from \"./report/render-summary\";\nimport { summarize } from \"./report/summarize\";\nimport { Logger } from \"./report/logger\";\nimport { HELP, parseArgs, type ParsedArgs } from \"./cli-args\";\nimport { buildRuleCatalog, serialiseCatalog } from \"./lib/rules/catalog\";\n\nasync function readVersion(): Promise<string> {\n try {\n const { readFileSync } = await import(\"node:fs\");\n const { fileURLToPath } = await import(\"node:url\");\n const { dirname, join } = await import(\"node:path\");\n const here = dirname(fileURLToPath(import.meta.url));\n const pkg = JSON.parse(readFileSync(join(here, \"..\", \"package.json\"), \"utf8\"));\n return typeof pkg.version === \"string\" ? pkg.version : \"0.0.0\";\n } catch {\n return \"0.0.0\";\n }\n}\n\n/**\n * Write JSON to a path, creating the directory it names.\n *\n * `writeFileSync` does not, and the one moment that matters is the first: a\n * repository adopting the gate runs `--update-baseline --baseline\n * .goflag/baseline.json` with no `.goflag/` yet, and the command that the\n * runbook documents as step one is the only one that fails. The audit had\n * already run at that point, so the failure also threw away several minutes of\n * crawling.\n */\nasync function writeJson(path: string, value: unknown): Promise<void> {\n const { mkdirSync, writeFileSync } = await import(\"node:fs\");\n const { dirname } = await import(\"node:path\");\n\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, `${JSON.stringify(value, null, 2)}\\n`, \"utf8\");\n}\n\nasync function main(): Promise<number> {\n let args: ParsedArgs;\n try {\n args = parseArgs(process.argv.slice(2));\n } catch (err) {\n process.stderr.write(`goflag: ${(err as Error).message}\\n\\n${HELP}\\n`);\n return 2;\n }\n\n if (args.help) {\n process.stdout.write(`${HELP}\\n`);\n return 0;\n }\n if (args.version) {\n process.stdout.write(`${await readVersion()}\\n`);\n return 0;\n }\n\n // Answers a question about goflag rather than about a site: no crawl, no\n // network, no URL. It exists so a consumer stops copying the catalogue by\n // hand — `apps/website` cannot import this package (I3) and its hand-written\n // mirror had drifted in three places by the time anybody checked.\n if (args.command === \"rules\") {\n process.stdout.write(serialiseCatalog(buildRuleCatalog(await readVersion())));\n return 0;\n }\n\n if (!args.url) {\n process.stderr.write(`goflag: missing <url>\\n\\n${HELP}\\n`);\n return 2;\n }\n\n // Progress always goes to stderr, so stdout (JSON / rendered report)\n // stays clean and pipeable. Colour follows the stderr TTY.\n const logger = new Logger({\n stream: process.stderr,\n mode: args.logMode,\n color: process.stderr.isTTY === true && !process.env.NO_COLOR,\n });\n\n // `--start` owns a child process for the whole audit; it must be torn down\n // on every exit path, including a failed audit, or CI leaks a held port.\n let server: StartedServer | undefined;\n let report;\n try {\n if (args.start) {\n logger.note(`goflag: starting \\`${args.start}\\` …`);\n server = await startServer({\n command: args.start,\n cwd: args.startCwd,\n url: args.url,\n timeoutMs: args.startTimeoutMs,\n allowInsecureTls: args.options.allowInsecureTls,\n onOutput: args.logMode === \"verbose\" ? (chunk) => process.stderr.write(chunk) : undefined,\n });\n }\n logger.note(`goflag: auditing ${args.url} …`);\n report = await runAudit(args.url, { ...args.options, onProgress: logger.onProgress });\n logger.stop();\n } catch (err) {\n logger.stop();\n process.stderr.write(`goflag: ${(err as Error).message}\\n`);\n return 2;\n } finally {\n await server?.stop();\n }\n\n // A sitemap that timed out is not a site without one. The crawl seeds from\n // the sitemap when there is one, so losing it drops the audit to link\n // following and it sees a fraction of the pages — measured on\n // openfinanceguide, 46 instead of 600, and 5 findings instead of 279.\n //\n // Reporting that as a result is how a gate goes red on nothing, or green on\n // 8% of a site. Neither is worth having, so this is fatal: the run could not\n // ask the question it was asked to ask.\n const sitemap = report.diagnostics.sitemap;\n if (sitemap?.unreachable) {\n process.stderr.write(\n `goflag: the sitemap could not be fetched — ${sitemap.unreachable}\\n` +\n `goflag: this run crawled links instead, so it saw a different site ` +\n `from one that reads the sitemap, and its findings are not comparable.\\n` +\n `goflag: retry, or pass --no-sitemap to audit by crawling on purpose.\\n`,\n );\n return 2;\n }\n\n // A baseline changes the question from \"is this site clean?\" to \"did this\n // change make it worse?\" — the only one a site with a backlog can answer.\n if (args.baseline) {\n const { existsSync, readFileSync } = await import(\"node:fs\");\n // Capturing a baseline for the first time is the one case where the file\n // is legitimately absent. Everywhere else a missing baseline is a typo,\n // and continuing would turn it into a green build.\n if (existsSync(args.baseline) || !args.updateBaseline) {\n try {\n const baseline = JSON.parse(readFileSync(args.baseline, \"utf8\"));\n report.diff = diffReports(baseline, report);\n } catch (err) {\n process.stderr.write(\n `goflag: could not read baseline ${args.baseline}: ${(err as Error).message}\\n`,\n );\n return 2;\n }\n }\n }\n\n // Writing the baseline is accepting everything in it. The one thing this\n // must not do is accept it quietly: a counter that drops without explanation\n // reads as \"the problem went away\".\n if (args.updateBaseline && args.baseline) {\n await writeJson(args.baseline, report);\n const total = totalFindings(report);\n if (report.diff) {\n const { added, resolved } = report.diff;\n process.stdout.write(\n `goflag: baseline updated — ${added.length} newly accepted, ${resolved.length} resolved, ` +\n `${total} findings now grandfathered in ${args.baseline}\\n`,\n );\n if (added.length > 0) {\n process.stdout.write(`${renderDiffTerminal(report.diff, { color: args.color })}\\n`);\n }\n } else {\n process.stdout.write(\n `goflag: baseline captured — ${total} findings grandfathered in ${args.baseline}\\n`,\n );\n }\n process.stdout.write(\n `goflag: set --max-debt ${total} to stop that number growing, and lower it as you fix.\\n`,\n );\n return 0;\n }\n\n if (args.report) {\n // The report file is always the full report — the source of truth a\n // baseline/diff can rely on, regardless of the --summary view choice.\n await writeJson(args.report, report);\n process.stderr.write(`goflag: report written to ${args.report}\\n`);\n }\n\n // In baseline mode the diff *is* the answer. Printing the full report first\n // buries \"nothing changed\" under a hundred known findings — the reader stops\n // looking, which is precisely the failure the baseline exists to prevent.\n // The complete report stays available via --json and --report.\n if (report.diff && !args.json) {\n process.stdout.write(`${renderDiffTerminal(report.diff, { color: args.color })}\\n`);\n const total = totalFindings(report);\n if (args.maxDebt !== undefined && total > args.maxDebt) {\n process.stderr.write(\n `goflag: ${total} findings exceeds the --max-debt budget of ${args.maxDebt}.\\n`,\n );\n }\n return diffExitCode(report.diff, args.failOn, { total, max: args.maxDebt });\n }\n\n if (args.summary) {\n const summary = summarize(report);\n process.stdout.write(\n args.json\n ? `${JSON.stringify(summary, null, 2)}\\n`\n : `${renderSummaryTerminal(summary, { color: args.color })}\\n`,\n );\n } else {\n process.stdout.write(\n args.json\n ? `${JSON.stringify(report, null, 2)}\\n`\n : `${renderTerminal(report, { color: args.color })}\\n`,\n );\n }\n\n const total = totalFindings(report);\n if (args.maxDebt !== undefined && total > args.maxDebt) {\n process.stderr.write(\n `goflag: ${total} findings exceeds the --max-debt budget of ${args.maxDebt}.\\n`,\n );\n return 1;\n }\n\n if (report.diff) {\n return diffExitCode(report.diff, args.failOn, { total, max: args.maxDebt });\n }\n\n return exitCode(report, args.failOn);\n}\n\nmain()\n .then((code) => process.exit(code))\n .catch((err) => {\n process.stderr.write(`goflag: unexpected error: ${(err as Error).stack ?? err}\\n`);\n process.exit(2);\n });\n"],"mappings":"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","names":["total"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -68,12 +68,23 @@ type Severity = "error" | "warning" | "info";
|
|
|
68
68
|
* I/O. The crawler in `crawl.ts` calls them once after the BFS
|
|
69
69
|
* finishes to populate the inspect-cache i18n payload.
|
|
70
70
|
*
|
|
71
|
-
* Locale code validity
|
|
72
|
-
*
|
|
73
|
-
*
|
|
71
|
+
* Locale code validity is decided in `./bcp47`, by ICU rather than by a
|
|
72
|
+
* shape: a tag whose language, script or region does not exist flags as
|
|
73
|
+
* `locale.invalid`, and `x-default` is always accepted.
|
|
74
74
|
*/
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
/**
|
|
77
|
+
* The codes `reciprocityIssues` emits — all three of them.
|
|
78
|
+
*
|
|
79
|
+
* `self-mismatch` used to sit here too, declared and unreachable: no branch
|
|
80
|
+
* ever produced it, so the site's rule catalogue advertised a check goflag
|
|
81
|
+
* could not perform, and a reader took silence for a pass. The declaration is
|
|
82
|
+
* gone rather than the check implemented, because a page whose self-reference
|
|
83
|
+
* points elsewhere is already caught as a `missing-back-link` on the peer it
|
|
84
|
+
* names. If it is ever worth its own code, it comes back with the branch that
|
|
85
|
+
* emits it.
|
|
86
|
+
*/
|
|
87
|
+
type ReciprocityCode = "missing-back-link" | "x-default-missing" | "locale.invalid";
|
|
77
88
|
interface ReciprocityIssue {
|
|
78
89
|
code: ReciprocityCode;
|
|
79
90
|
/** URL of the page where the issue is observed. */
|
|
@@ -767,6 +778,21 @@ interface GoflagReport {
|
|
|
767
778
|
* are duplicates. Reported so a shrinking finding count is explicable.
|
|
768
779
|
*/
|
|
769
780
|
duplicatePages?: number;
|
|
781
|
+
/**
|
|
782
|
+
* Translation clusters the sitemap declared with `xhtml:link`, when it
|
|
783
|
+
* declared any.
|
|
784
|
+
*
|
|
785
|
+
* A declared cluster decides which rows the translation matrix has — two
|
|
786
|
+
* URLs the site names as one page share a row, whatever their paths — so a
|
|
787
|
+
* site that translates its slugs stops reporting a hole per locale on a
|
|
788
|
+
* pair that is fully translated. It never fills a cell and never says a
|
|
789
|
+
* page exists: `conflicts` counts URLs a second entry claimed for a
|
|
790
|
+
* different cluster, where the first declaration was kept.
|
|
791
|
+
*/
|
|
792
|
+
declaredClusters?: {
|
|
793
|
+
count: number;
|
|
794
|
+
conflicts?: number;
|
|
795
|
+
};
|
|
770
796
|
/** Sitemap discovery outcome, when discovery ran. */
|
|
771
797
|
/** Which pages the run chose to audit, and what it therefore cannot promise. */
|
|
772
798
|
coverage?: {
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goflag/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Audits a site for broken links, missing translation pages, a robots.txt that contradicts your pages, and missing or misconfigured SEO metadata. JSON-first.",
|
|
6
6
|
"keywords": [
|
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
"files": [
|
|
45
|
-
"dist"
|
|
45
|
+
"dist",
|
|
46
|
+
"rules.json"
|
|
46
47
|
],
|
|
47
48
|
"publishConfig": {
|
|
48
49
|
"access": "public"
|
|
@@ -59,6 +60,7 @@
|
|
|
59
60
|
"test:integration": "vitest run test/integration",
|
|
60
61
|
"test:coverage": "vitest run --coverage",
|
|
61
62
|
"validate:sources": "tsx scripts/validate-sources.ts",
|
|
63
|
+
"generate:catalog": "tsx scripts/generate-catalog.ts",
|
|
62
64
|
"prepack": "node ../../scripts/stage-package-docs.mjs",
|
|
63
65
|
"prepublishOnly": "pnpm build",
|
|
64
66
|
"release": "commit-and-tag-version --path . --skip.tag"
|