diff --git a/README.md b/README.md index 3a08048..ba8f2d2 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,19 @@ this repository’s `docs/` folder during the build; generated API content is in `lib/api-docs.ts` and navigation in `lib/docs-catalog.ts`. No engine checkout or sibling directory is required. +## Reading with AI agents + +Agents can start at `/llms.txt`, which lists every published page with a link to +its plain Markdown at `/markdown/.md`. `/llms-full.txt` contains the +complete documentation, including generated API references and every language +example. These files are generated from the same catalog and sources as the +HTML pages on each build, so they stay synchronized automatically. + +HTML pages advertise their Markdown alternative and the agent index in their +head metadata, and navigation links directly to the agent index. +`/robots.txt` permits crawling and points to `/sitemap.xml`. +Canonical URLs use `lib/site-url.ts`; update it when hosting a fork elsewhere. + Markdown was imported from `Engine/docs` at engine commit `3db0126b`. This audit checked behavior against engine commit `017db99b7025eeda96acf21967664153a2df0795`. See [maintenance](/docs/open-source/docs/maintenance) for route/link checks and source synchronization. diff --git a/app/docs/[[...slug]]/page.tsx b/app/docs/[[...slug]]/page.tsx index d9f6d91..3ac8053 100644 --- a/app/docs/[[...slug]]/page.tsx +++ b/app/docs/[[...slug]]/page.tsx @@ -4,6 +4,7 @@ import { DocsLanding } from "@/components/docs-landing"; import { DocsShell } from "@/components/docs-shell"; import { allDocs, docAliases, routeFor } from "@/lib/docs-catalog"; import { findDoc, readDoc } from "@/lib/docs"; +import { markdownRouteFor } from "@/lib/site-url"; export const dynamicParams = false; @@ -15,7 +16,7 @@ export async function generateMetadata({ params }: PageProps<"/docs/[[...slug]]" const { slug } = await params; if (!slug?.length) return { title: "Welcome · Rustic Engine Docs", description: "Start building with Rustic Engine or contribute to the documentation website." }; const doc = findDoc(slug); - return doc ? { title: `${doc.title} · Rustic Engine Docs`, description: doc.description, alternates: { canonical: routeFor(doc.slug) } } : {}; + return doc ? { title: `${doc.title} · Rustic Engine Docs`, description: doc.description, alternates: { canonical: routeFor(doc.slug), types: { "text/markdown": markdownRouteFor(doc.slug), "text/plain": "/llms.txt" } } } : {}; } export default async function DocumentationPage({ params }: PageProps<"/docs/[[...slug]]">) { diff --git a/app/layout.tsx b/app/layout.tsx index ca312db..fe6775a 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,6 +1,7 @@ import type { Metadata } from "next"; import { IBM_Plex_Mono, Manrope } from "next/font/google"; import { SiteFrame } from "@/components/site-frame"; +import { siteUrl } from "@/lib/site-url"; import "./globals.css"; const manrope = Manrope({ subsets: ["latin"], variable: "--font-sans" }); @@ -11,9 +12,11 @@ const plexMono = IBM_Plex_Mono({ }); export const metadata: Metadata = { + metadataBase: new URL(siteUrl), title: "Rustic Engine Docs", description: "Guides and API reference for building games with Rustic Engine.", icons: { icon: "/favicon.svg" }, + alternates: { types: { "text/plain": "/llms.txt" } }, }; export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) { diff --git a/app/llms-full.txt/route.ts b/app/llms-full.txt/route.ts new file mode 100644 index 0000000..fa4ed2d --- /dev/null +++ b/app/llms-full.txt/route.ts @@ -0,0 +1,7 @@ +import { buildAgentFullText } from "@/lib/agent-docs"; + +export const dynamic = "force-static"; + +export async function GET() { + return new Response(await buildAgentFullText(), { headers: { "Content-Type": "text/plain; charset=utf-8" } }); +} diff --git a/app/llms.txt/route.ts b/app/llms.txt/route.ts new file mode 100644 index 0000000..0a38932 --- /dev/null +++ b/app/llms.txt/route.ts @@ -0,0 +1,7 @@ +import { buildAgentIndex } from "@/lib/agent-docs"; + +export const dynamic = "force-static"; + +export function GET() { + return new Response(buildAgentIndex(), { headers: { "Content-Type": "text/plain; charset=utf-8" } }); +} diff --git a/app/markdown/[...slug]/route.ts b/app/markdown/[...slug]/route.ts new file mode 100644 index 0000000..61d1ebe --- /dev/null +++ b/app/markdown/[...slug]/route.ts @@ -0,0 +1,16 @@ +import { allDocs } from "@/lib/docs-catalog"; +import { readAgentDoc } from "@/lib/agent-docs"; + +export const dynamic = "force-static"; +export const dynamicParams = false; + +export function generateStaticParams() { + return allDocs.map((doc) => ({ slug: `${doc.slug}.md`.split("/") })); +} + +export async function GET(_request: Request, { params }: { params: Promise<{ slug: string[] }> }) { + const { slug } = await params; + const doc = allDocs.find((entry) => `${entry.slug}.md` === slug.join("/")); + if (!doc) return new Response("Documentation not found", { status: 404 }); + return new Response(await readAgentDoc(doc), { headers: { "Content-Type": "text/markdown; charset=utf-8" } }); +} diff --git a/app/robots.ts b/app/robots.ts new file mode 100644 index 0000000..37dad72 --- /dev/null +++ b/app/robots.ts @@ -0,0 +1,8 @@ +import type { MetadataRoute } from "next"; +import { siteUrl } from "@/lib/site-url"; + +export const dynamic = "force-static"; + +export default function robots(): MetadataRoute.Robots { + return { rules: { userAgent: "*", allow: "/" }, sitemap: `${siteUrl}/sitemap.xml` }; +} diff --git a/app/sitemap.ts b/app/sitemap.ts new file mode 100644 index 0000000..35b29fb --- /dev/null +++ b/app/sitemap.ts @@ -0,0 +1,9 @@ +import type { MetadataRoute } from "next"; +import { allDocs, routeFor } from "@/lib/docs-catalog"; +import { siteUrl } from "@/lib/site-url"; + +export const dynamic = "force-static"; + +export default function sitemap(): MetadataRoute.Sitemap { + return ["/", "/docs", ...allDocs.map((doc) => routeFor(doc.slug))].map((pathname) => ({ url: `${siteUrl}${pathname}` })); +} diff --git a/components/site-frame.tsx b/components/site-frame.tsx index c0b6c48..dc40f4c 100644 --- a/components/site-frame.tsx +++ b/components/site-frame.tsx @@ -80,6 +80,7 @@ export function SiteFrame({ children }: { children: ReactNode }) {
Documentation