diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 2e749bf..d063b57 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -2,7 +2,7 @@ "name": "darkroom", "displayName": "Darkroom Engineering", "description": "Darkroom Engineering AI coding standards. Layered config with portable AGENTS.md, progressive complexity, and two-tier knowledge system.", - "version": "13.13.0", + "version": "13.14.0", "author": { "name": "Darkroom Engineering", "url": "https://darkroom.engineering" diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 5dfe701..18e8f49 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "darkroom", - "version": "13.13.0", + "version": "13.14.0", "description": "Darkroom Engineering coding standards and reusable engineering workflows for Codex.", "author": { "name": "Darkroom Engineering", diff --git a/CHANGELOG.md b/CHANGELOG.md index ea3f65a..d9cf45a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,19 @@ All notable changes to cc-settings are documented here. > **Versioning** — cc-settings uses a single version number matching the installer (`src/setup.ts` `VERSION` constant, written to `~/.claude/.cc-settings-version` sentinel). Historical entries below 10.0 predate this unification; the jump from v8.x to v10.x in April 2026 realigned the product version with the installer version that was already ahead. +## [13.14.0] — 2026-08-18 + +**The audit skill gains an SEO mode — discoverability for search engines and answer engines.** Distilled from shipped Darkroom work: satus PRs #348/#405/#413 and darkroomengineering/website PRs #40/#65 independently converged on one discoverability architecture, and the mode encodes that destination shape as 17 mechanical checks so any client project can be audited against it. + +**Added:** + +- **`skills/audit/references/seo-checks.md`** — the check reference (S1–S17, stable IDs), five groups: canonical integrity (self-referential per route; Next.js replaces, never merges, a child's `alternates`), advertised-vs-rendered (every sitemap URL must 200 — the sitemap never checks reachability itself), per-content metadata, structured data (JSON-LD hygiene, no fabricated facts), and AEO surfaces (`/llms.txt` generated from shared sources, AI crawlers named in robots.txt, machine-view routes for canvas-heavy sites). Each check carries the curl/grep detection command and the fix's destination shape. +- **`Mode: SEO` in `skills/audit/SKILL.md`** — rides the Shared Contract (CONFIRMED/PLAUSIBLE, stable IDs, `docs/audits/seo-audit-YYYY-MM-DD.md`), runs mechanical checks before source reading, and adds a per-check verdict table so successive audits diff against the last run. Router triggers: "seo audit", "aeo", "ai engine optimization", "answer engine", "rank better". + +**Changed:** + +- The audit skill description was compressed to fit the new mode under the 1024-char per-skill cap and the 12 KiB index budget — trigger phrases dropped where the remaining description text already carries the routing keyword ("harsh maintainability review", "docs audit", "audit the workflows", "abuse paths", "discoverability audit", "llms.txt"). + ## [13.13.0] — 2026-08-18 **Sync with Claude Code v2.1.234** (from 2.1.228; 2.1.230 was never published — the upstream changelog jumps 2.1.229 → 2.1.231). The headline is upstream removing the todo/task tools from Opus 4.8+ models; cc-settings follows the removal instead of opting back in. diff --git a/package.json b/package.json index 9ea07d1..e880b78 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cc-settings", - "version": "13.13.0", + "version": "13.14.0", "private": true, "type": "module", "engines": { diff --git a/skills/audit/SKILL.md b/skills/audit/SKILL.md index 8b3afb8..4270cf5 100644 --- a/skills/audit/SKILL.md +++ b/skills/audit/SKILL.md @@ -1,7 +1,7 @@ --- name: audit -argument-hint: "[maintainability|codebase|docs|process|debt|threat-model|motion]" -description: Whole-repo audits in seven modes. Maintainability mode — structural audit of sprawl, thin wrappers, leaked logic, dependency freshness. Triggers "nuclear review", "thermonuclear review", "code judo", "deep code quality audit", "harsh maintainability review", "whole codebase review", "should this exist". Codebase/Docs/Process modes — adversarial audits hunting defects, drift, dead ends. Codebase triggers "adversarial audit", "fable audit", "expectation gaps", "correctness audit". Docs triggers "audit the docs", "docs audit", "doc drift". Process triggers "process audit", "audit the workflows", "walk the journeys", "end-to-end audit". Threat-model mode — repo-grounded abuse-path analysis, triggers "threat model", "STRIDE", "attack surface", "abuse paths". Motion mode — animation audit, triggers "motion audit", "audit the animations". Debt mode ledgers `SHORTCUT:` markers — triggers "debt ledger", "shortcut ledger". Owns the bare "audit the codebase" — asks maintainability vs correctness when unpinned. +argument-hint: "[maintainability|codebase|docs|process|debt|threat-model|motion|seo]" +description: Whole-repo audits in eight modes. Maintainability — sprawl, thin wrappers, dependency freshness. Triggers "nuclear review", "thermonuclear review", "code judo", "deep code quality audit", "whole codebase review", "should this exist". Codebase/Docs/Process — adversarial audits hunting defects, drift, dead ends. Triggers "adversarial audit", "fable audit", "expectation gaps", "correctness audit", "audit the docs", "doc drift", "process audit", "walk the journeys", "end-to-end audit". Threat-model — abuse-path analysis. Triggers "threat model", "STRIDE", "attack surface". Motion — animation audit. Triggers "motion audit", "audit the animations". SEO — search + answer-engine discoverability (canonicals, sitemap reachability, structured data, llms.txt). Triggers "seo audit", "aeo", "ai engine optimization", "answer engine", "rank better". Debt — ledgers `SHORTCUT:` markers. Triggers "debt ledger", "shortcut ledger". Owns the bare "audit the codebase" — asks maintainability vs correctness when unpinned. context: main requires: - mcp: context7 @@ -9,16 +9,17 @@ requires: # Audit -One skill, seven whole-repo audit modes. Six of them share a skeleton: read the surface **in full** (never sample), hunt with explicit categories, and ship a prioritized, executable report or plan set. Four families of question: +One skill, eight whole-repo audit modes. Seven of them share a skeleton: read the surface **in full** (never sample), hunt with explicit categories, and ship a prioritized, executable report or plan set. Five families of question: - **Maintainability** — ported from Cursor's internal `thermo-nuclear-code-quality-review` skill (reported by Eric Zakariasson as Cursor's most-used internal skill; this mode was formerly the standalone `/nuclear-review` skill). Asks **should this code exist?** — structural quality, 1k-line sprawl, thin wrappers, code-judo deletions, dependency freshness via context7. - **Codebase, Docs, and Process** — adapted from the fable audit goal-spec trio (gist `diegomarino/04970a2b8d9cc419de3ba05b9a03db5a`; these modes were formerly the standalone `/adversarial-audit` skill). Ask **does it do what it promises?** — correctness/coherence/affordances (codebase), truth and structure of the docs (docs), walkable end-to-end journeys (process). The July 2026 cc-settings audit ran the codebase spec and produced 28 findings, ~all confirmed and fixed. The mechanics that made that work (stable IDs, CONFIRMED/PLAUSIBLE, concrete failure scenarios, design tensions vs line findings, open questions for the maintainer) are the contract for these three modes, whatever the mode. - **Threat-Model** — adapted from openai/skills `security-threat-model` (Apache-2.0). Asks **what can go wrong, and who would exploit it?** — trust boundaries, attacker capability, abuse paths tied to attacker goals, mitigations mapped to components. - **Motion** — adapted from emilkowalski/skills `improve-animations` (MIT). Asks **where does animation work have the highest leverage?** — purpose/frequency, easing/duration, physicality/origin, interruptibility, performance, accessibility, cohesion, and missed opportunities, turned into self-contained implementation plans rather than a findings report. +- **SEO** — distilled from shipped Darkroom work (satus PRs #348/#405/#413 and darkroomengineering/website PRs #40/#65, which converged independently on the same architecture). Asks **will this site be found, ranked, and cited?** — canonical integrity, sitemap reachability, per-content metadata, structured data, and the AEO surfaces (llms.txt, named AI crawlers, machine-view routes) that answer engines read. -Maintainability mode should push to be **ambitious** about code structure — do not merely identify local cleanup opportunities, actively search for "code judo" moves. The codebase, docs, process, and threat-model modes hold **no loyalty to the current design** — hunt defects, drift, dead ends, and abuse paths rather than confirm things work. +Maintainability mode should push to be **ambitious** about code structure — do not merely identify local cleanup opportunities, actively search for "code judo" moves. The codebase, docs, process, threat-model, and seo modes hold **no loyalty to the current design** — hunt defects, drift, dead ends, and abuse paths rather than confirm things work. -The seventh mode, **Debt**, is the odd one out: a mechanical grep that collects `SHORTCUT:` markers into a ledger. It shares none of the skeleton above and makes no judgement — see Mode: Debt at the end of this file. +The eighth mode, **Debt**, is the odd one out: a mechanical grep that collects `SHORTCUT:` markers into a ledger. It shares none of the skeleton above and makes no judgement — see Mode: Debt at the end of this file. ## Mode Router — disambiguate before fanning out @@ -38,6 +39,7 @@ Only proceed to the matching mode below once the answer disambiguates. This ques | Process | "process audit", "audit the workflows", "walk the journeys", "end-to-end audit" | | Threat-Model | "threat model", "STRIDE", "attack surface", "abuse paths" | | Motion | "motion audit", "audit the animations", "improve the animations" | +| SEO | "seo audit", "aeo", "ai engine optimization", "answer engine", "discoverability audit", "rank better", "llms.txt" | | Debt | "debt ledger", "shortcut ledger", "what did we defer", "what corners did we cut" | | Ambiguous — ASK | "audit the codebase" alone, or any phrasing that doesn't match a row above | @@ -49,7 +51,7 @@ in this skill. Run Debt standalone or as a cheap first pass before maintainabili ## When to use vs other review skills - `/review` — per-diff Darkroom checklist (TypeScript / React / a11y / perf / security), now including an animation checklist when the diff touches motion. Every change. -- `/audit` (this skill) — periodic whole-repo audit, seven modes. Maintainability mode asks "should this code exist?"; codebase, docs, and process modes ask "does it do what it promises?"; threat-model mode asks "what can go wrong, and who would exploit it?"; motion mode asks "where does the animation work have the highest leverage?"; debt mode asks "what did we defer on purpose?" Run maintainability and codebase mode on the same cadence (major version cuts, after extended velocity sprints, before a load-bearing migration) — they compose well back-to-back since they hunt different game. Docs and process modes shine before releases and after feature bursts. Threat-model mode fits before a security-sensitive launch or a new internet-facing surface. Motion mode fits well after a UI-heavy sprint or before a client showcase. +- `/audit` (this skill) — periodic whole-repo audit, eight modes. Maintainability mode asks "should this code exist?"; codebase, docs, and process modes ask "does it do what it promises?"; threat-model mode asks "what can go wrong, and who would exploit it?"; motion mode asks "where does the animation work have the highest leverage?"; seo mode asks "will this site be found, ranked, and cited?"; debt mode asks "what did we defer on purpose?" Run maintainability and codebase mode on the same cadence (major version cuts, after extended velocity sprints, before a load-bearing migration) — they compose well back-to-back since they hunt different game. Docs and process modes shine before releases and after feature bursts. Threat-model mode fits before a security-sensitive launch or a new internet-facing surface. Motion mode fits well after a UI-heavy sprint or before a client showcase. SEO mode fits before a site launch and as a first pass on any client marketing/content site. - `/zero-tech-debt` — rework a specific patch to its intended end-state. Not a review — it edits. - `/verify` — adversarial check of a single change/claim, not a repo sweep. @@ -386,7 +388,7 @@ Structural rubric ported from [`cursor/plugins/cursor-team-kit/skills/thermo-nuc --- -## Shared Contract (Codebase, Docs, Process, and Threat-Model modes) +## Shared Contract (Codebase, Docs, Process, Threat-Model, and SEO modes) **Role.** No loyalty to the current design/structure/flows. Act simultaneously as a senior staff engineer, a skeptical first-time consumer, and an adversarial reviewer. Understand deeply enough to challenge, not merely validate. @@ -493,6 +495,28 @@ Repo-grounded STRIDE-style threat modeling: enumerate trust boundaries, assets, **Extra output:** mitigations mapped one-to-one to the components/boundaries they protect, never a generic hardening checklist; and a QA pass confirming every entry point, boundary, and assumption is accounted for before delivery. Uses the same `docs/audits/threat-model-audit-YYYY-MM-DD.md` output path and stable-ID contract as Codebase/Docs/Process modes (Shared Contract, above). +## Mode: SEO + +Repo-grounded discoverability audit for search engines and answer engines (AEO — being findable and citable by LLM crawlers). Distilled from shipped Darkroom work: satus PRs #348/#405/#413 and darkroomengineering/website PRs #40/#65 converged independently on one architecture, and this mode encodes that destination shape as checks. Rides the Shared Contract above. + +**Role additions:** a search crawler that executes no JavaScript; an answer engine assembling a citation from a single fetch; a CMS editor who expects everything they publish to be reachable. + +**Scope.** The full discoverability surface: metadata generation (canonicals, OG, descriptions), sitemap/robots/llms.txt generation, structured-data builders, the route layer that must render every content type those surfaces enumerate — and, whenever a build or deploy is reachable, the rendered output itself. Prefer curling a `next build && next start` (or a preview deploy) over source reading alone; `next dev` skips static-generation paths that change metadata output. + +**Hunt for:** run every check in `references/seo-checks.md` (S1–S17, stable IDs — cite them in findings), grouped: + +1. **Canonical integrity** (S1–S3) — self-referential per route, never inherited from a layout; child canonicals that don't wholesale-drop shared alternates (Next replaces, never merges, a child's `alternates`); canonical and sitemap generated from one route-enumeration source. +2. **Advertised vs rendered** (S4–S5) — every sitemap URL returns 200 (the sitemap never checks reachability itself; the highest-value mechanical check in the mode); demo/example/admin routes carry their own route-level noindex, independent of sitemap exclusion. +3. **Per-content metadata** (S6–S9) — unique title/description/OG image per content item; `og:type`/`@type` matched to what the content is (editorial = Article, case study = CreativeWork); exactly one base-URL source with no inline env reads; empty CMS descriptions falling back to body-derived text, never the site-wide default. +4. **Structured data** (S10–S13) — CollectionPage + ItemList on listing pages; JSON-LD via script tag with `<` escaped (microdata is valid per Google — in client-rendered trees it's a migration suggestion, not a defect); no null/undefined/empty-array values (absent beats present-but-broken); no fabricated entity facts — an invented date passes validation and is never caught. +5. **AEO surfaces** (S14–S17) — `/llms.txt` generated from the same facts object and route enumeration as everything else; AI search/citation crawlers named explicitly in robots.txt (several only honor directives addressed by name; training-consent tokens are an owner decision, not a defect); a plain-HTML machine-view route for canvas/WebGL-heavy sites; parity between any hand-maintained page list and the sitemap. + +**Map section:** the discoverability data-flow — which module enumerates routes, which consumers read it (sitemap, llms.txt, canonicals, machine view), and every place two surfaces derive the same fact independently. Each independent derivation is a standing drift risk even while currently in agreement. + +**Method addition:** run the mechanical checks first — they are cheap, and their findings are CONFIRMED by a curl. Read source second, to locate fixes and to catch the architectural absences the curls can't see (no shared route module, no schema builder at all). A check that needs a running site when none is reachable downgrades to PLAUSIBLE from source reading — say so per finding. + +**Extra output:** a per-check verdict table (S1–S17: pass / finding ID / not-applicable) so the next audit starts from deltas; fix recommendations point at the destination shapes in `references/seo-checks.md` rather than restating them. + --- ## Mode: Motion diff --git a/skills/audit/references/seo-checks.md b/skills/audit/references/seo-checks.md new file mode 100644 index 0000000..769a057 --- /dev/null +++ b/skills/audit/references/seo-checks.md @@ -0,0 +1,294 @@ +# SEO / AEO check reference + +Working reference for the audit skill's SEO mode. Every check is mechanical +where the surface allows it: a command to run, the output that constitutes a +finding, and the destination-shape fix. Distilled from shipped Darkroom work — +satus PRs #348/#405/#413 and darkroomengineering/website PRs #40/#65, which +independently converged on the same architecture (that convergence is the +evidence these are destination shapes, not one-off fixes). + +`$BASE` below is the site under audit — a local `next build && next start`, +a preview deploy, or production. Prefer a build over `next dev` (dev skips +static generation paths that change metadata output). + +Checks are ordered by impact within each group. IDs are stable — findings in +the report cite them. + +--- + +## Group 1 — Canonical integrity + +### S1. Every route's canonical is self-referential + +A layout-level `alternates.canonical` is inherited by every child that does +not set its own — the whole subtree silently canonicalizes to one URL. + +**Detect:** + +```bash +grep -rn "canonical:" app/**/layout.tsx # a hit outside the root layout IS the finding +for path in / /about /work /articles; do # sample 3+ sibling routes + curl -s "$BASE$path" | grep -o ']*>' +done # two routes, same canonical → finding +``` + +**Fix shape:** one shared helper builds the alternates object per route +(satus `lib/seo/alternates.ts` `routeAlternates(path)`); the root layout is +the only caller passing `/`. Never a bare `canonical: '/x'` literal repeated +by hand. + +### S2. Child canonicals don't drop shared alternate links + +Next.js replaces a child's `metadata.alternates` object wholesale — it does +not merge. Any route that sets its own canonical silently loses every shared +entry the parent carried: hreflang, RSS, the `text/plain` pointer to +`/llms.txt`. The fix for S1 creates this bug unless both go through one +helper. + +**Detect:** on a route that sets its own canonical, + +```bash +curl -s "$BASE/child-route" | grep -o ']*>' +``` + +Canonical present but a root-layout alternate absent → finding. + +**Fix shape:** the same `routeAlternates()` helper always emits the per-route +canonical AND every shared entry, so a route cannot get one without the +other. + +### S3. Canonical and sitemap URLs come from one source + +A canonical that disagrees with the sitemap tells the engine to crawl one URL +and index another; the engine picks, usually wrongly. + +**Detect:** + +```bash +# portable loc extraction (BSD grep has no -P); -fsS makes a missing sitemap fail loudly +urls=$(curl -fsS "$BASE/sitemap.xml" | grep -o '[^<]*' | sed 's/<[^>]*>//g' | sort) +[ -n "$urls" ] || echo "FINDING: sitemap missing or empty" +# curl each URL, extract its canonical, compare to the sitemap entry +``` + +In source: does sitemap generation and metadata generation import from one +shared route-enumeration module, or does each keep its own hardcoded path +list? Two independent lists is the smell. + +**Fix shape:** one route module (satus `lib/seo/routes.ts`: `STATIC_ROUTES` + +`getCmsRoutes()`) consumed by sitemap, llms.txt, and metadata alike. + +--- + +## Group 2 — Advertised vs rendered + +### S4. Every sitemap URL returns 200 + +The sitemap only formats data it is given — it never checks reachability. A +CMS-backed site can enumerate documents no route renders: the editor +publishes `/about`, sees it in the sitemap, and visitors get a 404. Nothing +in CI catches it. This is the single highest-value mechanical check. + +**Detect:** + +```bash +curl -fsS "$BASE/sitemap.xml" | grep -o '[^<]*' | sed 's/<[^>]*>//g' | while read -r url; do + code=$(curl -s -o /dev/null -w '%{http_code}' "$url") + [ "$code" != "200" ] && echo "$code $url" +done +``` + +An empty URL list is itself a finding (sitemap unreachable or enumerating +nothing) — never report "all URLs 200" off zero URLs checked. + +**Fix shape:** a catch-all route rendering CMS documents at the exact slug +shape the enumeration uses (satus `app/(site)/[...slug]/page.tsx`), with the +catch-all also owning `notFound()` duty. + +### S5. Demo/example/admin routes carry their own noindex + +Sitemap exclusion is not enough — robots.txt typically allows `/` broadly, +and a crawler reaches the page via links or guessing. Starter-kit tutorial +routes, storybooks, and CMS studios all need route-level +`robots: { index: false, follow: false }`. + +**Detect:** + +```bash +grep -rn "robots.*index.*false" app/**/layout.tsx app/**/page.tsx +curl -s "$BASE/known-demo-route" | grep -o ']*>' +``` + +A route absent from the sitemap with no noindex of its own → finding. For +starter kits, pruning the demo directory at project setup beats noindex. + +--- + +## Group 3 — Per-content metadata + +### S6. Every content item has its own title/description/OG image + +A page with no metadata export inherits the parent layout's generic metadata +— two case studies with identical titles and identical social unfurls. + +**Detect:** curl 2+ sibling content pages, diff ``, +`<meta name="description">`, `og:image`. Identical output across distinct +items → finding. In source: every content route has `generateMetadata` +deriving from its own fetched data. + +### S7. `og:type` / JSON-LD `@type` matches what the content is + +Editorial writing is `article`/`Article`; portfolio and case-study pages are +`website`/`CreativeWork` — `Article` on client work invites news-content +treatment (recency decay, news carousel signals) that non-editorial content +should not get. Generic pages are `WebPage`. + +**Detect:** curl each content type, check `<meta property="og:type">` and the +JSON-LD `@type` against what the content actually is. + +### S8. One base-URL source, zero inline env reads + +Hand-copied base-URL expressions drift and lose their env fallback — the +symptom is `https://undefined/...` links in llms.txt and metadata on any +environment where the var is unset. + +**Detect:** + +```bash +grep -rn "process\.env\..*URL\|VERCEL_URL" app/ lib/ # >1 constructor site → finding +curl -s "$BASE/llms.txt" | grep -i undefined +curl -s "$BASE/sitemap.xml" | grep -i undefined +``` + +**Fix shape:** one exported constant with the fallback chain and +trailing-slash normalization defined once (satus `lib/seo/site.ts` +`BASE_URL`); every absolute-URL consumer imports it. + +### S9. Empty CMS descriptions fall back to derived, not site-wide + +Editors leave the SEO description field blank far more often than the body. +Falling through to one site-wide string makes every such page's snippet +identical. + +**Detect:** find CMS items with empty description fields; curl their pages; +`<meta name="description">` equal to the homepage's → finding. + +**Fix shape:** derive from the page's own body/excerpt, truncated ~155 chars +on a word boundary (satus `lib/utils/metadata.ts` `truncateDescription`), +wired as `description || derivedDescription`. + +--- + +## Group 4 — Structured data + +### S10. Listing pages emit CollectionPage + ItemList + +Without it, an answer engine asked "what has this studio built?" must crawl +the whole subtree; with it, membership and order are one fetch. + +**Detect:** curl each index/listing page for a JSON-LD payload with +`"@type": "CollectionPage"` and `mainEntity` of `"@type": "ItemList"`. Also +check the builder module exists at all — its absence makes the gap +architectural. All URLs in the list must be absolute; a half-relative +ItemList validates cleanly while pointing nowhere. + +### S11. JSON-LD via script tag, `<` escaped + +An unescaped `<` in a CMS-sourced string containing `</script>` closes the +JSON-LD script tag early — an XSS vector, not a rendering bug. That is the +finding here. Microdata (`itemProp`) is valid structured data per Google's +docs; but in client-rendered React trees it lives on visible DOM nodes and +gets lost or duplicated across re-renders, so flag it as a +migration-to-JSON-LD **suggestion**, never a defect. + +**Detect:** + +```bash +grep -rn "itemProp=\|itemScope" components/ app/ # presence → migration suggestion +grep -n "replace(/</g" lib/**/json-ld* # absence + CMS strings → finding +``` + +**Fix shape:** `JSON.stringify(data).replace(/</g, '\\u003c')` into a single +`<script type="application/ld+json">` (satus `lib/seo/json-ld.tsx`). + +### S12. No null/undefined/empty-array JSON-LD values + +`"description": null` reads as present-but-broken; an empty `sameAs: []` +asserts the entity has no profiles. Absent reads as "not specified" — always +better. + +**Detect:** extract each page's JSON-LD, `jq` for null or `[]` values. In +source: unconditional optional-field assignment in builders. + +**Fix shape:** build required fields, then conditionally assign optionals one +at a time — never spread a possibly-empty value. + +### S13. No fabricated entity facts + +An invented founding date or publish date passes every validator and is never +caught downstream. When the value isn't sourced, omit the field and leave a +comment marking where the real value threads through once it exists. + +**Detect:** review check, not mechanical — each entity fact in structured +data traces to a CMS field or a documented company fact. + +--- + +## Group 5 — AEO surfaces + +### S14. `/llms.txt` exists, is generated, and cannot drift + +The cheapest AEO win: a plain-text entity summary and content list an answer +engine reads in one fetch without executing JS. Hand-written prose drifts; +generate it from the same `SITE` facts object and route enumeration the +JSON-LD and sitemap use. + +**Detect:** + +```bash +curl -sI "$BASE/llms.txt" # 200, content-type text/plain +curl -s "$BASE/llms.txt" | grep -i undefined # any hit → finding (see S8) +``` + +Compare its content list against sitemap.xml — same enumeration source, same +entries. Grep the route for `SITE.` reads vs hardcoded prose. + +### S15. robots.txt names AI crawlers explicitly + +Several AI crawlers only honor directives addressed to them by name; a +wildcard `*` allow is not a guaranteed substitute — and named groups make the +allow/deny decision reviewable per bot. + +**Detect:** `curl -s "$BASE/robots.txt"` — look for explicit groups covering +at minimum the search/citation crawlers: `OAI-SearchBot`, `ChatGPT-User`, +`Claude-User`, `Claude-SearchBot`, `PerplexityBot`. These are the +discoverability surface — absent groups are the finding. + +Training-consent tokens (`GPTBot`, `ClaudeBot`, `Google-Extended`) are a +separate decision: they control whether content trains or grounds models, not +whether it's found or cited (`Google-Extended` is a robots.txt token, not a +crawler, and has no effect on Search indexing or ranking). Report their +absence as an open question for the site owner — never as a defect, and never +push a default. Allowing them is an AEO-reach choice; blocking them is a +content-rights choice. Both are owner calls. + +### S16. Visually-heavy sites ship a plain-HTML machine view + +WebGL canvases and client-rendered copy give a non-JS-executing crawler +nothing to cite. The fix is one deliberately plain, server-only route +(satus `/ai`): semantic elements, entity facts as a `<dl>`, every real page +linked with bare `<a href>`, and a "for agents" footer linking `/llms.txt`, +`/sitemap.xml`, `/robots.txt`. + +**Detect:** if `grep -rn "three\|<canvas" app/ components/` confirms a +canvas-heavy site, `curl -s "$BASE/" | grep -c '<p>\|<h1>\|<h2>'` near zero → +the site needs the route; then check one exists and is genuinely server-only +(no `'use client'` in its tree). + +### S17. Sitemap ↔ machine-view parity + +Any manually-maintained page list (the `/ai` route's link list) must stay in +sync with the sitemap — a route missing from either is invisible to the +surface it's missing from. + +**Detect:** diff the machine view's hrefs against sitemap `<loc>` paths. diff --git a/src/setup.ts b/src/setup.ts index b4ef348..16335ed 100644 --- a/src/setup.ts +++ b/src/setup.ts @@ -84,7 +84,7 @@ import { import type { McpStdioServer } from "./schemas/mcp.ts"; import { Settings } from "./schemas/settings.ts"; -const VERSION = "13.13.0"; // upstream sync 2.1.234: todo-tool strip, sandbox.ripgrep, marketplace aliases +const VERSION = "13.14.0"; // audit skill: SEO/AEO discoverability mode // --- Arg parsing ---------------------------------------------------------