diff --git a/PROJECT_STATUS.md b/PROJECT_STATUS.md index 0fbd7e6..0f57f72 100644 --- a/PROJECT_STATUS.md +++ b/PROJECT_STATUS.md @@ -18,6 +18,8 @@ maintenance-first support. ## Timeline +- **2026-07-31:** Kept the public sitemap HTML-only and added source-level + sitemap/catalog/Markdown parity coverage. - **2026-07-31:** Added locally verified Open Graph/Twitter image metadata and SoftwareApplication structured data to the public landing layout; production deployment remains separate. @@ -36,6 +38,7 @@ maintenance-first support. AI-assisted reading. - Account-backed and device-local capture paths. - Owned editorial product changelog at `/changelog`. +- HTML-only public sitemap with cataloged Markdown mirrors for agent discovery. ## Work queue diff --git a/docs/product/surfaces.md b/docs/product/surfaces.md index 94cbc6c..c6b4d3c 100644 --- a/docs/product/surfaces.md +++ b/docs/product/surfaces.md @@ -32,7 +32,7 @@ Served from `public/` and the `agent-edge.mjs` handler in `src/worker.ts` | `index.md` | `/index.md` | Product brief in Markdown (no JS) | | `api/ai` | `/api/ai` | JSON catalog of public surfaces | | `robots.txt` | `/robots.txt` | Allows all + lists agent surfaces | -| `sitemap.xml` | `/sitemap.xml` | Public URL inventory | +| `sitemap.xml` | `/sitemap.xml` | Canonical public HTML page inventory | | IndexNow key | `/fa7259e2e0d942f1a1267b344a75a143.txt` | Bing/Yandex URL submission key | The agent-edge payload is generated into `src/agent-edge.mjs` by the fleet diff --git a/public/sitemap.xml b/public/sitemap.xml index 89838c7..393f8b2 100644 --- a/public/sitemap.xml +++ b/public/sitemap.xml @@ -1,7 +1,5 @@ https://read.significanthobbies.com/ - https://read.significanthobbies.com/index.md - https://read.significanthobbies.com/llms.txt https://read.significanthobbies.com/login diff --git a/src/agent-surfaces.test.ts b/src/agent-surfaces.test.ts new file mode 100644 index 0000000..b55ecfe --- /dev/null +++ b/src/agent-surfaces.test.ts @@ -0,0 +1,58 @@ +import { existsSync, readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { describe, expect, it } from 'vitest'; + +const publicPath = (name: string) => resolve(process.cwd(), 'public', name); +const readPublicFile = (name: string) => readFileSync(publicPath(name), 'utf8'); + +const sitemap = readPublicFile('sitemap.xml'); +const catalog = JSON.parse(readPublicFile('api-ai.json')) as { + url: string; + llms: string; + llmsFull: string; + sitemap: string; + robots: string; + surfaces: Array<{ id: string; url: string; md: string }>; +}; + +const sitemapUrls = [...sitemap.matchAll(/([^<]+)<\/loc>/g)].map((match) => match[1]); + +describe('public agent surface parity', () => { + it('keeps the sitemap limited to cataloged HTML pages', () => { + expect(sitemapUrls.sort()).toEqual(catalog.surfaces.map((surface) => surface.url).sort()); + + for (const url of sitemapUrls) { + const pathname = new URL(url).pathname; + expect(pathname).not.toMatch(/\.(?:md|txt|json|xml)$/); + expect(pathname).not.toBe('/api/ai'); + } + }); + + it('keeps one checked-in Markdown mirror for every cataloged HTML page', () => { + for (const surface of catalog.surfaces) { + const markdownUrl = new URL(surface.md); + expect(markdownUrl.origin).toBe(catalog.url); + expect(markdownUrl.pathname).toMatch(/\.md$/); + expect(existsSync(publicPath(markdownUrl.pathname.slice(1)))).toBe(true); + } + }); + + it('advertises machine resources outside the HTML sitemap', () => { + const discoverySources = [ + readPublicFile('robots.txt'), + readPublicFile('llms.txt'), + readPublicFile('api-ai.json'), + ].join('\n'); + + for (const resource of [ + '/llms.txt', + '/llms-full.txt', + '/index.md', + '/api/ai', + '/robots.txt', + '/sitemap.xml', + ]) { + expect(discoverySources).toContain(resource); + } + }); +});