Skip to content

feat(docs): add LLMs.txt generation for AI-friendly documentation#1870

Merged
manudeli merged 13 commits intomainfrom
feat/llms-txt
Jan 7, 2026
Merged

feat(docs): add LLMs.txt generation for AI-friendly documentation#1870
manudeli merged 13 commits intomainfrom
feat/llms-txt

Conversation

@kangju2000
Copy link
Copy Markdown
Member

Overview

Closes #1858

Summary

Add LLMs.txt support for AI-friendly documentation. This generates machine-readable files that help LLMs understand Suspensive's documentation structure.

Generated Files

  • /llms.txt - Overview with links to all docs
  • /llms-full.txt - Complete documentation in single file
  • /docs/**/*.md - Individual markdown files

Implementation

  • Parse Nextra _meta.tsx to preserve sidebar ordering and section titles
  • Transform Nextra components to standard markdown (Tabs → headers, Callout → blockquotes)
  • Extract first sentence as description while preserving markdown links and inline code
  • Bypass i18n middleware for static .txt and .md files
  • Generate files during build (next build && tsx scripts/llms-txt/generate-llms-txt.ts)

PR Checklist

  • I did below actions if need
  1. I read the Contributing Guide
  2. I added documents and tests.

@kangju2000 kangju2000 self-assigned this Jan 6, 2026
@kangju2000 kangju2000 requested a review from manudeli as a code owner January 6, 2026 09:04
@coauthors
Copy link
Copy Markdown

coauthors Bot commented Jan 6, 2026

People can be co-author:

Candidate Reasons Count Add this as commit message
@Copilot #1870 (comment) #1870 (comment) #1870 (comment) #1870 (comment) #1870 (comment) #1870 (comment) #1870 (comment) #1870 (comment) #1870 (comment) 9 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@manudeli #1870 (comment) #1870 (comment) #1870 (comment) #1870 (review) #1870 (review) #1870 (review) #1870 (comment) 7 Co-authored-by: manudeli <61593290+manudeli@users.noreply.github.com>
@kangju2000 #1870 (comment) #1870 (review) #1870 3 Co-authored-by: kangju2000 <23312485+kangju2000@users.noreply.github.com>
@codecov-commenter #1870 (comment) 1 Co-authored-by: codecov-commenter <65553080+codecov-commenter@users.noreply.github.com>
@tooooo1 #1870 (comment) 1 Co-authored-by: tooooo1 <77133565+tooooo1@users.noreply.github.com>

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Jan 6, 2026

⚠️ No Changeset found

Latest commit: 5bb728a

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Jan 6, 2026

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

Project Deployment Review Updated (UTC)
suspensive-next-streaming-react-query Ready Ready Preview, Comment Jan 7, 2026 2:28am
v2.suspensive.org Ready Ready Preview, Comment Jan 7, 2026 2:28am
v3.suspensive.org Ready Ready Preview, Comment Jan 7, 2026 2:28am
visualization.suspensive.org Ready Ready Preview, Comment Jan 7, 2026 2:28am

Comment thread docs/suspensive.org/scripts/llms-txt/document-processor.ts Fixed
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Jan 6, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.53%. Comparing base (9965ec4) to head (4d59cc4).
⚠️ Report is 12 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main    #1870   +/-   ##
=======================================
  Coverage   93.53%   93.53%           
=======================================
  Files          45       45           
  Lines         727      727           
  Branches      185      185           
=======================================
  Hits          680      680           
  Misses         41       41           
  Partials        6        6           
Components Coverage Δ
@suspensive/react 96.58% <ø> (ø)
@suspensive/react-dom 100.00% <ø> (ø)
@suspensive/react-query 100.00% <ø> (ø)
@suspensive/react-query-4 100.00% <ø> (ø)
@suspensive/react-query-5 100.00% <ø> (ø)
@suspensive/jotai 100.00% <ø> (ø)
@suspensive/codemods 81.60% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jan 6, 2026

Size Change: 0 B

Total Size: 88.6 kB

ℹ️ View Unchanged
Filename Size
packages/jotai/dist/Atom-********.cjs 328 B
packages/jotai/dist/Atom-********.mjs 263 B
packages/jotai/dist/Atom.cjs 93 B
packages/jotai/dist/Atom.mjs 87 B
packages/jotai/dist/AtomValue-********.cjs 315 B
packages/jotai/dist/AtomValue-********.mjs 247 B
packages/jotai/dist/AtomValue.cjs 99 B
packages/jotai/dist/AtomValue.mjs 93 B
packages/jotai/dist/index.cjs 150 B
packages/jotai/dist/index.mjs 133 B
packages/jotai/dist/SetAtom-********.cjs 313 B
packages/jotai/dist/SetAtom-********.mjs 246 B
packages/jotai/dist/SetAtom.cjs 97 B
packages/jotai/dist/SetAtom.mjs 91 B
packages/next/dist/index.cjs 256 B
packages/next/dist/index.mjs 250 B
packages/next/dist/react-******.cjs 217 B
packages/next/dist/react-******.mjs 213 B
packages/react-dom/dist/FadeIn-********.cjs 471 B
packages/react-dom/dist/FadeIn-********.mjs 402 B
packages/react-dom/dist/FadeIn.cjs 96 B
packages/react-dom/dist/FadeIn.mjs 90 B
packages/react-dom/dist/index.cjs 176 B
packages/react-dom/dist/index.mjs 153 B
packages/react-dom/dist/InView-********.cjs 733 B
packages/react-dom/dist/InView-********.mjs 670 B
packages/react-dom/dist/InView.cjs 96 B
packages/react-dom/dist/InView.mjs 90 B
packages/react-dom/dist/useFadeIn-********.cjs 496 B
packages/react-dom/dist/useFadeIn-********.mjs 432 B
packages/react-dom/dist/useFadeIn.cjs 99 B
packages/react-dom/dist/useFadeIn.mjs 93 B
packages/react-dom/dist/useInView-********.cjs 1.66 kB
packages/react-dom/dist/useInView-********.mjs 1.61 kB
packages/react-dom/dist/useInView.cjs 99 B
packages/react-dom/dist/useInView.mjs 93 B
packages/react-query-4/dist/ClientOnly-********.cjs 394 B
packages/react-query-4/dist/ClientOnly-********.mjs 323 B
packages/react-query-4/dist/createGetQueryClient-********.cjs 1.08 kB
packages/react-query-4/dist/createGetQueryClient-********.mjs 1 kB
packages/react-query-4/dist/createGetQueryClient.cjs 98 B
packages/react-query-4/dist/createGetQueryClient.mjs 91 B
packages/react-query-4/dist/index.cjs 542 B
packages/react-query-4/dist/index.mjs 450 B
packages/react-query-4/dist/infiniteQueryOptions-********.cjs 375 B
packages/react-query-4/dist/infiniteQueryOptions-********.mjs 302 B
packages/react-query-4/dist/infiniteQueryOptions.cjs 98 B
packages/react-query-4/dist/infiniteQueryOptions.mjs 91 B
packages/react-query-4/dist/IsFetching-********.cjs 347 B
packages/react-query-4/dist/IsFetching-********.mjs 266 B
packages/react-query-4/dist/IsFetching.cjs 102 B
packages/react-query-4/dist/IsFetching.mjs 96 B
packages/react-query-4/dist/Mutation-********.cjs 393 B
packages/react-query-4/dist/Mutation-********.mjs 315 B
packages/react-query-4/dist/Mutation.cjs 100 B
packages/react-query-4/dist/Mutation.mjs 94 B
packages/react-query-4/dist/mutationOptions-********.cjs 203 B
packages/react-query-4/dist/mutationOptions-********.mjs 147 B
packages/react-query-4/dist/mutationOptions.cjs 90 B
packages/react-query-4/dist/mutationOptions.mjs 84 B
packages/react-query-4/dist/objectSpread2-********.cjs 799 B
packages/react-query-4/dist/objectSpread2-********.mjs 767 B
packages/react-query-4/dist/objectWithoutProperties-********.cjs 406 B
packages/react-query-4/dist/objectWithoutProperties-********.mjs 366 B
packages/react-query-4/dist/PrefetchInfiniteQuery-********.cjs 476 B
packages/react-query-4/dist/PrefetchInfiniteQuery-********.mjs 407 B
packages/react-query-4/dist/PrefetchInfiniteQuery.cjs 114 B
packages/react-query-4/dist/PrefetchInfiniteQuery.mjs 107 B
packages/react-query-4/dist/PrefetchQuery-********.cjs 463 B
packages/react-query-4/dist/PrefetchQuery-********.mjs 396 B
packages/react-query-4/dist/PrefetchQuery.cjs 105 B
packages/react-query-4/dist/PrefetchQuery.mjs 99 B
packages/react-query-4/dist/QueriesHydration-********.cjs 1.6 kB
packages/react-query-4/dist/QueriesHydration-********.mjs 1.52 kB
packages/react-query-4/dist/QueriesHydration.cjs 93 B
packages/react-query-4/dist/QueriesHydration.mjs 87 B
packages/react-query-4/dist/QueryClientConsumer-********.cjs 356 B
packages/react-query-4/dist/QueryClientConsumer-********.mjs 285 B
packages/react-query-4/dist/QueryClientConsumer.cjs 109 B
packages/react-query-4/dist/QueryClientConsumer.mjs 102 B
packages/react-query-4/dist/queryOptions-********.cjs 366 B
packages/react-query-4/dist/queryOptions-********.mjs 295 B
packages/react-query-4/dist/queryOptions.cjs 89 B
packages/react-query-4/dist/queryOptions.mjs 83 B
packages/react-query-4/dist/SuspenseInfiniteQuery-********.cjs 651 B
packages/react-query-4/dist/SuspenseInfiniteQuery-********.mjs 566 B
packages/react-query-4/dist/SuspenseInfiniteQuery.cjs 114 B
packages/react-query-4/dist/SuspenseInfiniteQuery.mjs 107 B
packages/react-query-4/dist/SuspenseQueries-********.cjs 572 B
packages/react-query-4/dist/SuspenseQueries-********.mjs 487 B
packages/react-query-4/dist/SuspenseQueries.cjs 107 B
packages/react-query-4/dist/SuspenseQueries.mjs 101 B
packages/react-query-4/dist/SuspenseQuery-********.cjs 638 B
packages/react-query-4/dist/SuspenseQuery-********.mjs 553 B
packages/react-query-4/dist/SuspenseQuery.cjs 105 B
packages/react-query-4/dist/SuspenseQuery.mjs 99 B
packages/react-query-4/dist/usePrefetchInfiniteQuery-********.cjs 464 B
packages/react-query-4/dist/usePrefetchInfiniteQuery-********.mjs 400 B
packages/react-query-4/dist/usePrefetchInfiniteQuery.cjs 117 B
packages/react-query-4/dist/usePrefetchInfiniteQuery.mjs 110 B
packages/react-query-4/dist/usePrefetchQuery-********.cjs 455 B
packages/react-query-4/dist/usePrefetchQuery-********.mjs 392 B
packages/react-query-4/dist/usePrefetchQuery.cjs 108 B
packages/react-query-4/dist/usePrefetchQuery.mjs 102 B
packages/react-query-4/dist/useSuspenseInfiniteQuery-********.cjs 380 B
packages/react-query-4/dist/useSuspenseInfiniteQuery-********.mjs 305 B
packages/react-query-4/dist/useSuspenseInfiniteQuery.cjs 117 B
packages/react-query-4/dist/useSuspenseInfiniteQuery.mjs 110 B
packages/react-query-4/dist/useSuspenseQueries-********.cjs 375 B
packages/react-query-4/dist/useSuspenseQueries-********.mjs 300 B
packages/react-query-4/dist/useSuspenseQueries.cjs 111 B
packages/react-query-4/dist/useSuspenseQueries.mjs 104 B
packages/react-query-4/dist/useSuspenseQuery-********.cjs 369 B
packages/react-query-4/dist/useSuspenseQuery-********.mjs 298 B
packages/react-query-4/dist/useSuspenseQuery.cjs 108 B
packages/react-query-4/dist/useSuspenseQuery.mjs 102 B
packages/react-query-5/dist/ClientOnly-********.cjs 394 B
packages/react-query-5/dist/ClientOnly-********.mjs 323 B
packages/react-query-5/dist/createGetQueryClient-********.cjs 1.08 kB
packages/react-query-5/dist/createGetQueryClient-********.mjs 1.01 kB
packages/react-query-5/dist/createGetQueryClient.cjs 98 B
packages/react-query-5/dist/createGetQueryClient.mjs 91 B
packages/react-query-5/dist/index.cjs 537 B
packages/react-query-5/dist/index.mjs 447 B
packages/react-query-5/dist/infiniteQueryOptions-********.cjs 370 B
packages/react-query-5/dist/infiniteQueryOptions-********.mjs 297 B
packages/react-query-5/dist/infiniteQueryOptions.cjs 98 B
packages/react-query-5/dist/infiniteQueryOptions.mjs 91 B
packages/react-query-5/dist/IsFetching-********.cjs 432 B
packages/react-query-5/dist/IsFetching-********.mjs 351 B
packages/react-query-5/dist/IsFetching.cjs 102 B
packages/react-query-5/dist/IsFetching.mjs 96 B
packages/react-query-5/dist/Mutation-********.cjs 393 B
packages/react-query-5/dist/Mutation-********.mjs 318 B
packages/react-query-5/dist/Mutation.cjs 100 B
packages/react-query-5/dist/Mutation.mjs 94 B
packages/react-query-5/dist/mutationOptions-********.cjs 368 B
packages/react-query-5/dist/mutationOptions-********.mjs 296 B
packages/react-query-5/dist/mutationOptions.cjs 90 B
packages/react-query-5/dist/mutationOptions.mjs 84 B
packages/react-query-5/dist/objectSpread2-********.cjs 799 B
packages/react-query-5/dist/objectSpread2-********.mjs 767 B
packages/react-query-5/dist/objectWithoutProperties-********.cjs 406 B
packages/react-query-5/dist/objectWithoutProperties-********.mjs 366 B
packages/react-query-5/dist/PrefetchInfiniteQuery-********.cjs 469 B
packages/react-query-5/dist/PrefetchInfiniteQuery-********.mjs 396 B
packages/react-query-5/dist/PrefetchInfiniteQuery.cjs 114 B
packages/react-query-5/dist/PrefetchInfiniteQuery.mjs 107 B
packages/react-query-5/dist/PrefetchQuery-********.cjs 462 B
packages/react-query-5/dist/PrefetchQuery-********.mjs 390 B
packages/react-query-5/dist/PrefetchQuery.cjs 105 B
packages/react-query-5/dist/PrefetchQuery.mjs 99 B
packages/react-query-5/dist/QueriesHydration-********.cjs 1.61 kB
packages/react-query-5/dist/QueriesHydration-********.mjs 1.53 kB
packages/react-query-5/dist/QueriesHydration.cjs 93 B
packages/react-query-5/dist/QueriesHydration.mjs 87 B
packages/react-query-5/dist/QueryClientConsumer-********.cjs 358 B
packages/react-query-5/dist/QueryClientConsumer-********.mjs 281 B
packages/react-query-5/dist/QueryClientConsumer.cjs 109 B
packages/react-query-5/dist/QueryClientConsumer.mjs 102 B
packages/react-query-5/dist/queryOptions-********.cjs 361 B
packages/react-query-5/dist/queryOptions-********.mjs 290 B
packages/react-query-5/dist/queryOptions.cjs 89 B
packages/react-query-5/dist/queryOptions.mjs 83 B
packages/react-query-5/dist/SuspenseInfiniteQuery-********.cjs 654 B
packages/react-query-5/dist/SuspenseInfiniteQuery-********.mjs 566 B
packages/react-query-5/dist/SuspenseInfiniteQuery.cjs 114 B
packages/react-query-5/dist/SuspenseInfiniteQuery.mjs 107 B
packages/react-query-5/dist/SuspenseQueries-********.cjs 588 B
packages/react-query-5/dist/SuspenseQueries-********.mjs 505 B
packages/react-query-5/dist/SuspenseQueries.cjs 107 B
packages/react-query-5/dist/SuspenseQueries.mjs 101 B
packages/react-query-5/dist/SuspenseQuery-********.cjs 630 B
packages/react-query-5/dist/SuspenseQuery-********.mjs 543 B
packages/react-query-5/dist/SuspenseQuery.cjs 105 B
packages/react-query-5/dist/SuspenseQuery.mjs 99 B
packages/react-query-5/dist/usePrefetchInfiniteQuery-********.cjs 373 B
packages/react-query-5/dist/usePrefetchInfiniteQuery-********.mjs 301 B
packages/react-query-5/dist/usePrefetchInfiniteQuery.cjs 117 B
packages/react-query-5/dist/usePrefetchInfiniteQuery.mjs 110 B
packages/react-query-5/dist/usePrefetchQuery-********.cjs 369 B
packages/react-query-5/dist/usePrefetchQuery-********.mjs 297 B
packages/react-query-5/dist/usePrefetchQuery.cjs 108 B
packages/react-query-5/dist/usePrefetchQuery.mjs 102 B
packages/react-query-5/dist/useSuspenseInfiniteQuery-********.cjs 374 B
packages/react-query-5/dist/useSuspenseInfiniteQuery-********.mjs 299 B
packages/react-query-5/dist/useSuspenseInfiniteQuery.cjs 117 B
packages/react-query-5/dist/useSuspenseInfiniteQuery.mjs 110 B
packages/react-query-5/dist/useSuspenseQueries-********.cjs 369 B
packages/react-query-5/dist/useSuspenseQueries-********.mjs 294 B
packages/react-query-5/dist/useSuspenseQueries.cjs 111 B
packages/react-query-5/dist/useSuspenseQueries.mjs 104 B
packages/react-query-5/dist/useSuspenseQuery-********.cjs 363 B
packages/react-query-5/dist/useSuspenseQuery-********.mjs 292 B
packages/react-query-5/dist/useSuspenseQuery.cjs 108 B
packages/react-query-5/dist/useSuspenseQuery.mjs 102 B
packages/react-query/dist/index.cjs 351 B
packages/react-query/dist/index.mjs 201 B
packages/react-query/dist/v4.cjs 351 B
packages/react-query/dist/v4.mjs 201 B
packages/react-query/dist/v5.cjs 351 B
packages/react-query/dist/v5.mjs 201 B
packages/react/dist/ClientOnly-********.cjs 609 B
packages/react/dist/ClientOnly-********.mjs 536 B
packages/react/dist/ClientOnly.cjs 97 B
packages/react/dist/ClientOnly.mjs 91 B
packages/react/dist/DefaultProps-********.cjs 996 B
packages/react/dist/DefaultProps-********.mjs 932 B
packages/react/dist/DefaultProps.cjs 118 B
packages/react/dist/DefaultProps.mjs 114 B
packages/react/dist/DefaultPropsContexts-********.cjs 328 B
packages/react/dist/DefaultPropsContexts-********.mjs 258 B
packages/react/dist/Delay-********.cjs 1.09 kB
packages/react/dist/Delay-********.mjs 1.03 kB
packages/react/dist/Delay.cjs 94 B
packages/react/dist/Delay.mjs 88 B
packages/react/dist/ErrorBoundary-********.cjs 2.15 kB
packages/react/dist/ErrorBoundary-********.mjs 2.1 kB
packages/react/dist/ErrorBoundary.cjs 134 B
packages/react/dist/ErrorBoundary.mjs 132 B
packages/react/dist/ErrorBoundaryGroup-********.cjs 1.19 kB
packages/react/dist/ErrorBoundaryGroup-********.mjs 1.12 kB
packages/react/dist/ErrorBoundaryGroup.cjs 133 B
packages/react/dist/ErrorBoundaryGroup.mjs 132 B
packages/react/dist/index.cjs 365 B
packages/react/dist/index.mjs 329 B
packages/react/dist/lazy-********.cjs 2.06 kB
packages/react/dist/lazy-********.mjs 1.99 kB
packages/react/dist/lazy.cjs 127 B
packages/react/dist/lazy.mjs 127 B
packages/react/dist/noop-********.cjs 203 B
packages/react/dist/noop-********.mjs 144 B
packages/react/dist/objectSpread2-********.cjs 805 B
packages/react/dist/objectSpread2-********.mjs 773 B
packages/react/dist/objectWithoutProperties-********.cjs 413 B
packages/react/dist/objectWithoutProperties-********.mjs 372 B
packages/react/dist/Suspense-********.cjs 909 B
packages/react/dist/Suspense-********.mjs 838 B
packages/react/dist/Suspense.cjs 98 B
packages/react/dist/Suspense.mjs 92 B
packages/react/dist/SuspensiveError-********.cjs 522 B
packages/react/dist/SuspensiveError-********.mjs 441 B
packages/react/dist/useIsClient-********.cjs 318 B
packages/react/dist/useIsClient-********.mjs 251 B
packages/react/dist/useIsClient.cjs 98 B
packages/react/dist/useIsClient.mjs 92 B

compressed-size-action

Comment thread docs/suspensive.org/scripts/llms-txt/document-processor.ts Fixed
Comment thread docs/suspensive.org/.gitignore Outdated
Copy link
Copy Markdown
Member

@manudeli manudeli left a comment

Choose a reason for hiding this comment

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

We have node v24 so we don't need tsx

Comment thread docs/suspensive.org/package.json Outdated
"type": "module",
"scripts": {
"build": "next build && pagefind --site .next/server/app --output-path public/_pagefind",
"build": "next build && tsx scripts/llms-txt/generate-llms-txt.ts && pagefind --site .next/server/app --output-path public/_pagefind",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
"build": "next build && tsx scripts/llms-txt/generate-llms-txt.ts && pagefind --site .next/server/app --output-path public/_pagefind",
"build": "next build && node scripts/llms-txt/generate-llms-txt.ts && pagefind --site .next/server/app --output-path public/_pagefind",

Comment thread docs/suspensive.org/package.json Outdated
Comment on lines +52 to +53
"tailwindcss": "catalog:",
"tsx": "^4.21.0"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
"tailwindcss": "catalog:",
"tsx": "^4.21.0"
"tailwindcss": "catalog:"

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds LLMs.txt support to generate AI-friendly documentation from Nextra-based docs. The implementation parses MDX files, transforms Nextra components to markdown, and generates three types of output files: an overview file with links, a complete documentation file, and individual markdown files.

Key changes:

  • Script suite to parse Nextra _meta.tsx files, transform MDX/Nextra components, and generate LLMs.txt files
  • Middleware bypass for .txt and .md files to skip i18n processing
  • Integration into the build process

Reviewed changes

Copilot reviewed 10 out of 11 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
docs/suspensive.org/src/middleware.ts Adds bypass logic for .md and .txt files to skip i18n middleware
docs/suspensive.org/scripts/llms-txt/types.ts Defines TypeScript interfaces for metadata entries and document information
docs/suspensive.org/scripts/llms-txt/nextra-transform.ts Transforms Nextra/MDX components (Tabs, Callout, Sandpack) to standard markdown
docs/suspensive.org/scripts/llms-txt/meta-parser.ts Parses Nextra _meta.tsx files to extract sidebar ordering and titles
docs/suspensive.org/scripts/llms-txt/document-processor.ts Processes MDX files to extract titles, descriptions, and clean content
docs/suspensive.org/scripts/llms-txt/config.ts Configuration constants for directory paths
docs/suspensive.org/scripts/llms-txt/builders.ts Builds the LLMs.txt output formats from processed documents
docs/suspensive.org/scripts/llms-txt/generate-llms-txt.ts Main script that orchestrates the generation process
docs/suspensive.org/package.json Integrates LLMs.txt generation into build script and adds tsx dependency
docs/suspensive.org/.gitignore Ignores generated LLMs.txt files
pnpm-lock.yaml Lock file updates for tsx dependency and libc fields for Linux packages
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread docs/suspensive.org/package.json Outdated
"type": "module",
"scripts": {
"build": "next build && pagefind --site .next/server/app --output-path public/_pagefind",
"build": "next build && tsx scripts/llms-txt/generate-llms-txt.ts && pagefind --site .next/server/app --output-path public/_pagefind",
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

The build script chains commands with && which means if the LLMs.txt generation fails, the pagefind step won't run. However, the build output (next build) will already exist. Consider whether LLMs.txt generation should be a critical step that blocks the entire build, or if it should run independently with proper error handling.

Suggested change
"build": "next build && tsx scripts/llms-txt/generate-llms-txt.ts && pagefind --site .next/server/app --output-path public/_pagefind",
"build": "next build && (tsx scripts/llms-txt/generate-llms-txt.ts || echo \"Warning: failed to generate LLMs.txt\" >&2) && pagefind --site .next/server/app --output-path public/_pagefind",

Copilot uses AI. Check for mistakes.
Comment on lines +70 to +71
return {
category: categoryMatch?.[1] ?? ROOT_CATEGORY,
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

The regex pattern /docs\/([^/]+)\// is fragile as it assumes a specific directory structure. If the path doesn't match this pattern (e.g., for root-level docs or if the path format changes), the category will always default to ROOT_CATEGORY without warning. Consider adding validation or logging when the pattern doesn't match to help debug potential issues.

Suggested change
return {
category: categoryMatch?.[1] ?? ROOT_CATEGORY,
const category = categoryMatch?.[1] ?? ROOT_CATEGORY
if (!categoryMatch) {
console.warn(
`[document-processor] Unable to extract category from path "${filePath}" using pattern /docs\\/([^/]+)\\//; defaulting to "${category}".`
)
}
return {
category,

Copilot uses AI. Check for mistakes.
Comment on lines +30 to +50
function findSentenceEnd(text: string): number {
let bracketDepth = 0
let inCode = false

for (let i = 0; i < text.length; i++) {
const char = text[i]

if (char === '`') {
inCode = !inCode
continue
}

if (inCode) continue

if (char === '[') bracketDepth++
else if (char === ']') bracketDepth = Math.max(0, bracketDepth - 1)
else if (bracketDepth === 0 && SENTENCE_ENDINGS.has(char)) return i
}

return -1
}
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

The findSentenceEnd function doesn't handle consecutive backticks correctly. If the text contains an even number of backticks (like "test `` test."), the inCode flag will be false at the period, potentially causing incorrect sentence detection. Consider tracking backtick pairs more carefully or using a more robust parser.

Copilot uses AI. Check for mistakes.
Comment on lines +77 to +87
const rawContent = fs.readFileSync(filePath, 'utf-8')
const { category, slug } = parseFilePath(filePath)
const relativePath = filePath.replace(DOCS_DIR, '/docs').replace(/\.mdx$/, '')

return {
title: extractTitle(rawContent, slug),
description: extractFirstSentence(rawContent),
content: cleanContent(rawContent),
path: relativePath,
category,
slug,
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

Missing error handling when reading files or parsing content. If a file is corrupted, unreadable, or contains malformed content, the script will crash without a helpful error message. Consider wrapping file operations and parsing logic in try-catch blocks with descriptive error messages that include the file path being processed.

Suggested change
const rawContent = fs.readFileSync(filePath, 'utf-8')
const { category, slug } = parseFilePath(filePath)
const relativePath = filePath.replace(DOCS_DIR, '/docs').replace(/\.mdx$/, '')
return {
title: extractTitle(rawContent, slug),
description: extractFirstSentence(rawContent),
content: cleanContent(rawContent),
path: relativePath,
category,
slug,
try {
const rawContent = fs.readFileSync(filePath, 'utf-8')
const { category, slug } = parseFilePath(filePath)
const relativePath = filePath.replace(DOCS_DIR, '/docs').replace(/\.mdx$/, '')
return {
title: extractTitle(rawContent, slug),
description: extractFirstSentence(rawContent),
content: cleanContent(rawContent),
path: relativePath,
category,
slug,
}
} catch (error) {
const message =
error instanceof Error ? error.message : String(error)
throw new Error(
`Failed to process document "${filePath}": ${message}`
)

Copilot uses AI. Check for mistakes.
preserved.push(match)
return `__PRESERVED_${preserved.length - 1}__`
})
.replace(/`[^`]+`/g, (match) => {
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

The regex pattern for inline code may incorrectly match escaped backticks. Consider using a more robust pattern that handles escaped backticks: /(?<!\\)(?:[^`\\]|\\.)+`(?!\\)/g`. The current pattern `/`[^`]+/g will fail to preserve inline code that contains escaped backticks or match unintended sequences when backticks appear in other contexts.

Suggested change
.replace(/`[^`]+`/g, (match) => {
.replace(/(?<!\\)`(?:[^`\\]|\\.)+`(?!\\)/g, (match) => {

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,3 @@
export const DOCS_DIR = 'src/content/en/docs'
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

The hardcoded path 'src/content/en/docs' may break if the directory structure changes or if the script is run from a different working directory. Consider making this configurable through environment variables or command-line arguments, or use path.join with __dirname to make it relative to the script location.

Suggested change
export const DOCS_DIR = 'src/content/en/docs'
import * as path from 'path'
export const DOCS_DIR =
process.env.DOCS_DIR ?? path.resolve(__dirname, '../../../src/content/en/docs')

Copilot uses AI. Check for mistakes.
Comment on lines +30 to +31
const entryPattern =
/['"]?([^'":,\s]+)['"]?\s*:\s*\{[^}]*?title:\s*['"]([^'"]+)['"][^}]*?\}/g
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

The regex pattern /['"]?([^'":,\s]+)['"]?\s*:\s*\{[^}]*?title:\s*['"]([^'"]+)['"][^}]*?\}/g is complex and may fail to match entries if the _meta.tsx format varies slightly (e.g., with nested objects, multi-line formatting, or titles containing quotes). Consider using a proper TypeScript/JavaScript parser (like @babel/parser) to reliably extract the metadata structure instead of relying on regex.

Copilot uses AI. Check for mistakes.
trimmed.startsWith('>') ||
trimmed.startsWith('- ') ||
trimmed.startsWith('* ') ||
/^[\w]+=/.test(trimmed) || // JSX attributes like title="..."
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

The regex pattern /^[\w]+=/.test(trimmed) for detecting JSX attributes is too broad and may incorrectly skip valid content lines. For example, a line like "key=value" in regular text would be skipped. Consider making this more specific to JSX attribute patterns, such as checking for common JSX attribute patterns like className=, style=, or using a more precise pattern that accounts for JSX context.

Suggested change
/^[\w]+=/.test(trimmed) || // JSX attributes like title="..."
/^[A-Za-z_][\w:-]*\s*=\s*["'{]/.test(trimmed) || // JSX-like attributes, e.g. title="...", className={...}

Copilot uses AI. Check for mistakes.
result = result.replace(
/<Tabs\s+items=\{(\[[^\]]+\])\}>([\s\S]*?)<\/Tabs>/g,
(_match, itemsStr: string, tabsContent: string) => {
const items = JSON.parse(itemsStr.replace(/'/g, '"')) as string[]
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

The JSON.parse call on line 22 lacks error handling and assumes the items array only contains single-quoted strings. The replacement logic itemsStr.replace(/'/g, '"') could produce invalid JSON if the actual items use double quotes, contain apostrophes in the strings, or have mixed quote styles. Wrap this in a try-catch block with a fallback value, and consider a more robust parsing approach that handles various quote styles.

Suggested change
const items = JSON.parse(itemsStr.replace(/'/g, '"')) as string[]
let items: string[] = []
try {
// First, try parsing as-is in case itemsStr is already valid JSON
items = JSON.parse(itemsStr) as string[]
} catch {
try {
// Fallback: attempt a simple single-quote to double-quote conversion
items = JSON.parse(itemsStr.replace(/'/g, '"')) as string[]
} catch {
// On failure, keep items as an empty array and use default tab labels
items = []
}
}

Copilot uses AI. Check for mistakes.
"esModuleInterop": true,
"isolatedModules": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Added .ts extensions to all relative imports to enable native Node.js TypeScript execution without tsx. This requires allowImportingTsExtensions: true in tsconfig.json (which works because noEmit is already set).

@manudeli
Copy link
Copy Markdown
Member

manudeli commented Jan 7, 2026

Thanks! ❤️

@manudeli manudeli merged commit e41e0d1 into main Jan 7, 2026
16 checks passed
@manudeli manudeli deleted the feat/llms-txt branch January 7, 2026 06:19
@tooooo1
Copy link
Copy Markdown
Contributor

tooooo1 commented Jan 8, 2026

cool

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.

[Feature]: llms.txt auto generated in suspensive.org

6 participants