Skip to content

feat(wiki): align llms.txt exports with the llms.txt v2 spec - #356

Open
davida-ps wants to merge 1 commit into
mainfrom
claude/llms-txt-v2-alignment
Open

feat(wiki): align llms.txt exports with the llms.txt v2 spec#356
davida-ps wants to merge 1 commit into
mainfrom
claude/llms-txt-v2-alignment

Conversation

@davida-ps

@davida-ps davida-ps commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

User description

The problem

The llms.txt v2 spec says:

The links in an llms.txt file should therefore point to LLM-friendly content, such as the markdown versions of pages described above.

Ours pointed at SPA hash routes (https://clawsec.prompt.security/#/wiki/overview). A #... fragment is never sent to the server, so an agent following a link from our llms.txt gets the app shell and no content:

GET /#/wiki/overview   -> 5189 bytes, contains Overview content? NO
GET /wiki/overview/llms.txt -> 6856 bytes, contains Overview content? YES

That's the same dead-end #349 was filed about, one step further along: we fixed the 404s, then pointed the links at a target that serves nothing.

Four more gaps against v2, found while reading the spec:

Gap v2 says
Two # H1s per export (wrapper + inlined body) "An H1 with the name of the project or site. This is the only required section"
No blockquote summary "A blockquote with a short summary of the project"
## Markdown inlined an entire page body H2 sections contain "file lists of URLs"; v2 "shifted... to emphasizing that links should point to LLM-friendly content rather than requiring full page content inline"
/llms.txt was 404; no rel discovery v2 adds rel="alternate" type="text/markdown" and rel="describedby"

(/wiki/llms.txt living at a subpath is fine — v2 explicitly allows it: "a file covers the pages under its path, and the most specific file applies".)

What changed

  • /wiki/<slug>.md — new. A clean markdown alternate per page (122 of them): internal links rewritten to other .md alternates, assets to raw source. This is the content surface agents read. Side effect: /wiki/overview.md, the URL llms.txt exports ship dead internal links (all 121 exports; repo-relative .md and asset URLs 404) #349 assumed existed, is now real.
  • /wiki/llms.txt — rebuilt as a conformant index. One H1, blockquote summary, H2 link-list sections pointing at the .md alternates. Sections and ordering are derived from wiki/INDEX.md, so the curated structure is preserved rather than invented; prose-only sections (Summary, Update Notes, Source References) contribute no links and are dropped.
  • /llms.txt — new site-root index.
  • Discovery — static rel="describedby" in index.html, plus a rel="alternate" type="text/markdown" in WikiBrowser.tsx that tracks the active route (needed because this is an SPA, so a static tag would be wrong on every page but one).
  • /wiki/<slug>/llms.txt — unchanged and still published, so anything already consuming those URLs keeps working. No breaking change.

Verification

  • Export gate extended and passing 6/6, including two new checks: a v2 structure test (exactly one H1, blockquote present, every line under an H2 must be a link-list item — this is what catches an inlined page body) and a rule that fails on any SPA hash route used as a link target.
  • Confirmed in a browser against a live dev server: both discovery links render, rel="alternate" updates across route changes (/#/wiki/overview/wiki/overview.md, /#/wiki/es/index/wiki/es/index.md), and fetching the advertised alternate returns real markdown (200, correct localized content).
  • dist/ contains all 122 .md alternates, /llms.txt, /wiki/llms.txt, and the retained per-page exports. 0 SPA hash routes remain as link targets anywhere in the build.
  • scripts/test-wiki-sync-export.mjs 4/4 and scripts/test-deploy-pages-checksums.mjs pass — GitHub Wiki mirror and advisory pipelines unaffected.
  • npx eslint . --max-warnings 0, npx tsc --noEmit, npm run build all clean.

🤖 Generated with Claude Code


Generated description

Below is a concise technical summary of the changes proposed in this PR:
Align wiki and site llms.txt exports with the v2 specification by generating markdown page alternates and structured link indexes. Update generate-wiki-llms, WikiBrowser, and static discovery metadata so agents receive real content through route-aware markdown links.

TopicDetails
Markdown Discovery Advertise the site and active wiki page markdown alternates through rel="describedby" and route-aware rel="alternate" metadata, with tests preventing SPA hash routes and broken targets.
Modified files (3)
  • index.html
  • pages/WikiBrowser.tsx
  • scripts/test-wiki-llms-export.mjs
Latest Contributors(2)
UserCommitDate
David.a@prompt.securityfeat(wiki): align llms...September 01, 2026
david.a@prompt.securityfix(wiki): rewrite int...September 01, 2026
LLM Markdown Exports Generate per-page .md alternates with rewritten internal links, rebuild wiki and site indexes with one H1, summaries, and curated link-list sections, while retaining existing per-page exports.
Modified files (3)
  • .gitignore
  • scripts/generate-wiki-llms.mjs
  • scripts/test-wiki-llms-export.mjs
Latest Contributors(2)
UserCommitDate
David.a@prompt.securityfeat(wiki): align llms...September 01, 2026
david.a@prompt.securityfix(wiki): rewrite int...September 01, 2026
Review this PR on Baz | Customize your next review

The llms.txt v2 spec states that "the links in an llms.txt file should
therefore point to LLM-friendly content, such as the markdown versions
of pages". Ours pointed at SPA hash routes (`/#/wiki/overview`). A
`#...` fragment is never sent to the server, so an agent following a
link received the 5KB app shell with none of the page content -- the
same dead-end #349 was filed about, just one step further along.

- Publish a clean markdown alternate per page at /wiki/<slug>.md, with
  internal links rewritten to other .md alternates and assets to raw
  source. This is the content surface agents read, and it makes
  /wiki/overview.md -- the URL #349 assumed existed -- real.
- Rebuild /wiki/llms.txt as a conformant index: exactly one H1, a
  blockquote summary, then H2 sections that are link lists pointing at
  the .md alternates. Sections and ordering come from wiki/INDEX.md, so
  the curated structure is preserved; prose-only sections are dropped.
  Previously each export carried two H1s and inlined a whole page body
  under an H2, which is not the shape agents are told to expect.
- Add a site-root /llms.txt, which was 404.
- Add v2 discovery: a static rel="describedby" to /llms.txt, and a
  rel="alternate" type="text/markdown" that tracks the active wiki
  route in the SPA.
- Keep /wiki/<slug>/llms.txt unchanged so existing consumers of those
  URLs are unaffected.

The export gate gains a v2 structure test (single H1, blockquote, H2
bodies must be link lists) and now fails on any SPA hash route used as
a link target. Verified in a browser that both discovery links render
and that the advertised alternate returns real markdown.
@baz-reviewer

baz-reviewer Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Merger

Needs Review

The retained per-page /llms.txt exports still advertise SPA hash routes, leaving agents with the app shell instead of markdown content. This is a diff-confirmed correctness issue on the shipped path and remains unresolved.

Commit 1bfcef5 · Evaluated 2026-09-01 14:50 UTC

Review this PR on Baz | Customize your next review

Comment on lines +152 to +163
const extractIndexSections = (indexDoc, docsBySlug) => {
if (!indexDoc) return [];

const sections = [];
let current = null;

for (const line of indexDoc.content.split(/\r?\n/)) {
const heading = /^##\s+(.+?)\s*$/.exec(line);
if (heading) {
current = { title: heading[1], entries: [] };
sections.push(current);
continue;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate wiki index parsing drifts

extractIndexSections duplicates the markdown section and wiki-link parsing in extractLinkedSections, so the active generators can diverge in link retention and path resolution — should we extract a shared helper using utils/wikiPathHelpers.mjs and let each generator map the resolved entries to its output shape?

Severity

Want Baz to fix this for you? Activate Fixer

Comment on lines +166 to +175
const item = /^\s*-\s+\[([^\]]+)\]\(([^)\s]+)\)/.exec(line);
if (!current || !item) continue;

const [, label, href] = item;
const { path: resolvedPath, aboveWikiRoot } = resolveLinkPath(indexDoc.relativePath, splitWikiHash(href).path);
if (aboveWikiRoot > 0 || !resolvedPath.toLowerCase().endsWith('.md')) continue;

const slug = resolvedPath.replace(/\.md$/i, '').toLowerCase();
const doc = docsBySlug.get(slug);
if (doc) current.entries.push({ label, doc });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curated pages silently disappear from llms index

([^)\s]+) rejects valid Markdown links such as - [Overview](overview.md "page title") and - [Asset](<assets/my page.md>), so the loop silently omits entries while buildLlmsIndex still writes a successful /wiki/llms.txt — should we parse these forms or fail when a wiki/INDEX.md entry cannot be represented?

Severity

Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents
Before applying, verify this suggestion against the current code. In
`scripts/generate-wiki-llms.mjs` around lines 166-175, update `extractIndexSections` so
its Markdown list-item parser accepts valid link destinations such as optional titles
and angle-bracketed paths containing spaces. Track list entries that cannot be resolved
or represented, and throw a clear error instead of silently omitting them, so
`/wiki/llms.txt` cannot be generated with an incomplete curated index.

* @returns {Promise<{ pageCount: number, outputFiles: string[] }>}
*/
export const generateWikiLlms = async ({ wikiRoot, publicWikiRoot }) => {
export const generateWikiLlms = async ({ wikiRoot, publicWikiRoot, publicRoot }) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale API contract hides generated artifacts

The exported JSDoc for generateWikiLlms still advertises only { wikiRoot, publicWikiRoot } and { pageCount, outputFiles }, while .md alternates are returned separately in markdownFiles and rootIndexFile is nullable, so consumers see an incomplete contract — should we document optional publicRoot and the distinct outputFiles, markdownFiles, and rootIndexFile: string | null returns?

Severity

Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents
Before applying, verify this suggestion against the current code. In
`scripts/generate-wiki-llms.mjs` around lines 220-220, update the JSDoc for
`generateWikiLlms` to include the optional `publicRoot` parameter. Revise the return
type documentation to include `markdownFiles: string[]` and `rootIndexFile: string |
null`, and clarify that `outputFiles` contains the llms.txt outputs while markdown
alternates are tracked separately.

Comment on lines +257 to 261
// Retained unchanged so anything already consuming /wiki/<slug>/llms.txt keeps working.
if (isWikiIndexSlug(doc.slug)) continue;
const outputFile = path.join(publicWikiRoot, doc.slug, 'llms.txt');
await fs.mkdir(path.dirname(outputFile), { recursive: true });
await fs.writeFile(outputFile, buildPageBody(doc, slugSet), 'utf8');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Legacy exports retain dead SPA canonical links

The retained /wiki/<slug>/llms.txt compatibility export still sets Wiki page to https://clawsec.prompt.security/#/wiki/<slug> in buildPageBody, so agents following pageLlmsPath reach the SPA shell instead of the canonical /wiki/<slug>.md content — should we update that metadata to the generated .md URL while preserving /llms.txt?

Severity

Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents
Before applying, verify this suggestion against the current code. In
scripts/generate-wiki-llms.mjs around lines 257-261, update the compatibility
`/wiki/<slug>/llms.txt` generation and its `buildPageBody` metadata so the `Wiki page`
link points to the generated `/wiki/<slug>.md` URL instead of the obsolete
`/#/wiki/<slug>` SPA route. Preserve the legacy `llms.txt` files and endpoint paths, but
ensure all advertised page links resolve directly to LLM-readable markdown content.

@wallidsaydi-creator

Copy link
Copy Markdown

Ran the generator locally on this branch to verify the full export surface before/after — here's the quantified state, including the one gap baz-reviewer flagged:

Verified working (ran node scripts/generate-wiki-llms.mjs on this branch):

  • 122 .md alternates generated, internal links rewritten to sibling .md targets, assets to raw source — clean.
  • New /wiki/llms.txt index: 32 entries across INDEX-derived sections, every link target is a .md alternate (0 non-md targets), single H1 + blockquote + H2 link lists — v2-conformant per my read of the spec.
  • Root /llms.txt with the describedby discovery + rel="alternate" injection in WikiBrowser.tsx (route-synced via useEffect, correctly cleaned up on unmount).

The retained gap, quantified (this is what the bot's verdict is about):

All 121 per-page /wiki/<slug>/llms.txt exports still carry the SPA hash route in their Canonical section:

## Canonical
- Wiki page: https://clawsec.prompt.security/#/wiki/overview   ← fragment never reaches the server
- LLM export: https://clawsec.prompt.security/wiki/overview/llms.txt

Live agent experience today (verified 3 passes): the hash route serves the 5,189-byte app shell with zero page content; the .md alternate that does carry content 404s until this ships. So an agent consuming a legacy per-page export post-merge still has exactly one content-bearing target — the llms.txt itself — and one dead-end labeled "Wiki page".

One-line hardening if you want it: toWikiMarkdownUrl() already exists in this diff — adding a Markdown alternate: ${toWikiMarkdownUrl(doc.slug)} line to buildPageBody's Canonical section gives every legacy consumer a direct content path without breaking the retained format. Same reasoning as the index repoint, one level down.

(Minor, same theme: the per-page exports also retain the full-page-body ## Markdown inline — row 3 of your gap table — so the v2 "link, don't inline" principle lands on the index but not the per-page surface. Understandable for backwards compat; just flagging that the two surfaces now intentionally diverge on that.)

The export-time link-checker offer from #349 stands if useful for CI — it would have caught both the original 404 wave and this canonical gap pre-deploy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants