Skip to content

DOCOPS-153 Markdown versions of published pages for LLMs (Antora extension) - #96

Open
recrwplay wants to merge 7 commits into
devfrom
markdown-pages-extension
Open

DOCOPS-153 Markdown versions of published pages for LLMs (Antora extension)#96
recrwplay wants to merge 7 commits into
devfrom
markdown-pages-extension

Conversation

@recrwplay

@recrwplay recrwplay commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Markdown versions of published pages for LLMs

Adds a new Antora site-generator extension, @neo4j-antora/markdown-pages, that emits a
Markdown version of every published page so LLMs (and fetch tools) can consume the docs as
Markdown. An LLM can retrieve a page's Markdown from its path — the .md sits right beside the
generated HTML (.../page/index.html.../page/index.md).

Why convert the generated HTML (not the AsciiDoc source)

Converting the rendered HTML means everything is already resolved — includes, attributes,
xrefs (as real links), and custom macros (label:, roles-labels, table-footnotes). Converting from
AsciiDoc source would require re-implementing Asciidoctor, and — crucially — many attributes are
defined in the playbook / antora.yml, not in the page (e.g. {neo4j-docs-base-uri}, limits,
versions). Those never appear in the content, so a raw-.adoc consumer can't resolve them at all;
only the built HTML has them substituted. (It also would not help with the hard table cases — see
Tables — because that limitation is Markdown's, not the source format's.)

How it works

  • Runs at the pagesComposed event — after Asciidoctor conversion and the roles-labels /
    table-footnotes extensions — and selects each page's article.doc content node (so page
    chrome/nav/footer is excluded).
  • Converts that HTML to Markdown with turndown +
    the GFM plugin, scoping with node-html-parser (already a repo dependency).
  • Adds each .md to the site catalog via siteCatalog.addFile, mirroring the HTML output path.

Conversion handling:

  • Code blocks → fenced, with the source language, syntax-highlight spans stripped.
  • Admonitions (NOTE/TIP/IMPORTANT/WARNING/CAUTION) → GitHub-style > **NOTE** blockquotes.
  • Content labels (roles-labels) → inline `label` badges — parenthesised after the title
    on headings (## Heading (label)), bare on other blocks.
  • Icon glyphs (icon:check[] etc. → <i class="fa fa-check">, which has no text content) →
    resolved to Unicode / (else the icon's title or name). Without this, turndown drops the
    empty element and capability/feature matrices silently lose their cells.
  • Heading anchors → the <a class="anchor"> self-links are dropped.
  • Links/images → rewritten to absolute URLs (portable for LLMs).
  • Minimal YAML frontmatter (title, absolute url) per file.

Tables (hybrid)

Markdown/GFM can't express colspan or nested grids, so a one-size rule mangles Neo4j's tables:

  • Header tables → GFM.
  • Clean headerless 2-column tables (config specs — Description / Valid values / Default) →
    key/value blocks.
  • Complex/irregular tables (colspan, embedded sub-grids — e.g. Cypher function Details
    tables with a Name/Type/Description arguments grid) → kept as HTML. HTML tables are valid
    Markdown and read reliably by LLMs (structure/colspan explicit), so this is faithful rather than
    a degraded fallback.

Usage

In a docset playbook:

antora:
  extensions:
  - "@neo4j-antora/markdown-pages"          # or, with options:
  - require: "@neo4j-antora/markdown-pages"
    frontmatter: true        # YAML title/url frontmatter (default true)
    absolute_links: true     # rewrite relative links/images to absolute (default true)

Testing / verification

  • 13 unit tests over the pure HTML→Markdown core (node --test).
  • Verified end-to-end via npm run build:preview in docs-tools, and against real
    docs-cypher (function references, config tables) and docs-aura pages — including the
    permission/capability matrices, confirming icons resolve to / and no cells are lost.

Known limitations (follow-ups, not blockers)

  • Lists render "loose" (a blank line between items), and block-heavy output has some extra blank
    lines — cosmetic; doesn't affect LLM reading.
  • Complex tables are kept as HTML (intentional — see Tables); an optional future enhancement could
    detect a function's Arguments sub-grid and render it as a nested GFM table.
  • Content-label scope on non-heading blocks is proximity-based: a label attached to a heading
    applies to the whole section in AsciiDoc, but flat Markdown can't encode that containment, so the
    badge sits next to the block it was on.
  • No root llms.txt index or llms-full.txt yet (natural next step).

🤖 Generated with Claude Code

New site-generator extension that, at pagesComposed, converts each published
page's article content to a sibling .md (mirrors the HTML output path) for LLM
consumption. All AsciiDoc is already resolved (includes, attributes, xrefs,
macros) since it converts the generated HTML.

- turndown + turndown-plugin-gfm; node-html-parser for scoping to article.doc
- code fences (with language), admonitions -> blockquotes, heading-anchor strip,
  absolute links, minimal title/url frontmatter
- tables: header -> GFM; clean headerless 2-col -> key/value blocks;
  complex/irregular (colspan, embedded sub-grids) -> kept as HTML (LLM-readable)
- wired into preview.yml; 10 unit tests; verified on docs-tools + docs-cypher

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread extensions/antora/markdown-pages/markdown-pages.js Fixed
Comment thread extensions/antora/markdown-pages/markdown-pages.js Fixed
Comment thread extensions/antora/markdown-pages/markdown-pages.js Fixed
recrwplay and others added 6 commits July 13, 2026 17:01
- escape backslashes as well as | / " when building GFM cells and YAML frontmatter
- strip trailing slashes in joinUrl without a regex (no ReDoS on untrusted input)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
roles-labels nests <div class="labels"> inside the labeled element; convert each
in place to inline-code badges so they attach to their block and don't concatenate
(the reported "AuraDB Business CriticalEnterprise Edition..." run-together).
Parentheses on headings (`## Heading (`label`)`); bare badges on other labeled
blocks (paragraphs, examples, admonitions, table cells). +2 tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
icon:name[] renders as an empty <i class="fa fa-name"> element with no
text, which turndown drops as a blank node -- silently emptying the check
marks in capability/feature matrices. Resolve the glyph to text in the
node-html-parser preprocessing step (fa-check -> checkmark, fa-times ->
cross, else title or icon name) so it survives both the top-level and
nested table-cell conversion passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@neo4j-docops-agent

Copy link
Copy Markdown
Collaborator

This PR includes documentation updates
View the updated docs at https://neo4j-docs-tools-96.surge.sh

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.

3 participants