You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@magpie.apache.org by jm...@apache.org on 2026/09/05 04:28:23 UTC

(magpie-site) 01/01: Serve and advertise the Markdown twins

This is an automated email from the ASF dual-hosted git repository.

justinmclean pushed a commit to branch markdown-negotiation
in repository https://gitbox.apache.org/repos/asf/magpie-site.git

commit 79eabd3996d1dabebd1ea5d97e46a866574e88e2
Author: Justin Mclean <ju...@classsoftware.com>
AuthorDate: Sat Sep 5 03:42:59 2026 +0000

    Serve and advertise the Markdown twins
    
    The previous change writes a .md twin beside every docs page; this one
    makes them findable without knowing the convention.
    
    - public/.htaccess: text/markdown on .md files and the two llms files, and
      X-Robots-Tag: noindex on the twins, which are alternates of the HTML
      page, not pages
    - src/components/JsonLd.astro: <link rel="alternate" type="text/markdown">
      on docs pages, pointing at the twin, so it is discoverable from the HTML.
      DocsLayout.astro needs to pass docMarkdownPath(entry) through
    
    No Accept-header negotiation, as on iggy.apache.org: the sibling file needs
    no server control. The is-agentic scan reported negotiation without Vary on
    this site; PR.md has the curl to confirm whether the server does it on its
    own, and the one-line Vary header to add only if it does.
    
    PR.md carries the wiring steps and the curl checks for all three branches.
    
    Generated-by: Claude (Fable 5.1)
---
 PR.md                       | 69 +++++++++++++++++++++++++++++++++++++++++++++
 public/.htaccess            | 10 +++++++
 src/components/JsonLd.astro | 11 +++++++-
 3 files changed, 89 insertions(+), 1 deletion(-)

diff --git a/PR.md b/PR.md
new file mode 100644
index 0000000..ff34160
--- /dev/null
+++ b/PR.md
@@ -0,0 +1,69 @@
+# feat(site): agent discoverability — llms.txt, Markdown twins, sitemap, 404, structured data
+
+Ports apache/iggy-website #77 and #79 to the Astro site. Nothing visible to a
+person changes; every addition is a file agents read or a header they see.
+
+## What the scan found (is-agentic, 59/100, 5 Sep)
+
+- unknown paths answer 200 with the app shell (critical)
+- the scanner reported Accept negotiation without `Vary: Accept`; unverified, see below
+- no `llms.txt`; developer resources not discoverable by name
+- phantom "API" surface: the scanner inferred an HTTP API and marked it blocked
+
+## Files
+
+| Path | Does |
+|---|---|
+| `public/.htaccess` | `ErrorDocument 404 /404.html`; `text/markdown` type on `.md` and the llms files |
+| `public/robots.txt` | allow all, points at the sitemap |
+| `src/lib/site.ts` | site constants, static route list, docs → markdown helpers |
+| `src/pages/llms.txt.ts` | llmstxt.org index from the docs collection; says plainly there is no API on this domain |
+| `src/pages/llms-full.txt.ts` | every docs page concatenated |
+| `src/pages/docs/[...slug].md.ts` | Markdown twin of every docs page, same path with `.md` |
+| `src/components/JsonLd.astro` | Organization / WebSite / SoftwareSourceCode JSON-LD, `rel=alternate` to the twin, `rel=llms-txt` |
+| `src/pages/404.astro` | recovery links |
+
+## Wiring (needs the checkout)
+
+1. `npm install` (branch 1 adds `@astrojs/sitemap` to package.json and wires
+   it in astro.config.mjs; the lockfile needs regenerating).
+2. `DOCS_COLLECTION` in `src/lib/site.ts` assumes the synced collection is
+   named `docs` and entries carry `title` / `description` frontmatter. Match to
+   `src/content.config.ts`.
+3. `docHtmlPath` / `docSourceUrl` assume the URL scheme `/docs/<id>/` and the
+   upstream layout `apache/magpie/docs/<id>.md`. If the sync rewrites names
+   (readme → index), adjust the two regexes.
+4. Add `<JsonLd markdownPath={...} />` to the base layout `<head>`; on docs
+   pages pass `docMarkdownPath(entry)`, elsewhere omit it.
+5. If a 404 page already exists, merge the link list into it and delete mine.
+6. No workflow change: Astro copies `public/` into `dist/`, and build.yml
+   pushes `dist/` to `publish`. Confirm `.htaccess` is in `dist/` after a build.
+7. **Check the catch-all.** The scan got a 200 for `/apache/magpie`. Search
+   `.htaccess`, `.asf.yaml` and the workflow for `RewriteRule`, `FallbackResource`
+   or a copy of `index.html` to `404.html`; whichever is producing the 200 has to
+   go, or `ErrorDocument` never fires.
+
+## Verify after `npm run build` (or on staging)
+
+```sh
+curl -s -o /dev/null -w "%{http_code}\n" https://magpie.apache.org/no-such-page      # 404
+curl -sI https://magpie.apache.org/docs/modes.md | grep -i '^content-type'            # text/markdown
+curl -sI -H "Accept: text/markdown" https://magpie.apache.org/docs/modes/ | grep -i '^content-type'  # see note
+curl -s https://magpie.apache.org/llms.txt | head -20
+curl -s https://magpie.apache.org/docs/modes/ | grep -o '<link rel="alternate"[^>]*>'
+grep -c '"@type":"SoftwareSourceCode"' dist/index.html                                # 1
+```
+
+Then rescan: `npx is-agentic magpie.apache.org`.
+
+## Deliberately not done
+
+- No Accept-header negotiation, same as Iggy: a static export on ASF
+  infrastructure has no server-side control worth relying on, and the `.md`
+  sibling needs none. If the curl above already returns text/markdown, the
+  server is negotiating on its own (MultiViews); then, and only then, add
+  `Header merge Vary "Accept"` to `.htaccess` so caches key the variants apart.
+
+- No OpenAPI spec and no JSON error bodies: there is no API. `llms.txt` states
+  that, which is what removes the scanner's phantom API surface.
+- No ASF postal address in the JSON-LD (dropped from Iggy for the same reason).
diff --git a/public/.htaccess b/public/.htaccess
index 6d39488..05c74ff 100644
--- a/public/.htaccess
+++ b/public/.htaccess
@@ -24,6 +24,16 @@
 # with its own bare "Not Found" body and the page's recovery links are lost.
 ErrorDocument 404 /404.html
 
+# Root discovery files and the .md twins carry the Markdown media type. The
+# twins are alternates of the HTML page, not pages, so they stay out of
+# search results.
 <Files "llms.txt">
   Header set Content-Type "text/markdown; charset=utf-8"
 </Files>
+<Files "llms-full.txt">
+  Header set Content-Type "text/markdown; charset=utf-8"
+</Files>
+<FilesMatch "\.md$">
+  Header set Content-Type "text/markdown; charset=utf-8"
+  Header set X-Robots-Tag "noindex"
+</FilesMatch>
diff --git a/src/components/JsonLd.astro b/src/components/JsonLd.astro
index 4b00a77..fd8d421 100644
--- a/src/components/JsonLd.astro
+++ b/src/components/JsonLd.astro
@@ -19,10 +19,16 @@
  */
 
 // Organization / WebSite / SoftwareSourceCode structured data for the <head>.
-// Drop into BaseLayout.astro's <head> once.
+// Drop into BaseLayout.astro's <head> once; DocsLayout.astro passes
+// `markdownPath` through so the <link rel="alternate"> lands on docs pages.
 
 import { SITE_URL, SITE_NAME, SITE_DESCRIPTION, SOURCE_REPO, DEV_LIST } from "../lib/site";
 
+interface Props {
+  /** Path of this page's Markdown twin, e.g. "/docs/modes.md". Omit on non-docs pages. */
+  markdownPath?: string;
+}
+const { markdownPath } = Astro.props;
 
 const org = {
   "@type": "Organization",
@@ -67,4 +73,7 @@ const json = JSON.stringify(payload).replace(/</g, "\\u003c");
 ---
 
 <script type="application/ld+json" set:html={json} />
+{markdownPath && (
+  <link rel="alternate" type="text/markdown" href={`${SITE_URL}${markdownPath}`} />
+)}
 <link rel="llms-txt" href={`${SITE_URL}/llms.txt`} />