feat(llms): serve /llms.txt + /llms-full.txt from the registry#34
Merged
Conversation
Every docs-kit site now serves the two llmstxt.org artifacts, built from the registry with zero authoring, making the docs AI-consumable without HTML scraping. ## Summary - DocsKit::LlmsText (lib/docs_kit/llms_text.rb) — a pure, Rails-free builder: .index → the llms.txt index (H1 brand, `> tagline` blockquote, one `##` section per nav group, tight `- [Title](abs .md url)` bullet list per authored page); .pages → authored pages across every registry; .full → each page's Markdown joined by `---`. Unwritten pages (no view_class) are excluded, so no dead links. - DocsKit::LlmsController (app/controllers/docs_kit/llms_controller.rb) — thin, ships in the gem; routes live in the host app (the engine stays glue-only). Both actions render text/plain; charset=utf-8 and HTTP-cache via stale?/etag on [VERSION, body]. #full threads the Rails view context so MarkdownExport can render each page's twin. - c.tagline config knob (default nil → blockquote omitted). - Install generator scaffolds both routes idempotently; docs-kit.rb.erb documents the tagline knob. - Dogfood: docs/ routes + a tagline; docs route gains (.:format) so its .md links resolve. - README "AI-readable docs (llms.txt)" section. Rails' own autoloader owns DocsKit::LlmsController from app/controllers/docs_kit/ (the engine already opts app/ out of the gem's zeitwerk loader; the controller's ActionController::Base superclass is Rails-only). The gem controller deliberately does NOT define #config — RequestForgeryProtection delegates allow_forgery_protection to it, so shadowing it with DocsKit.configuration broke csrf_meta_tags when #full renders a page's <head>; the DocsKit config reader is #docs_config. ## Test Coverage - spec/docs_kit/llms_text_spec.rb — H1/blockquote/H2 groups/tight bullet lists, .md suffixes, absolute + relative URLs, tagline omission, multi-registry order, empty-registry, authored-only .pages, .full concatenation. 100%. - spec/docs_kit/llms_controller_spec.rb — source wiring: ships at the Rails autoload path, subclasses AC::Base, text/plain, builds via LlmsText, etag, and the #config-shadowing regression guard. - spec/docs_kit/configuration_spec.rb — c.tagline default + override (Config 100%). - spec/generators/install_generator_spec.rb — both routes added + idempotent re-run (no duplicates). ## Verification - [x] bundle exec rubocop — 78 files, no offenses - [x] bundle exec rspec — 321 examples, 0 failures; Configuration/Registry/LlmsText 100% - [x] Dogfood: curl /llms.txt (index, 9 working .md links) + /llms-full.txt (9 pages, 8 rules) - [x] Generator re-run does not duplicate routes
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.
Closes #18.
Every docs-kit site now serves the two llmstxt.org artifacts, built from the registry with zero authoring — making the docs AI-consumable without HTML scraping.
What you get
/llms.txt— H1 brand, an optional> taglinesummary, one##section per nav group, and a tight- [Title](…/page.md)bullet list linking each authored page's Markdown twin./llms-full.txt— every page's Markdown (the same twin as.md) joined by---. Both aretext/plain; charset=utf-8and HTTP-cached; unwritten pages are excluded, so no dead links.Live dogfood output (
docs/):Design
Per the issue's Decision: controller in the gem, routes in the host app.
DocsKit::LlmsText(lib/docs_kit/llms_text.rb) — a pure, Rails-free builder (.index/.pages/.full). All text shaping is unit-tested without booting Rails.DocsKit::LlmsController(app/controllers/docs_kit/llms_controller.rb) — thin; ships in the gem, routes live in the app so a site keeps full control over path/auth/omission (the engine stays glue-only).#fullthreads the Rails view context soMarkdownExportcan render each page's twin, then hands[title, md]pairs toLlmsText.full.c.taglineconfig knob (defaultnil→ blockquote line omitted).docs-kit.rb.erbdocuments the tagline knob.stale?/etagon[DocsKit::VERSION, body].Two things worth a reviewer's eye
DocsKit::LlmsControllerfromapp/controllers/docs_kit/(docs_kit→DocsKitunder the default inflector). The engine already optsapp/out of the gem's zeitwerk loader, and the controller'sActionController::Basesuperclass is Rails-only — so pushing it from the gem loader both conflicts with Rails' loader and can't load standalone. Letting Rails resolve it is the clean path; verified live (the constant loads and both endpoints serve).#configshadow (root cause of a mid-work 500).RequestForgeryProtectiondoesdelegate :allow_forgery_protection, to: :config. A first cut definedprivate def config = DocsKit.configurationon the controller, which shadowedActionController::Base#config— socsrf_meta_tags(called inDocsUI::Shell's<head>when#fullrenders a page) resolvedallow_forgery_protectionagainst aDocsKit::Configurationand raisedNameError. Fixed by renaming the reader to#docs_config; a regression guard in the controller spec asserts nodef configon the controller.Test plan
spec/docs_kit/llms_text_spec.rb.mdsuffixes, absolute + relative URLs, tagline omission, multi-registry order, empty registry, authored-only.pages,.fullconcatenation. 100%spec/docs_kit/llms_controller_spec.rbAC::Base,text/plain, builds viaLlmsText, etag, and the#config-shadowing regression guardspec/docs_kit/configuration_spec.rbc.taglinedefault + override (Configuration 100%)spec/generators/install_generator_spec.rbVerification
bundle exec rubocop— 78 files, no offensesbundle exec rspec— 321 examples, 0 failures; Configuration / Registry / LlmsText 100%curl /llms.txt(index, 9 working.mdlinks) +curl /llms-full.txt(9 pages, 8---rules, 37 KB)Out of scope (per the issue)
MCP (next phase), sitemap/SEO artifacts, per-page exclusion flags.