Skip to content

feat: Add llms-full.txt#364

Open
elnelson575 wants to merge 41 commits into
mainfrom
elnelson575/add-llms-txt
Open

feat: Add llms-full.txt#364
elnelson575 wants to merge 41 commits into
mainfrom
elnelson575/add-llms-txt

Conversation

@elnelson575

@elnelson575 elnelson575 commented Mar 27, 2026

Copy link
Copy Markdown
Contributor

Closes #363

Auto-generated llms-full.txt following the llmstxt.org spec, so LLMs can discover and consume Shiny for Python documentation.

Key decisions

  • llms-full.txt (572 KB full content) — includes cleaned page content for RAG/coding assistant use, matching the pattern used by Pydantic and others
  • Content scope: Get Started, Concepts, Components (with examples), Layouts, API Reference, Templates. Gallery excluded (most of these are links to other repos or content made by others)
  • Deterministic generation — output is a pure function of source files; only changed pages produce diffs
  • CI freshness check — build fails with instructions if files are out of date
  • _quarto.yml is the source of truth for site structure and page ordering; templates discovered by directory scan (not in sidebars)
  • QMD cleaning strips frontmatter, Quarto directives, raw HTML, shinylive metadata; preserves prose and code blocks
  • Page descriptions use title only (86/119 pages lack descriptions in frontmatter)
  • @dataclass for site structure types — auto-generates __init__, __repr__, and structural __eq__; the last is essential for test assertions like assert entries == [SidebarEntry(...)]. NamedTuple was the alternative but feels semantically odd for types with mutable list fields.

Usage

make llms-full-txt          # generate both files
make all               # includes llms-txt in full build

Test plan


Implementation plan

File Structure

File Action Responsibility
scripts/generate_llms_txt.py Create Main generation script — all logic lives here
tests/test_generate_llms_txt.py Create Unit tests for parsing and cleaning functions
Makefile Modify Add target, update all
_quarto.yml Modify Addllms-full.txt to resources (llms.txt is generated live from quarto from previous PR
.github/workflows/deploy-docs.yml Modify Add CI freshness check
llms-full.txt Create (generated) Full content file

E Nelson and others added 19 commits March 26, 2026 19:23
Design for issue #363. Covers file formats, generation script,
QMD content cleaning, build integration, and CI verification.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Layouts: use top-level pages only, skip anchor links
- Templates: add directory scanning special case
- Section name cleaning: strip Quarto markup, HTML, image syntax
- API docs: include all individual function pages (matches Pydantic pattern)
- _quarto.yml parsing: document four entry formats and recursive walker

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
9 tasks covering: QMD cleaning, section name parsing, sidebar walking,
URL generation, site structure builder, output generators, main entry
point, build integration (Makefile + CI), and end-to-end verification.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements clean_qmd_content() in scripts/generate_llms_txt.py that
strips Quarto-specific markup (YAML frontmatter, div fences, raw HTML
blocks, Shinylive directives) from .qmd files, leaving clean prose and
standard markdown code blocks. Includes 12 passing unit tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous assertion was always true due to substring matching
with trailing newline differences. Now directly checks no triple
newlines remain and verifies the expected output format.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…er, and URL generation helpers

Implements Tasks 2, 3, and 4 for the llms.txt generator:
- clean_section_name(): strips Quarto/HTML/bold markup from sidebar section names
- extract_title(): extracts title/pagetitle from .qmd YAML frontmatter (regex fallback when PyYAML unavailable)
- SidebarEntry dataclass + walk_sidebar(): recursively walks _quarto.yml sidebar contents
- file_path_to_url(): converts file paths to site URLs with proper index handling

All 31 tests pass (12 existing + 19 new).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds Page, Subsection, Section dataclasses plus build_site_structure(),
_load_page(), _build_templates_section(), and _build_api_sections() to
scripts/generate_llms_txt.py. Adds three new tests covering section
discovery, template scanning, and anchor-link skipping (34 tests total,
all passing).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…files

Implements Tasks 6 and 7:
- Add BASE_URL and SITE_DESCRIPTION constants
- Add _write_header(), generate_llms_txt(), generate_llms_full_txt() output writers
- Add main() entry point that generates llms.txt and llms-full.txt from _quarto.yml
- Fix _load_page() to skip directory paths (is_file() guard)
- Add 4 new tests for output writers (38 total, all passing)
- Generate llms.txt (8,553 bytes) and llms-full.txt (573,877 bytes) from live site

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Makefile: new llms-txt target (depends on quartodoc), added to all
- _quarto.yml: llms.txt and llms-full.txt as project resources
- CI: verify llms.txt is up to date after build, fail with instructions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The previous regex only matched ::: with {.class} syntax but missed
::: callout-note style fences (no braces). Now matches any line
starting with 3+ colons.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
These were working documents used during design and implementation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The layouts sidebar uses anchor hrefs (e.g. /layouts/navbars/index.html#section)
for all sub-pages, which caused walk_sidebar to skip them entirely. Fix resolves
fragment hrefs to their parent page path and deduplicates, so Navbars, Sidebars,
Tabs, Panels & Cards, and Arrange Elements now appear in both llms.txt and
llms-full.txt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- _load_page now resolves trailing-slash hrefs (e.g. components/) to
  index.qmd, fixing the Components index page being silently dropped
- Rename test_walk_skips_anchor_links to test_walk_resolves_fragment_hrefs
  and correct its assertion to match actual behavior (resolves to parent
  page rather than skipping)
- Update integration test to verify deduplication of multiple fragment
  hrefs pointing to the same parent page
- Regenerate llms.txt and llms-full.txt (113 pages, +1 Components index)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Drop base_url parameter from file_path_to_url; use BASE_URL directly
- Extract API_SUBSECTIONS constant (consistent with SIDEBAR_DISPLAY_NAMES)
- Remove seen_subsections list; dict preserves insertion order in Python 3.7+
- Inline _write_header into HEADER constant
- Simplify pages_list loop to walrus comprehension in _build_api_sections
- Remove narrating inline comments

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Picks up new toolbar/card components, updated layout section names,
and updates py-shiny submodule to latest.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Picks up new OpenTelemetry docs from main.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
github-actions Bot temporarily deployed to pr-364 April 2, 2026 19:40 Destroyed
@github-actions
github-actions Bot temporarily deployed to pr-364 April 2, 2026 20:47 Destroyed
@elnelson575
elnelson575 requested a review from cpsievert April 2, 2026 20:54
@elnelson575
elnelson575 marked this pull request as ready for review April 2, 2026 20:54
@elnelson575
elnelson575 requested a review from gadenbuie April 2, 2026 21:05
E Nelson and others added 5 commits April 15, 2026 17:46
Design for issue #363. Covers file formats, generation script,
QMD content cleaning, build integration, and CI verification.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Layouts: use top-level pages only, skip anchor links
- Templates: add directory scanning special case
- Section name cleaning: strip Quarto markup, HTML, image syntax
- API docs: include all individual function pages (matches Pydantic pattern)
- _quarto.yml parsing: document four entry formats and recursive walker

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
9 tasks covering: QMD cleaning, section name parsing, sidebar walking,
URL generation, site structure builder, output generators, main entry
point, build integration (Makefile + CI), and end-to-end verification.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements clean_qmd_content() in scripts/generate_llms_txt.py that
strips Quarto-specific markup (YAML frontmatter, div fences, raw HTML
blocks, Shinylive directives) from .qmd files, leaving clean prose and
standard markdown code blocks. Includes 12 passing unit tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous assertion was always true due to substring matching
with trailing newline differences. Now directly checks no triple
newlines remain and verifies the expected output format.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
E Nelson and others added 15 commits April 15, 2026 17:46
…er, and URL generation helpers

Implements Tasks 2, 3, and 4 for the llms.txt generator:
- clean_section_name(): strips Quarto/HTML/bold markup from sidebar section names
- extract_title(): extracts title/pagetitle from .qmd YAML frontmatter (regex fallback when PyYAML unavailable)
- SidebarEntry dataclass + walk_sidebar(): recursively walks _quarto.yml sidebar contents
- file_path_to_url(): converts file paths to site URLs with proper index handling

All 31 tests pass (12 existing + 19 new).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds Page, Subsection, Section dataclasses plus build_site_structure(),
_load_page(), _build_templates_section(), and _build_api_sections() to
scripts/generate_llms_txt.py. Adds three new tests covering section
discovery, template scanning, and anchor-link skipping (34 tests total,
all passing).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…files

Implements Tasks 6 and 7:
- Add BASE_URL and SITE_DESCRIPTION constants
- Add _write_header(), generate_llms_txt(), generate_llms_full_txt() output writers
- Add main() entry point that generates llms.txt and llms-full.txt from _quarto.yml
- Fix _load_page() to skip directory paths (is_file() guard)
- Add 4 new tests for output writers (38 total, all passing)
- Generate llms.txt (8,553 bytes) and llms-full.txt (573,877 bytes) from live site

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Makefile: new llms-txt target (depends on quartodoc), added to all
- _quarto.yml: llms.txt and llms-full.txt as project resources
- CI: verify llms.txt is up to date after build, fail with instructions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The previous regex only matched ::: with {.class} syntax but missed
::: callout-note style fences (no braces). Now matches any line
starting with 3+ colons.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
These were working documents used during design and implementation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The layouts sidebar uses anchor hrefs (e.g. /layouts/navbars/index.html#section)
for all sub-pages, which caused walk_sidebar to skip them entirely. Fix resolves
fragment hrefs to their parent page path and deduplicates, so Navbars, Sidebars,
Tabs, Panels & Cards, and Arrange Elements now appear in both llms.txt and
llms-full.txt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- _load_page now resolves trailing-slash hrefs (e.g. components/) to
  index.qmd, fixing the Components index page being silently dropped
- Rename test_walk_skips_anchor_links to test_walk_resolves_fragment_hrefs
  and correct its assertion to match actual behavior (resolves to parent
  page rather than skipping)
- Update integration test to verify deduplication of multiple fragment
  hrefs pointing to the same parent page
- Regenerate llms.txt and llms-full.txt (113 pages, +1 Components index)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Drop base_url parameter from file_path_to_url; use BASE_URL directly
- Extract API_SUBSECTIONS constant (consistent with SIDEBAR_DISPLAY_NAMES)
- Remove seen_subsections list; dict preserves insertion order in Python 3.7+
- Inline _write_header into HEADER constant
- Simplify pages_list loop to walrus comprehension in _build_api_sections
- Remove narrating inline comments

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Picks up new toolbar/card components, updated layout section names,
and updates py-shiny submodule to latest.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Picks up new OpenTelemetry docs from main.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nsumption

- Remove llms.txt generation (now handled natively by Quarto)
- Rename make target to llms-full-txt and update CI to match
- Remove /llms.txt from _quarto.yml resources

Content quality improvements to generate_llms_txt.py:
- Preserve <placeholder> syntax in prose (e.g. <name>, <checkbox_id>)
- Resolve all relative/.qmd links to absolute URLs
- Strip authoring template headings (Details, Variations)
- Detect nav hub pages by pagedescription: frontmatter; replace body
  with description only (fixes layouts/components index nav chrome)
- Truncate input component pages to first paragraph + See also links
- Strip multi-line HTML comments
- Buffer code blocks; drop hidden blocks (echo/include: false)
- Convert space-variant shinylive fences (``` {shinylive-python})
- Strip Show code/Hide code fold labels
- Add 33 new tests covering all improvements

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions
github-actions Bot temporarily deployed to pr-364 April 17, 2026 16:38 Destroyed
@elnelson575 elnelson575 changed the title feat: Add llms.txt and llms-full.txt feat: Add llms-full.txt Apr 17, 2026
@github-actions
github-actions Bot temporarily deployed to pr-364 April 17, 2026 17:21 Destroyed
Comment on lines +58 to +66
- name: Verify llms-full.txt is up to date
run: |
make llms-full-txt QUARTO_PATH=quarto
if ! git diff --exit-code llms-full.txt; then
echo ""
echo "ERROR: llms-full.txt is out of date."
echo "Run 'make llms-full-txt' locally and commit the updated file."
exit 1
fi

@cpsievert cpsievert Apr 17, 2026

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.

I think it probably makes sense for this to come earlier in the workflow. In particular, make all takes a long time -- It would be frustrating to wait for the site to be built only for it to err.

@@ -0,0 +1,613 @@
import sys

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.

It'd be helpful to have a docstring at the top of the file here to explain the intention behind the tests, when to run them, etc.

Comment thread Makefile
## Generate llms-full.txt
.PHONY: llms-full-txt
llms-full-txt: $(PYBIN) quartodoc
. $(PYBIN)/activate && python scripts/generate_llms_txt.py

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.

Do we not also want to run the tests after generation?

if ! git diff --exit-code llms-full.txt; then
echo ""
echo "ERROR: llms-full.txt is out of date."
echo "Run 'make llms-full-txt' locally and commit the updated file."

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.

To me, llms-full.txt seems like more of a build artifact than a source artifact. I'd personally prefer that it be built at render time and not checked into source.

It's not that I don't see the utility in tracking changes. If we were using a gh-pages branch, we'd have a copy of changes, which is nice.

But I have a strong aversion to having CI fail for things that could be automated. I have come to accept that we do this for build assets for reasons but it's not a pattern I'd recommend we follow for this file in particular.

Comment thread llms-full.txt
Comment on lines +16 to +27
#### Action Button

An action button appears as a button and has a value that increments each time the user presses the button.

See also: [Action Link](https://shiny.posit.co/py/components/inputs/action-link/)

---
#### Action Link

An action link appears as a link in your app and has a value that increments each time the user presses the link.

See also: [Action Button](https://shiny.posit.co/py/components/inputs/action-button/)

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.

This whole section surprised me a bit. I would have expected a section like this to include code snippets showing the actual code to create the inputs.

Also the links seem reasonable, but they point to non-LLM-oriented pages. If we're making this whole system of documentation easier to navigate for LLMs, I'd think we'd prefer to link to LLM-ready pages.

I'm not saying that we have to have an LLM-ready markdown file for py/components/inputs/action-button/, but given how little other information is included in this section, we're basically telling the model to go read that file. So there's an asymmetry here.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>


def clean_qmd_content(content: str, file_path: str = "") -> str:
"""Strip Quarto-specific markup from .qmd content, keeping prose and code."""

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.

Did you look into using pandoc to handle this conversion? Quarto's markdown is really just pandoc markdown, and pandoc can handle moving between markdown variants.

It's more work, but I think much of this could be done through a Lua filter if the goal is to suppress Quarto-specific elements. Just in general, that stack is much better set up to handle parsing and rewriting markdown.

Frankly, I'm confident this function works right now but I wouldn't want to maintain it at all. Claude changes the calculus and all, but my larger worry than this particular function is that this code blends low-importance mechanical transformations with high-level assumptions about how we've laid out our docs, etc.

Relatedly, did you look into using something like markdownify or markdownify-rs? They work on HTML, but if this code moved away from dealing with the low-level details of parsing our source content I think it'd be more maintainable for us in the long run (not to mention more repeatable if we do something like this in other repos).


At one more level of abstraction higher, if we feel that this overall process is important I think it's worth looking into how to do this inside of Quarto. This kind of extension would be useful anywhere else in the ecosystem, like in quartodoc or Great Docs, and it'd probably be a lot easier to write and maintain if it were closer to the Quarto toolchain (i.e. pandoc and Lua filters).

OTOH, if we're just trying to get this done it seems like using something else that handles the HTML parsing for us would be beneficial even if the output isn't as tight as we'd like.

…re build

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.

feat: Add llms.txt

3 participants