Skip to content

chore: improve llms.txt for AI agents#715

Merged
rsbh merged 10 commits intomainfrom
chore_docs_mdx_response
Mar 20, 2026
Merged

chore: improve llms.txt for AI agents#715
rsbh merged 10 commits intomainfrom
chore_docs_mdx_response

Conversation

@rsbh
Copy link
Copy Markdown
Member

@rsbh rsbh commented Mar 18, 2026

Summary

  • Return MDX content instead of HTML for AI agent consumption
  • Add Content-Type: text/markdown header to all LLM routes (llms.txt, llms-full.txt, llms.mdx)
  • Always append .mdx suffix to URLs in llms.txt so agents follow links to markdown, not HTML
  • Add per-category design token routes (/tokens.mdx/colors, /tokens.mdx/spacing, /tokens.mdx/typography, /tokens.mdx/effects, /tokens.mdx/radius)
  • Add script (generate:tokens) to generate flat, dense token markdown from CSS source files

Test plan

  • curl http://localhost:3001/llms.txt — URLs should have .mdx suffix, tokens section listed
  • curl http://localhost:3001/tokens.mdx/colors — flat token list, no tables/prose
  • curl http://localhost:3001/docs/components/button.mdx — returns markdown via rewrite
  • npm run generate:tokens — regenerates token files from CSS

Summary by CodeRabbit

  • New Features

    • Added a documentation endpoint that serves design token pages by category in Markdown with static route generation.
  • Improvements

    • Standardized HTTP Content-Type to text/markdown for Markdown endpoints.
    • Updated documentation link paths to include explicit .mdx targets.
    • Extended generated Markdown with a tokens index section linking key token pages.
    • Added a routing rewrite to support token page URLs.

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 18, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
apsara Ready Ready Preview, Comment Mar 20, 2026 5:18am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 18, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5e289c7e-a62c-469a-b933-b0c8574c503d

📥 Commits

Reviewing files that changed from the base of the PR and between 6922481 and d9f5399.

📒 Files selected for processing (2)
  • apps/www/next.config.mjs
  • apps/www/src/app/(llms)/llms.txt/route.ts

📝 Walkthrough

Walkthrough

Adds Content-Type: text/markdown headers to several LLM Markdown routes, appends .mdx links and a tokens section to the LLM text route, and introduces a new tokens.mdx/[category] route that reads, parses, and renders CSS design tokens as Markdown.

Changes

Cohort / File(s) Summary
Markdown Response Headers
apps/www/src/app/(llms)/llms-full.txt/route.ts, apps/www/src/app/(llms)/llms.mdx/[[...slug]]/route.ts
Add Content-Type: text/markdown header to GET responses; import reorder in llms.mdx route.
LLM Text Route Enhancement
apps/www/src/app/(llms)/llms.txt/route.ts
Append ## tokens section with links to token pages, change page links to ${page.url}.mdx, and set Content-Type: text/markdown.
Token Documentation Route
apps/www/src/app/(llms)/tokens.mdx/[category]/route.ts
New route: exports GET and generateStaticParams; validates categories, reads CSS from shared styles, parses sections/tokens (parseCSSFile), renders Markdown (toMarkdown), returns Content-Type: text/markdown, revalidate = false.
Next Config Rewrite
apps/www/next.config.mjs
Add rewrite mapping /tokens/:path*.mdx/tokens.mdx/:path*.

Sequence Diagram

sequenceDiagram
    actor Client
    participant Router as Token Route Handler
    participant FS as File System
    participant Parser as parseCSSFile
    participant Formatter as toMarkdown

    Client->>Router: GET /tokens.mdx/{category}
    Router->>Router: Validate category in CATEGORIES
    alt invalid category
        Router->>Client: notFound()
    else valid category
        Router->>FS: Read CSS file for category
        alt file missing
            Router->>Client: notFound()
        else file found
            FS-->>Router: CSS content
            Router->>Parser: parseCSSFile(content)
            Parser-->>Router: sections & tokens
            Router->>Formatter: toMarkdown(parsed)
            Formatter-->>Router: Markdown string
            Router->>Client: 200 Response (Content-Type: text/markdown)
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • rohanchkrabrty

Poem

🐰 Hopping through routes with a twitch and a wink,

Headers set tidy, links now in sync,
Tokens parsed neatly from CSS lines,
Markdown blooms forth in organized signs,
A rabbit applauds this tidy design! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'chore: improve llms.txt for AI agents' is directly related to the main changeset objectives, which focus on improving the llms.txt endpoint and adding markdown content delivery for AI agents.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore_docs_mdx_response
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
apps/www/package.json (1)

10-10: Consider wiring token generation into the build pipeline.

Line 10 adds the generator, but it isn’t invoked by build, so tokens.md can become stale.

♻️ Suggested change
-    "build": "next build",
+    "build": "npm run generate:tokens && next build",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/www/package.json` at line 10, The build pipeline currently doesn't
invoke the "generate:tokens" script, so tokens.md can go stale; update the
package.json "build" script to run "generate:tokens" as part of the build (e.g.,
run "npm run generate:tokens" before or as part of the existing "build" script)
so that the "generate:tokens" script is executed automatically during builds and
keeps tokens.md up to date; reference the "generate:tokens" script name and the
"build" script when making this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/www/scripts/generate-tokens.mjs`:
- Around line 51-65: The parser currently assigns currentComment =
commentMatch[1] and then clears it when the comment startsWith('Example
usage:'), which overwrites and drops existing section headings; instead, only
set currentComment when the extracted comment is not an "Example usage:" line —
i.e., replace the assignment in the comment-handling block so you check
commentMatch[1] first (e.g., let extracted = commentMatch[1]; if
(!extracted.startsWith('Example usage:')) currentComment = extracted;) and leave
currentComment untouched when skipping example comments so sections, selector,
and tokens logic (sections, currentSelector, tokens) retain the prior heading.

In `@apps/www/src/app/`(llms)/llms.txt/route.ts:
- Around line 17-19: The generated links use page.url + ".mdx" which produces
non-resolvable /docs/*.mdx paths; change the link target to the catch-all LLMS
route by using the slug segments from page.slugs (e.g., join with '/'), so build
the link as "/llms.mdx/" + page.slugs.join('/') instead of `${page.url}.mdx`,
keeping the same display text (page.data.title) and description
(page.data.description) in the list.push call in route.ts.

---

Nitpick comments:
In `@apps/www/package.json`:
- Line 10: The build pipeline currently doesn't invoke the "generate:tokens"
script, so tokens.md can go stale; update the package.json "build" script to run
"generate:tokens" as part of the build (e.g., run "npm run generate:tokens"
before or as part of the existing "build" script) so that the "generate:tokens"
script is executed automatically during builds and keeps tokens.md up to date;
reference the "generate:tokens" script name and the "build" script when making
this change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 45cbd980-b3f8-4a1d-afe7-5497d7224a47

📥 Commits

Reviewing files that changed from the base of the PR and between f77adb8 and 7ba4c61.

📒 Files selected for processing (7)
  • apps/www/package.json
  • apps/www/scripts/generate-tokens.mjs
  • apps/www/src/app/(llms)/llms-full.txt/route.ts
  • apps/www/src/app/(llms)/llms.mdx/[[...slug]]/route.ts
  • apps/www/src/app/(llms)/llms.txt/route.ts
  • apps/www/src/app/(llms)/tokens.mdx/route.ts
  • apps/www/src/app/(llms)/tokens.mdx/tokens.md

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
apps/www/scripts/generate-tokens.mjs (1)

53-65: ⚠️ Potential issue | 🟡 Minor

Don’t clobber section comments when skipping “Example usage” lines.

At Line 63 and Line 64, assigning then nulling currentComment drops the previous heading context, which can mislabel sections in output.

Proposed fix
-    const commentMatch = trimmed.match(/^\/\*\s*(.+?)\s*\*\/$/);
+    const commentMatch = trimmed.match(/^\/\*\s*(.+?)\s*\*\/$/);
     if (commentMatch && currentSelector) {
+      const nextComment = commentMatch[1];
+      if (nextComment.startsWith('Example usage:')) continue;
+
       if (tokens.length) {
         sections.push({
           selector: currentSelector,
           comment: currentComment,
           tokens: [...tokens]
         });
         tokens = [];
       }
-      currentComment = commentMatch[1];
-      if (currentComment.startsWith('Example usage:')) currentComment = null;
+      currentComment = nextComment;
       continue;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/www/scripts/generate-tokens.mjs` around lines 53 - 65, The code assigns
currentComment = commentMatch[1] then immediately nulls it for "Example usage:"
comments which clobbers the previous section heading; change the logic in the
comment-handling block (the commentMatch/currentSelector flow that pushes into
sections and resets tokens) so you do not overwrite currentComment when the
matched comment starts with "Example usage:" — either only assign currentComment
when commentMatch[1] does not startWith('Example usage:') or move the startsWith
check before assigning; keep existing pushes to sections (sections, tokens,
currentSelector) unchanged.
🧹 Nitpick comments (1)
apps/www/scripts/generate-tokens.mjs (1)

99-121: Automate token generation in the build lifecycle to prevent drift.

main() is only executed when this script is run manually, but the runtime route serves tokens.md directly. Please wire generation into build/prebuild so docs stay in sync with CSS tokens.

Suggested wiring (outside this file)
--- a/apps/www/package.json
+++ b/apps/www/package.json
@@
-    "prebuild": "cd ../.. && npm run build:apsara",
+    "prebuild": "cd ../.. && npm run build:apsara && npm run generate:tokens",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/www/scripts/generate-tokens.mjs` around lines 99 - 121, The
generate-tokens script currently only runs when main() is executed manually;
wire it into the project build by invoking this script as a build/prebuild step
(e.g., add a package.json script entry that runs node
apps/www/scripts/generate-tokens.mjs before the docs/build step), ensuring the
same FILES/OUTPUT constants and main() entry are used so tokens.md is
regenerated automatically; update CI/build pipelines to run that package script
(or add a "prebuild" lifecycle script) so build/route serving tokens.md always
reflects the CSS token source.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@apps/www/scripts/generate-tokens.mjs`:
- Around line 53-65: The code assigns currentComment = commentMatch[1] then
immediately nulls it for "Example usage:" comments which clobbers the previous
section heading; change the logic in the comment-handling block (the
commentMatch/currentSelector flow that pushes into sections and resets tokens)
so you do not overwrite currentComment when the matched comment starts with
"Example usage:" — either only assign currentComment when commentMatch[1] does
not startWith('Example usage:') or move the startsWith check before assigning;
keep existing pushes to sections (sections, tokens, currentSelector) unchanged.

---

Nitpick comments:
In `@apps/www/scripts/generate-tokens.mjs`:
- Around line 99-121: The generate-tokens script currently only runs when main()
is executed manually; wire it into the project build by invoking this script as
a build/prebuild step (e.g., add a package.json script entry that runs node
apps/www/scripts/generate-tokens.mjs before the docs/build step), ensuring the
same FILES/OUTPUT constants and main() entry are used so tokens.md is
regenerated automatically; update CI/build pipelines to run that package script
(or add a "prebuild" lifecycle script) so build/route serving tokens.md always
reflects the CSS token source.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a88f997a-c7b4-4ab5-8082-3a9e1c077a09

📥 Commits

Reviewing files that changed from the base of the PR and between 7ba4c61 and 3c57a2b.

📒 Files selected for processing (2)
  • apps/www/scripts/generate-tokens.mjs
  • apps/www/src/app/(llms)/tokens.mdx/tokens.md

@rsbh rsbh changed the title chore: add docs link for css variables list chore: improve llms.txt for AI agents Mar 18, 2026
Remove generate-tokens script and pre-generated .md files. The tokens
route now parses CSS files on demand with early 404 if file is missing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
apps/www/src/app/(llms)/tokens.mdx/[category]/route.ts (2)

107-113: Avoid fs.stat + fs.readFile split; read once and map ENOENT to 404.

Line 107 does a separate existence check before Line 113 reads the file. This adds duplicate I/O and a race window (file can disappear between calls).

🛠️ Single-read pattern
-  const exists = await fs.stat(filePath).then(
-    () => true,
-    () => false
-  );
-  if (!exists) notFound();
-
-  const content = await fs.readFile(filePath, 'utf-8');
+  let content: string;
+  try {
+    content = await fs.readFile(filePath, 'utf-8');
+  } catch (error) {
+    if ((error as NodeJS.ErrnoException).code === 'ENOENT') notFound();
+    throw error;
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/www/src/app/`(llms)/tokens.mdx/[category]/route.ts around lines 107 -
113, Replace the two-step existence check (fs.stat -> exists -> notFound()) plus
fs.readFile with a single fs.readFile call wrapped in try/catch: attempt to read
filePath with fs.readFile('utf-8') and on error check if err.code === 'ENOENT'
then call notFound(), otherwise rethrow or propagate the error; remove the prior
fs.stat usage to avoid duplicate I/O and the race condition between stat and
read.

10-10: Add explicit dynamic = 'force-static' for clarity (optional).

generateStaticParams is sufficient to pre-render the route for all specified categories at build time. However, adding export const dynamic = 'force-static' makes the static behavior explicit and prevents confusion about caching intent.

♻️ Suggested alignment
+export const dynamic = 'force-static';
 export const revalidate = false;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/www/src/app/`(llms)/tokens.mdx/[category]/route.ts at line 10, The route
currently exports "revalidate = false" but should also explicitly export
"dynamic = 'force-static'"; update the file to add an export const dynamic =
'force-static' alongside the existing export const revalidate = false so the
route's static pre-rendering intent is explicit (refer to the exports named
revalidate and add the new dynamic export).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/www/src/app/`(llms)/tokens.mdx/[category]/route.ts:
- Around line 5-8: STYLES_DIR currently uses path.resolve(process.cwd(),
'../../packages/raystack/styles') which is brittle in Vercel; replace it with a
defensive candidate-resolution: build an ordered list of plausible bases (e.g.,
process.cwd(), path.join(process.cwd(), 'apps/www'), possibly __dirname), map
each to a candidate via path.resolve(base, '../../packages/raystack/styles'),
use fs.existsSync to pick the first existing candidate and assign that to
STYLES_DIR (and if none exist, throw a clear error). Update the const STYLES_DIR
declaration in route.ts and keep references to path.resolve, process.cwd(), and
add fs.existsSync checks so the route works regardless of working directory.

---

Nitpick comments:
In `@apps/www/src/app/`(llms)/tokens.mdx/[category]/route.ts:
- Around line 107-113: Replace the two-step existence check (fs.stat -> exists
-> notFound()) plus fs.readFile with a single fs.readFile call wrapped in
try/catch: attempt to read filePath with fs.readFile('utf-8') and on error check
if err.code === 'ENOENT' then call notFound(), otherwise rethrow or propagate
the error; remove the prior fs.stat usage to avoid duplicate I/O and the race
condition between stat and read.
- Line 10: The route currently exports "revalidate = false" but should also
explicitly export "dynamic = 'force-static'"; update the file to add an export
const dynamic = 'force-static' alongside the existing export const revalidate =
false so the route's static pre-rendering intent is explicit (refer to the
exports named revalidate and add the new dynamic export).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 394c5712-124d-4cc4-9c5a-697964d72aa0

📥 Commits

Reviewing files that changed from the base of the PR and between 3c57a2b and 6922481.

📒 Files selected for processing (2)
  • apps/www/src/app/(llms)/llms.txt/route.ts
  • apps/www/src/app/(llms)/tokens.mdx/[category]/route.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/www/src/app/(llms)/llms.txt/route.ts

@rsbh rsbh requested a review from rohanchkrabrty March 18, 2026 11:11
Copy link
Copy Markdown
Contributor

@rohanchkrabrty rohanchkrabrty left a comment

Choose a reason for hiding this comment

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

Let's add a rewrite for tokens/*.mdx -> tokens.mdx/*. So that the public route exposed is in the format tokens/*.mdx

async rewrites() {
    return [
      {
        source: '/tokens/*.mdx',
        destination: '/tokens.mdx/*'
      },

Rewrite /tokens/:path*.mdx to /tokens.mdx/:path* and update llms.txt
links to use the public URL format.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@rsbh rsbh merged commit 41ee723 into main Mar 20, 2026
4 checks passed
@rsbh rsbh deleted the chore_docs_mdx_response branch March 20, 2026 05:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants