From 7ec7e1161bc3658e626f258f151140521af356ec Mon Sep 17 00:00:00 2001 From: haranrk Date: Fri, 10 Jul 2026 21:55:24 +0000 Subject: [PATCH 1/4] feat: add flatten_md pure helpers for flat page.md URLs --- scripts/flatten_md.py | 42 ++++++++++++++++++++++++++++++++++++ scripts/test_flatten_md.py | 44 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 scripts/flatten_md.py create mode 100644 scripts/test_flatten_md.py diff --git a/scripts/flatten_md.py b/scripts/flatten_md.py new file mode 100644 index 0000000000..de85b9f8f8 --- /dev/null +++ b/scripts/flatten_md.py @@ -0,0 +1,42 @@ +"""MkDocs post-build hook: flatten per-page Markdown from ``page/index.md`` to +``page.md``. + +Registered via the top-level ``hooks:`` list in ``mkdocs.yml``. Runs after the +``mkdocs-llmstxt`` plugin (see the ``event_priority`` on ``on_post_build``). +""" + +from __future__ import annotations + +import re +from pathlib import PurePosixPath + + +def flat_md_target(rel_path: str) -> str | None: + """Map a generated Markdown file's site-relative path to its flat target. + + Args: + rel_path: Path of the generated file relative to ``site_dir``, POSIX form. + + Returns: + The new site-relative path (e.g. ``agents/config.md``), or ``None`` if + the file must stay in place (root ``index.md`` or a non-``index.md`` file). + """ + p = PurePosixPath(rel_path) + if p.name != "index.md": + return None + parent = p.parent + if parent in (PurePosixPath("."), PurePosixPath("")): + return None # root site/index.md stays as-is + return f"{parent.as_posix()}.md" + + +def rewrite_index_links(text: str, site_url: str) -> str: + """Rewrite ``{site_url}//index.md`` links to ``{site_url}/.md``. + + The bare homepage link ``{site_url}/index.md`` has no intermediate path + segment and is left unchanged. Scoping to ``site_url`` avoids corrupting + unrelated URLs that merely contain the substring ``index.md``. + """ + base = site_url.rstrip("/") + pattern = re.compile(re.escape(base) + r"/([^\s)]+?)/index\.md") + return pattern.sub(lambda m: f"{base}/{m.group(1)}.md", text) diff --git a/scripts/test_flatten_md.py b/scripts/test_flatten_md.py new file mode 100644 index 0000000000..ff30dab9e0 --- /dev/null +++ b/scripts/test_flatten_md.py @@ -0,0 +1,44 @@ +from flatten_md import flat_md_target, rewrite_index_links + +SITE = "https://adk.dev" + + +def test_flat_md_target_regular_page(): + assert flat_md_target("agents/config/index.md") == "agents/config.md" + + +def test_flat_md_target_section_index(): + assert flat_md_target("agents/index.md") == "agents.md" + + +def test_flat_md_target_root_index_stays(): + assert flat_md_target("index.md") is None + + +def test_flat_md_target_non_index_ignored(): + assert flat_md_target("agents/config.md") is None + + +def test_rewrite_links_regular_page(): + text = "[cfg](https://adk.dev/agents/config/index.md)" + assert rewrite_index_links(text, SITE) == "[cfg](https://adk.dev/agents/config.md)" + + +def test_rewrite_links_section_index(): + text = "See https://adk.dev/agents/index.md for details" + assert rewrite_index_links(text, SITE) == "See https://adk.dev/agents.md for details" + + +def test_rewrite_links_homepage_unchanged(): + text = "Home: https://adk.dev/index.md" + assert rewrite_index_links(text, SITE) == "Home: https://adk.dev/index.md" + + +def test_rewrite_links_trailing_slash_site_url(): + text = "https://adk.dev/agents/config/index.md" + assert rewrite_index_links(text, "https://adk.dev/") == "https://adk.dev/agents/config.md" + + +def test_rewrite_links_idempotent(): + once = rewrite_index_links("https://adk.dev/agents/config/index.md", SITE) + assert rewrite_index_links(once, SITE) == once From 48d097f035c8ab3a4ee238b9a06de66dc09d57dd Mon Sep 17 00:00:00 2001 From: haranrk Date: Fri, 10 Jul 2026 22:00:46 +0000 Subject: [PATCH 2/4] feat: flatten generated page markdown to page.md via post-build hook --- mkdocs.yml | 4 ++++ scripts/flatten_md.py | 49 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index 5c8f50f467..acefadb9a8 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -262,6 +262,10 @@ plugins: Community: - community/*.md +# Hooks +hooks: + - scripts/flatten_md.py + # Navigation nav: - Home: index.md diff --git a/scripts/flatten_md.py b/scripts/flatten_md.py index de85b9f8f8..255a1162e0 100644 --- a/scripts/flatten_md.py +++ b/scripts/flatten_md.py @@ -7,8 +7,11 @@ from __future__ import annotations +import logging import re -from pathlib import PurePosixPath +from pathlib import Path, PurePosixPath + +import mkdocs.plugins def flat_md_target(rel_path: str) -> str | None: @@ -40,3 +43,47 @@ def rewrite_index_links(text: str, site_url: str) -> str: base = site_url.rstrip("/") pattern = re.compile(re.escape(base) + r"/([^\s)]+?)/index\.md") return pattern.sub(lambda m: f"{base}/{m.group(1)}.md", text) + + +log = logging.getLogger("mkdocs.hooks.flatten_md") + + +@mkdocs.plugins.event_priority(-100) +def on_post_build(config, **kwargs) -> None: # noqa: ARG001 + """Rename generated ``index.md`` files to flat ``page.md`` and fix links. + + Priority ``-100`` ensures this runs after the ``mkdocs-llmstxt`` plugin's + own ``on_post_build`` (lower priority runs later). + """ + site_dir = Path(config["site_dir"]) + site_url = config["site_url"] + + # Pass 1: rename /index.md -> .md (root index.md is left alone). + renamed = 0 + for md_path in list(site_dir.rglob("index.md")): + rel = md_path.relative_to(site_dir).as_posix() + target_rel = flat_md_target(rel) + if target_rel is None: + continue + md_path.replace(site_dir / target_rel) + renamed += 1 + + # Pass 2: rewrite site-internal .../index.md links in all text outputs. + rewritten = 0 + targets = list(site_dir.rglob("*.md")) + for extra in ("llms.txt", "llms-full.txt"): + extra_path = site_dir / extra + if extra_path.exists(): + targets.append(extra_path) + for path in targets: + text = path.read_text(encoding="utf8") + new_text = rewrite_index_links(text, site_url) + if new_text != text: + path.write_text(new_text, encoding="utf8") + rewritten += 1 + + log.info( + "flatten_md: renamed %d markdown file(s), rewrote links in %d file(s)", + renamed, + rewritten, + ) From a242092fb1970e99d9567895317a3aa094aa6b00 Mon Sep 17 00:00:00 2001 From: haranrk Date: Fri, 10 Jul 2026 22:07:16 +0000 Subject: [PATCH 3/4] feat: point view/copy Markdown buttons at flat page.md URLs --- overrides/partials/actions.html | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/overrides/partials/actions.html b/overrides/partials/actions.html index 058316e363..d87025e25a 100644 --- a/overrides/partials/actions.html +++ b/overrides/partials/actions.html @@ -5,15 +5,14 @@ {% include ".icons/" ~ icon ~ ".svg" %} {% endif %} + {% set md_href = (page.url.rstrip('/') ~ ".md") | url %} {% if "content.action.view" in features %} - {% set view_url = page.url | url ~ "index.md" %} - + {% set icon = config.theme.icon.view or "material/file-eye-outline" %} {% include ".icons/" ~ icon ~ ".svg" %} {% endif %} - {% set raw_url = page.url | url ~ "index.md" %} - {% endif %} From 2f14ec852b07136739350c0b35da8aca1018ef72 Mon Sep 17 00:00:00 2001 From: haranrk Date: Fri, 10 Jul 2026 22:14:27 +0000 Subject: [PATCH 4/4] test: add on_post_build integration test and document absolute-link assumption --- scripts/flatten_md.py | 4 ++ scripts/test_flatten_md.py | 76 +++++++++++++++++++++++++++++++++++++- 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/scripts/flatten_md.py b/scripts/flatten_md.py index 255a1162e0..fbea7ead1b 100644 --- a/scripts/flatten_md.py +++ b/scripts/flatten_md.py @@ -69,6 +69,10 @@ def on_post_build(config, **kwargs) -> None: # noqa: ARG001 renamed += 1 # Pass 2: rewrite site-internal .../index.md links in all text outputs. + # Load-bearing assumption: internal .md links are absolute {site_url}/... URLs + # because mkdocs-llmstxt normalizes them via its _convert_to_absolute_link, so + # rewrite_index_links only handles absolute links; relative .../index.md links + # are not expected here. rewritten = 0 targets = list(site_dir.rglob("*.md")) for extra in ("llms.txt", "llms-full.txt"): diff --git a/scripts/test_flatten_md.py b/scripts/test_flatten_md.py index ff30dab9e0..3be6147efe 100644 --- a/scripts/test_flatten_md.py +++ b/scripts/test_flatten_md.py @@ -1,4 +1,4 @@ -from flatten_md import flat_md_target, rewrite_index_links +from flatten_md import flat_md_target, on_post_build, rewrite_index_links SITE = "https://adk.dev" @@ -42,3 +42,77 @@ def test_rewrite_links_trailing_slash_site_url(): def test_rewrite_links_idempotent(): once = rewrite_index_links("https://adk.dev/agents/config/index.md", SITE) assert rewrite_index_links(once, SITE) == once + + +def _build_fake_site(site_root): + """Create a realistic post-build site layout under ``site_root``. + + Returns the ``config`` dict expected by ``on_post_build``. + """ + (site_root / "agents" / "config").mkdir(parents=True) + (site_root / "agents" / "config" / "index.md").write_text( + "[agents](https://adk.dev/agents/index.md)\n" + "[qs](https://adk.dev/get-started/quickstart/index.md)\n", + encoding="utf8", + ) + (site_root / "agents" / "index.md").write_text( + "[cfg](https://adk.dev/agents/config/index.md)\n", + encoding="utf8", + ) + (site_root / "index.md").write_text( + "Home: https://adk.dev/index.md\n", + encoding="utf8", + ) + (site_root / "llms.txt").write_text( + "- [X](https://adk.dev/agents/config/index.md)\n", + encoding="utf8", + ) + (site_root / "llms-full.txt").write_text( + "See https://adk.dev/agents/index.md for details\n", + encoding="utf8", + ) + return {"site_dir": str(site_root), "site_url": SITE} + + +def test_on_post_build_renames_layout(tmp_path): + site_root = tmp_path / "site" + config = _build_fake_site(site_root) + + on_post_build(config) + + # Flat targets exist; original nested index.md files are gone. + assert (site_root / "agents" / "config.md").exists() + assert not (site_root / "agents" / "config" / "index.md").exists() + assert (site_root / "agents.md").exists() + assert not (site_root / "agents" / "index.md").exists() + + # Root index.md is left in place (not renamed). + assert (site_root / "index.md").exists() + + +def test_on_post_build_rewrites_links(tmp_path): + site_root = tmp_path / "site" + config = _build_fake_site(site_root) + + on_post_build(config) + + config_md = (site_root / "agents" / "config.md").read_text(encoding="utf8") + assert "https://adk.dev/agents.md" in config_md + assert "https://adk.dev/get-started/quickstart.md" in config_md + assert "index.md" not in config_md + + agents_md = (site_root / "agents.md").read_text(encoding="utf8") + assert "https://adk.dev/agents/config.md" in agents_md + assert "index.md" not in agents_md + + llms = (site_root / "llms.txt").read_text(encoding="utf8") + assert "https://adk.dev/agents/config.md" in llms + assert "index.md" not in llms + + llms_full = (site_root / "llms-full.txt").read_text(encoding="utf8") + assert "https://adk.dev/agents.md" in llms_full + assert "index.md" not in llms_full + + # Bare homepage link is preserved unchanged. + root_md = (site_root / "index.md").read_text(encoding="utf8") + assert root_md == "Home: https://adk.dev/index.md\n"