Module: DocsKit::LlmsText
- Defined in:
- lib/docs_kit/llms_text.rb
Overview
Builds the two AI-readable artifacts a docs-kit site serves, straight from the registry — zero authoring:
/llms.txt — the llmstxt.org index: H1 brand, `> tagline` blockquote,
one `## {group}` section per nav group, and a
`- [title](abs .md url)` line per authored page.
/llms-full.txt — every page's Markdown twin concatenated, `# {title}` +
body, separated by `---`.
It's a pure text builder: given a DocsKit::Configuration and (for the full
form) already-rendered [title, markdown] pairs, it produces strings with no
Rails. The controller owns the Rails view context — it renders each page to
Markdown (DocsKit::MarkdownExport) and hands the pairs to .full — so all the
shaping is unit-testable without booting Rails.
The enumeration source is DocsKit::Registry v2: each registry in
config.nav_registries responds to #nav_items ({ group => [NavItem] },
authored pages only) for the index and #all (entries with #view_class) for
the authored page list. An unwritten page (no resolvable view_class) is
excluded from both, so neither artifact ever links or concatenates a page
that doesn't exist yet.
Class Method Summary collapse
-
.full(_config, title_markdown_pairs) ⇒ Object
The llms-full.txt body: each [title, markdown] pair as
# {title}+ body, separated by a---rule. -
.groups(config) ⇒ Object
{ group => [links] } across every registry's #nav_items, in config order.
-
.index(config, base_url: nil) ⇒ Object
The llms.txt index string.
-
.link_line(link, base_url) ⇒ Object
- [label](absolute .md url). -
.mcp_block(base_url) ⇒ Object
The
## MCPsection pointing an agent at the read-only MCP endpoint. -
.md_url(href, base_url) ⇒ Object
href + ".md", absolutized against base_url when given.
-
.pages(config) ⇒ Object
The authored pages across every registry, in config/registry order — each responds to #title / #href / #view_class.
-
.section_blocks(config, base_url) ⇒ Object
One
## {group}block per nav group, each a tight bullet list of its authored pages'.mdlinks, in registry order.
Class Method Details
.full(_config, title_markdown_pairs) ⇒ Object
The llms-full.txt body: each [title, markdown] pair as # {title} + body,
separated by a --- rule. Empty pairs → "".
66 67 68 |
# File 'lib/docs_kit/llms_text.rb', line 66 def full(_config, title_markdown_pairs) title_markdown_pairs.map { |title, markdown| "# #{title}\n\n#{markdown}" }.join("\n\n---\n\n") end |
.groups(config) ⇒ Object
{ group => [links] } across every registry's #nav_items, in config order. A registry with no authored pages contributes nothing, so no empty section is ever emitted.
73 74 75 76 77 |
# File 'lib/docs_kit/llms_text.rb', line 73 def groups(config) config.nav_registries.values.each_with_object({}) do |registry, acc| registry.nav_items.each { |group, links| (acc[group] ||= []).concat(links) } end end |
.index(config, base_url: nil) ⇒ Object
The llms.txt index string. base_url absolutizes each page's .md href so
agent tooling fetches a portable URL; omit it for relative links.
Blocks (H1, the tagline blockquote, and one per section) are separated by a
blank line; within a section the ## heading and its - [..] bullets are a
single tight list (no blank lines between bullets), per the llmstxt.org
convention.
35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/docs_kit/llms_text.rb', line 35 def index(config, base_url: nil) blocks = ["# #{config.brand}"] tagline = config.tagline blocks << "> #{tagline}" if tagline && !tagline.to_s.empty? blocks.concat(section_blocks(config, base_url)) # Advertise the built-in MCP endpoint last, so an agent that reads llms.txt # discovers it can also connect over the protocol (native tools vs fetching # text). Only when the endpoint is actually live (gem present + c.mcp on). blocks << mcp_block(base_url) if config.mcp_enabled? blocks.join("\n\n") end |
.link_line(link, base_url) ⇒ Object
- [label](absolute .md url). The .md suffix targets the page's Markdown
twin (DocsKit::Controller#render_page).
92 93 94 |
# File 'lib/docs_kit/llms_text.rb', line 92 def link_line(link, base_url) "- [#{link.label}](#{md_url(link.href, base_url)})" end |
.mcp_block(base_url) ⇒ Object
The ## MCP section pointing an agent at the read-only MCP endpoint. The
/mcp URL is absolutized against base_url when available (agents connect to
a portable URL); relative otherwise.
82 83 84 85 86 87 88 |
# File 'lib/docs_kit/llms_text.rb', line 82 def mcp_block(base_url) url = base_url ? "#{base_url.chomp('/')}/mcp" : "/mcp" "## MCP\n" \ "This documentation is also available over the Model Context Protocol " \ "(search, page retrieval) at #{url} — add it to an MCP client " \ "(Claude Code, Claude.ai, Cursor) to query these docs as tools." end |
.md_url(href, base_url) ⇒ Object
href + ".md", absolutized against base_url when given. base_url has no trailing slash concerns here (hrefs are root-relative like "/docs/x").
98 99 100 101 102 103 |
# File 'lib/docs_kit/llms_text.rb', line 98 def md_url(href, base_url) path = "#{href}.md" return path unless base_url "#{base_url.chomp('/')}#{path}" end |
.pages(config) ⇒ Object
The authored pages across every registry, in config/registry order — each responds to #title / #href / #view_class. The controller renders these to Markdown for .full.
60 61 62 |
# File 'lib/docs_kit/llms_text.rb', line 60 def pages(config) config.nav_registries.values.flat_map { |registry| registry.all.select(&:view_class) } end |
.section_blocks(config, base_url) ⇒ Object
One ## {group} block per nav group, each a tight bullet list of its
authored pages' .md links, in registry order.
51 52 53 54 55 |
# File 'lib/docs_kit/llms_text.rb', line 51 def section_blocks(config, base_url) groups(config).map do |group, links| ["## #{group}", *links.map { |link| link_line(link, base_url) }].join("\n") end end |