| 1 | import assert from "node:assert/strict"; |
| 2 | import { readFileSync, existsSync } from "node:fs"; |
| 3 | import { test } from "node:test"; |
| 4 | import { FEATURES } from "../apps/web/app/lib/features.ts"; |
| 5 | |
| 6 | const read = (path) => readFileSync(new URL(`../${path}`, import.meta.url), "utf8").replaceAll("\r\n", "\n"); |
| 7 | |
| 8 | test("llms.txt follows the heading and file-list format and preserves the detailed guide", () => { |
| 9 | const index = read("apps/web/public/llms.txt"); |
| 10 | assert.match(index, /^# g1t\n\n> /); |
| 11 | for (const section of index.split(/^## .+$/m).slice(1)) { |
| 12 | const lines = section.trim().split("\n").filter(Boolean); |
| 13 | assert.ok(lines.length > 0); |
| 14 | for (const line of lines) assert.match(line, /^- \[[^\]]+\]\(https:\/\/[^)]+\)(: .+)?$/); |
| 15 | } |
| 16 | for (const url of ["/features", "/developers", "/agents.md", "/openapi.json", "/reference/versioning.md", "/settings/tokens"]) assert.ok(index.includes(url), url); |
| 17 | assert.match(read("apps/web/public/agents.md"), /## Set someone up/); |
| 18 | }); |
| 19 | |
| 20 | test("agent guides name work items, not the issues they replaced", () => { |
| 21 | // Issues became workspace work items: no /repos/…/issues routes, no |
| 22 | // issues:* scopes and no `issue` MCP tool remain (see |
| 23 | // apps/docs/src/content/docs/about/issues-became-work-items.md). |
| 24 | const stale = [ |
| 25 | [/\/issues\b/, "an /issues route"], |
| 26 | [/\bissues:(read|write)\b/, "an issues:* scope"], |
| 27 | [/`issue`|"name": *"issue"/, "an MCP tool named issue"], |
| 28 | [/\bkeep_issue_open\b|\bis:issue\b|\bg1t issue\b|\bg1t work context\b/, "a renamed field, qualifier or command"], |
| 29 | ]; |
| 30 | for (const path of ["apps/web/public/agents.md", "apps/web/public/llms.txt", "apps/web/public/install/claude.sh"]) { |
| 31 | const text = read(path); |
| 32 | for (const [pattern, what] of stale) { |
| 33 | const found = text.match(pattern); |
| 34 | assert.equal(found, null, `${path} still names ${what}: ${found?.[0]}`); |
| 35 | } |
| 36 | } |
| 37 | const guide = read("apps/web/public/agents.md"); |
| 38 | for (const text of ["`work_item`", "/workspaces/{workspace}/work", "work:write", "is:item", "keep_item_open", "/-/work/sessions"]) assert.ok(guide.includes(text), text); |
| 39 | }); |
| 40 | |
| 41 | test("features is routed, discoverable on desktop and mobile, and has real documentation targets", () => { |
| 42 | assert.match(read("apps/web/app/routes.ts"), /route\("features", "routes\/features.tsx"\)/); |
| 43 | assert.match(read("apps/web/app/routes/sitemap-xml.ts"), /"\/features"/); |
| 44 | const header = read("apps/web/app/components/public-header.tsx"); |
| 45 | assert.match(header, /<HeaderLink to="\/features">Features<\/HeaderLink>/); |
| 46 | assert.match(header, /<Link to="\/features">/); |
| 47 | assert.equal(new Set(FEATURES.map((f) => f.id)).size, FEATURES.length); |
| 48 | for (const feature of FEATURES) for (const link of feature.links) { |
| 49 | if (!link.href.startsWith("https://docs.g1t.sh/")) continue; |
| 50 | const slug = new URL(link.href).pathname.replace(/\/$/, ""); |
| 51 | assert.ok(["md", "mdx"].some((ext) => existsSync(new URL(`../apps/docs/src/content/docs${slug}.${ext}`, import.meta.url))), link.href); |
| 52 | } |
| 53 | }); |
| 54 | |
| 55 | test("built developer pages have branded headings, canonical URLs and Markdown counterparts", () => { |
| 56 | for (const slug of ["reference/developers", "reference/versioning", "reference/api", "reference/mcp", "guides/authentication"]) { |
| 57 | const html = read(`apps/docs/dist/${slug}/index.html`); |
| 58 | assert.match(html, /<title>g1t /); |
| 59 | assert.match(html, /<h1[^>]*[^]*?>g1t /); |
| 60 | assert.ok(html.includes(`https://docs.g1t.sh/${slug}/`)); |
| 61 | assert.match(read(`apps/docs/dist/${slug}.md`), /^# g1t /); |
| 62 | } |
| 63 | const policy = read("apps/docs/dist/reference/versioning.md"); |
| 64 | for (const text of ["X-G1t-Api-Version", "Deprecation", "Sunset", 'rel="deprecation"', "No REST version is currently deprecated"]) assert.ok(policy.includes(text), text); |
| 65 | }); |