Repository navigation
fix: keep llms.txt generation behind one shared guard - #410
Merged
has2k1 merged 3 commits intoOct 23, 2026
Merged
Conversation
posit-dev#350 reported pages advertising llms.txt and llms-full.txt for projects that never generate them. posit-dev#351 gated the homepage margin, the SKILL.md resources and the build log on `_llms_txt_available()`, but the checks that decide whether the files exist still drifted apart: - `_generate_llms_full_txt` only required a `package`, so an `api-reference` block without sections still wrote a header-only `llms-full.txt`. - `_prepare_for_freeze` called both generators directly, bypassing the predicate `build()` gates on, so the freeze path could leave files behind that a full build never writes. `_llms_txt_quarto_config()` is now the single source of truth for the structural half of that guard, shared by the predicate and both generators, and `_prepare_for_freeze` honours the same predicate as `build()` so the build directory really does end up in the state a full build produces.
tyza66
force-pushed
the
fix/llms-txt-links-only-when-generated
branch
from
October 22, 2026 18:06
0b886a1 to
d4f580b
Compare
The shared guard only lets the llms.txt generators run when the API reference configuration names a package and lists at least one section, so the empty fallbacks they still carried for both settings could never apply.
Preparing a build directory for freezing still generated the API reference with quartodoc, which great-docs no longer depends on. The import failed silently, so the freeze path never wrote reference pages even though it promises to leave the build directory as a full build would. It now generates them with the same renderer as a full build, and only when the project has an API reference.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
#350 points out that every page advertises
llms.txtandllms-full.txt, butthose files are only generated for projects that have an API reference. #351
gated the places that advertise the files on
_llms_txt_available(), yet thecode that decides whether the files are actually written still had three
separate, slightly different opinions about when to run. This PR removes that
drift, so a page can never advertise a file the build will not write and a file
can never appear on disk without being advertised.
Two holes are closed:
_generate_llms_full_txt()only required anapi-referenceblock with apackage, so a project with an emptysectionslist still got aheader-only
llms-full.txtwritten while_llms_txt_available()reportedFalse. This is exactly the docs-only-site case from Every page advertises llms.txt and llms-full.txt, but they are only generated for projects with an API reference — docs-only sites ship two dead links #350, where a pagelinks a file that is effectively empty.
_prepare_for_freeze()called_generate_llms_txt()and_generate_llms_full_txt()directly, bypassing the predicatebuild()gateson. The freeze path could therefore leave files behind that a full
build()never writes, which breaks the invariant
_prepare_for_freezedocuments:that the build directory ends up holding files in exactly the state a full
build()would produce.Changes:
_llms_txt_quarto_config()is the single source of truth for thestructural half of the guard:
_quarto.ymlexists, parses to a mapping, andits
api-referenceblock has apackageand at least one section. Itreturns the parsed config, or
Nonewhen the project is not eligible._llms_txt_available()delegates to that helper and keeps the importabilitycheck that only it needs.
_generate_llms_txt()and_generate_llms_full_txt()use the same helperinstead of re-parsing
_quarto.ymlunder their own, narrower conditions._prepare_for_freeze()now wraps both generator calls inif self._llms_txt_available():, so the freeze path and a full build agree._quarto.ymlshapes (no file, noapi-reference, nosections, no package, unimportable package, importable package with
sections) and assert that
_generate_llms_full_txt()and_prepare_for_freeze()agree with_llms_txt_available()in every case.This extends the existing
test_llms_txt_available_matches_generatorsfromthe predicate alone to the generators and the freeze path.
One deliberate scope note:
_generate_llms_txt()is not given animportability check. Existing tests such as
test_generate_llms_txt_writes_fileand
test_generate_llms_txt_b9pin the behaviour that it writesllms.txtfora package that cannot be imported, and after this change both production call
sites (
build()Step 3 and_prepare_for_freeze()) are gated by the predicateanyway, so the asymmetry is no longer reachable in a real build. I would rather
flag it here than churn those tests in the same PR; happy to follow up if you
would prefer the guard moved into the generator itself.
Related GitHub Issues and PRs
Checklist
Verification
pytest tests/test_great_docs.py -k "llms or margin or freeze"passes with 94tests. The full suite was run on Windows before and after the change: 7886
passed / 112 failed before, 7898 passed / 112 failed after, which is exactly the
12 new parametrized tests and no new failures. The 112 failures are pre-existing
and environment-specific (PTY recorder, layout migration, lint, skill install,
page status, interlinks) and reproduce identically on a clean checkout.
ruff formatreports both files already formatted andruff checkadds no newfindings. Reverting only
great_docs/core.pymakes 3 of the new tests fail, sothey do pin the fix.
AI disclosure
This change was produced with the help of an AI coding agent. The agent used
Alpha as its tool: Alpha
drives the editing session, and the agent worked through it to inspect the
repository, make the edits, and run the test suite. Every hunk above was
reviewed by me, and the numbers in the Verification section were produced by
running the suite locally on the branch in this PR.