This is an automated email from the ASF dual-hosted git repository.
spmallette pushed a commit to branch afd
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
The following commit(s) were added to refs/heads/afd by this push:
new c88deafa66 Fix llms.txt index entries for book landing pages and
tutorials
c88deafa66 is described below
commit c88deafa66deefd63cc508dabfb3604eba66891c
Author: Stephen Mallette <[email protected]>
AuthorDate: Sat Jul 25 19:40:57 2026 +0000
Fix llms.txt index entries for book landing pages and tutorials
The summary-driven split produced wrong llms.txt entries for every book's
landing page: the document title broke off to its own page, leaving index.md
owning the first chapter, so the index line showed that chapter's title and
a
scraped description (an image macro, the version line, or a mid-sentence
fragment).
- Splitter: fold the leading document-title section into the index page so
its
own llms-summary describes index.md and its content renders there,
instead of
breaking off to a separate _<doctitle>.md page.
- Converter: for article-doctype pages (the tutorials), the title-bearing
section is promoted to the document title, so read its llms-summary /
allow-oversize from the promoted leading section (not just the Document)
and
emit the hidden markers on the landing page.
- Curation: give the headingless gremlin-language-variants tutorial a titled
summary, and replace four recipe summaries that had fallen back to
generic or
mid-sentence text (collections, shortest-path, edge-move,
if-then-based-grouping) with proper descriptions.
Result: all 240 pages, including every book/tutorial landing page, have a
clean
curated llms.txt description; build passes; 412 links resolve; 174 tests
pass.
Assisted-by: Claude Code:claude-opus-4-8
---
docs/src/recipes/collections.asciidoc | 2 +-
docs/src/recipes/edge-move.asciidoc | 2 +-
docs/src/recipes/if-then-based-grouping.asciidoc | 2 +-
docs/src/recipes/shortest-path.asciidoc | 2 +-
.../gremlin-language-variants/index.asciidoc | 3 +++
.../tinkerpop/tinkeradoc/MarkdownConverter.java | 28 +++++++++++++++++++-
.../tinkerpop/tinkeradoc/MarkdownSplitter.java | 30 ++++++++++++++++++++++
7 files changed, 64 insertions(+), 5 deletions(-)
diff --git a/docs/src/recipes/collections.asciidoc
b/docs/src/recipes/collections.asciidoc
index 1dab723f74..5c1d493a53 100644
--- a/docs/src/recipes/collections.asciidoc
+++ b/docs/src/recipes/collections.asciidoc
@@ -16,7 +16,7 @@ KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
////
-[llms-summary="Recipe: A recipe for collections in Gremlin."]
+[llms-summary="Recipe: reshaping List and Map collections within a traversal —
unfolding, folding, grouping, and reformatting side-effect data."]
[[collections]]
== Collections
diff --git a/docs/src/recipes/edge-move.asciidoc
b/docs/src/recipes/edge-move.asciidoc
index 25a416d030..5f8efcec55 100644
--- a/docs/src/recipes/edge-move.asciidoc
+++ b/docs/src/recipes/edge-move.asciidoc
@@ -16,7 +16,7 @@ KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
////
-[llms-summary="Recipe: A recipe for moving an edge in Gremlin."]
+[llms-summary="Recipe: moving an existing edge from one incident vertex to
another by recreating it with new endpoints and dropping the original."]
[[edge-move]]
== Moving an Edge
diff --git a/docs/src/recipes/if-then-based-grouping.asciidoc
b/docs/src/recipes/if-then-based-grouping.asciidoc
index 5d88b9a6ab..753cbb70e9 100644
--- a/docs/src/recipes/if-then-based-grouping.asciidoc
+++ b/docs/src/recipes/if-then-based-grouping.asciidoc
@@ -16,7 +16,7 @@ KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
////
-[llms-summary="Recipe: Consider the following traversal over the \"modern\"
toy graph:"]
+[llms-summary="Recipe: bucketing elements into groups by if/then-style
conditions using choose() within group()."]
[[if-then-based-grouping]]
== If-Then Based Grouping
diff --git a/docs/src/recipes/shortest-path.asciidoc
b/docs/src/recipes/shortest-path.asciidoc
index 89a0577e9d..66d71f6b12 100644
--- a/docs/src/recipes/shortest-path.asciidoc
+++ b/docs/src/recipes/shortest-path.asciidoc
@@ -16,7 +16,7 @@ KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
////
-[llms-summary="Recipe: A recipe for shortest path in Gremlin."]
+[llms-summary="Recipe: finding the shortest path between vertices with
Gremlin, using repeat()/until() and path-length comparison."]
[[shortest-path]]
== Shortest Path
diff --git a/docs/src/tutorials/gremlin-language-variants/index.asciidoc
b/docs/src/tutorials/gremlin-language-variants/index.asciidoc
index 1a3f285e9f..64779d3915 100644
--- a/docs/src/tutorials/gremlin-language-variants/index.asciidoc
+++ b/docs/src/tutorials/gremlin-language-variants/index.asciidoc
@@ -24,6 +24,9 @@
image::apache-tinkerpop-logo.png[width=500,link="https://tinkerpop.apache.org"]
*x.y.z*
+[llms-summary="A stub tutorial that redirects to the reference documentation
on Gremlin drivers and language variants."]
+= Gremlin Language Variants
+
image::gremlin-house-of-mirrors.png[width=1024]
Please see the
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#gremlin-drivers-variants[Reference
Documentation]
diff --git
a/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/MarkdownConverter.java
b/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/MarkdownConverter.java
index e7b617b503..26269ec1e0 100644
---
a/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/MarkdownConverter.java
+++
b/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/MarkdownConverter.java
@@ -167,9 +167,17 @@ public class MarkdownConverter extends StringConverter {
// already appears as an H1 there (matching the HTML backend's single
<h1 class="sect0">).
// In an article, the title is not part of the content, so emit it as
the leading H1.
if (title != null && !title.isEmpty() && !isBook(doc)) {
+ // Article doctype: the title-bearing section is promoted to the
document title, so its
+ // curated llms-summary / allow-oversize attributes are not
visible via convertSection.
+ // They resolve on the Document (when set as document attributes)
or on the leading
+ // section AsciidoctorJ promoted to the title; check both so the
landing page carries the
+ // hidden markers that drive the llms.txt description and the size
lint.
final StringBuilder head = new StringBuilder();
appendAnchor(head, doc.getId());
- head.append("# ").append(title).append("\n\n").append(content);
+ head.append("# ").append(title).append("\n\n");
+ appendLlmsSummary(head, docLevelAttribute(doc, LLMS_SUMMARY_ATTR));
+ appendAllowOversizeMarker(head, docLevelAttribute(doc,
LLMS_ALLOW_OVERSIZE_ATTR));
+ head.append(content);
assembled = head.toString();
} else {
assembled = content;
@@ -181,6 +189,24 @@ public class MarkdownConverter extends StringConverter {
return substituteVersion(doc, assembled);
}
+ /**
+ * Resolves a doc-level attribute for the article title path: prefers the
Document's own
+ * attribute, falling back to the leading section that AsciidoctorJ
promoted to the title (which
+ * is where a {@code [llms-summary]} on the article's {@code == Title}
actually lands).
+ */
+ private static Object docLevelAttribute(final Document doc, final String
name) {
+ final Object onDoc = doc.getAttribute(name);
+ if (onDoc != null) return onDoc;
+ for (final StructuralNode block : doc.getBlocks()) {
+ if (block instanceof Section) {
+ final Object onSection = block.getAttribute(name);
+ if (onSection != null) return onSection;
+ break; // only the first/leading section is the promoted title
+ }
+ }
+ return null;
+ }
+
/** Replaces {@code x.y.z} with the resolved TinkerPop version, if
available. */
private static String substituteVersion(final Document doc, final String
text) {
final Object version =
firstNonNull(doc.getAttribute("tinkerpop-version"),
doc.getAttribute("revnumber"));
diff --git
a/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/MarkdownSplitter.java
b/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/MarkdownSplitter.java
index fcc65ba381..8b37863088 100644
---
a/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/MarkdownSplitter.java
+++
b/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/MarkdownSplitter.java
@@ -221,6 +221,18 @@ class MarkdownSplitter {
// up to — but not including — the next summarized descendant, which
breaks off to its own
// page. There is no size-based splitting; page size is an author
concern surfaced by the
// build's size lint, with intentional exceptions flagged via
allow-oversize.
+ // The document title (the single leading top-level section — the
book's "= Title") is the
+ // landing page: it owns index.md so its own llms-summary describes
the index and its content
+ // renders there, rather than breaking off to a page of its own. Fold
it into the root so the
+ // root preamble + doctitle render together as index.md. Everything
below still breaks on
+ // summaries as usual.
+ final Node docTitle = leadingDocTitle(root);
+ if (docTitle != null) {
+ root.lines.addAll(docTitle.lines);
+ root.children.remove(docTitle);
+ root.children.addAll(0, docTitle.children);
+ }
+
final Map<String, String> anchorToFile = new LinkedHashMap<>();
final List<PagePlan> plans = new ArrayList<>();
final PagePlan index = new PagePlan(indexFileName, root);
@@ -238,6 +250,24 @@ class MarkdownSplitter {
return pages;
}
+ /**
+ * Returns the document-title node to fold into the index page, or {@code
null} if there is none.
+ * The doctitle is the book's leading section: the root's first child,
when that child is the sole
+ * shallowest-level top section (i.e. the other top-level siblings are its
heading level or
+ * deeper). In practice this is the {@code = Title} that AsciidoctorJ
renders as the first h1.
+ */
+ private static Node leadingDocTitle(final Node root) {
+ if (root.children.isEmpty()) return null;
+ final Node first = root.children.get(0);
+ // Only treat it as the doctitle when it carries content meant to lead
the landing page: it
+ // has a summary (so it would otherwise wrongly break off) or the root
has no preamble of its
+ // own. Guard against odd trees by requiring it be the shallowest
top-level section.
+ for (final Node sib : root.children) {
+ if (sib != first && sib.level < first.level) return null;
+ }
+ return first;
+ }
+
/**
* Walks the tree creating a page for every summarized descendant of
{@code node} (the node's own
* page having already been created). Recurses through the whole tree so
nested summarized