Files
gbrain/test/build-llms.test.ts
Garry Tan 7f156c8873 feat: v0.15.0 llms.txt + llms-full.txt + AGENTS.md (#294)
* feat: llms.txt + llms-full.txt + AGENTS.md (v0.15.0)

Ship three new public artifacts at the repo root so agents that aren't
Claude Code can discover GBrain documentation cleanly:

- AGENTS.md — ~45-line install + operating protocol for non-Claude agents
  (Codex, Cursor, OpenClaw, Aider). Covers install, read order, trust
  boundary, config/debug/migration pointers, fork regeneration. Uses
  relative links so it survives fork/rename.
- llms.txt — llmstxt.org-spec index (H1 + blockquote + Core entry points /
  Configuration / Debugging / Migrations / Philosophy / Optional H2s).
- llms-full.txt — same index with core docs inlined for single-fetch
  ingestion. ~225KB, well under the 600KB FULL_SIZE_BUDGET.

Generator-driven via scripts/build-llms.ts + scripts/llms-config.ts.
LLMS_REPO_BASE env var makes it fork-friendly. bun run build:llms
regenerates both outputs deterministically.

test/build-llms.test.ts has 7 cases: paths resolve on disk, generator
idempotent, llms.txt spec shape, checked-in files match generator output
(drift guard), content contract (RESOLVER / AGENTS / INSTALL referenced),
AGENTS mirrors README + INSTALL_FOR_AGENTS install path, llms-full.txt
under size budget.

Leverage point per Codex review: README.md + INSTALL_FOR_AGENTS.md
install prompts now tell agents to fetch AGENTS.md first. Without this,
the new files were invisible.

Drive-by fix: INSTALL_FOR_AGENTS.md:136 had `git pull origin main` while
the repo's default branch is master (origin/HEAD -> master). Corrected.

Plan + reviews: /plan-eng-review CLEARED, /codex adversarial review
found 15 issues — 7 folded in directly, 3 user tension decisions, 5
stayed as NOT-in-scope with reasoning.

Version bumps to 0.15.0 (new public-artifact feature surface per Step 12
of /ship feature-signal heuristic).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: normalize VERSION to 3-digit to match master

master uses 3-digit semver (0.14.2); my earlier /ship bumped VERSION to
the 4-digit gstack format (0.15.0.0). Revert to 0.15.0 to match
package.json (already 3-digit) and master's convention.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 11:51:32 -07:00

99 lines
4.1 KiB
TypeScript

import { describe, test, expect } from "bun:test";
import { existsSync, readFileSync, statSync } from "node:fs";
import { join } from "node:path";
import { buildLlmsFiles } from "../scripts/build-llms";
import { SECTIONS, FULL_SIZE_BUDGET } from "../scripts/llms-config";
const repoRoot = join(import.meta.dir, "..");
describe("build-llms generator", () => {
// Case 1 — every config path resolves on disk. Catches rename-induced 404s.
test("every configured path exists on disk", () => {
for (const section of SECTIONS) {
for (const entry of section.entries) {
const abs = join(repoRoot, entry.path);
expect(existsSync(abs), `missing: ${entry.path}`).toBe(true);
const st = statSync(abs);
if (entry.path.endsWith("/")) {
expect(st.isDirectory(), `${entry.path} should be a directory`).toBe(true);
} else {
expect(st.isFile(), `${entry.path} should be a file`).toBe(true);
}
}
}
});
// Case 2 — generator is idempotent. Run twice in-memory, compare byte-for-byte.
test("generator output is deterministic across runs", () => {
const first = buildLlmsFiles();
const second = buildLlmsFiles();
expect(second.llmsTxt).toBe(first.llmsTxt);
expect(second.llmsFullTxt).toBe(first.llmsFullTxt);
});
// Case 3 — llms.txt spec shape per llmstxt.org: H1 + blockquote + required H2s.
test("llms.txt follows llmstxt.org spec shape", () => {
const { llmsTxt } = buildLlmsFiles();
const lines = llmsTxt.split("\n");
expect(lines[0], "first line must be H1").toBe("# GBrain");
// Blockquote summary on line 2 or 3 (spec allows blank line after H1).
const hasEarlyBlockquote =
lines.slice(1, 4).some((line) => line.startsWith("> "));
expect(hasEarlyBlockquote, "needs > blockquote summary near top").toBe(true);
// Required H2 sections for GBrain's user need (config/debug/migration).
expect(llmsTxt).toContain("## Core entry points");
expect(llmsTxt).toContain("## Configuration");
expect(llmsTxt).toContain("## Debugging");
expect(llmsTxt).toContain("## Migrations");
});
// Case 4 — checked-in files match generator output. Catches "forgot to rerun
// generator" before ship. If this fails in CI, run `bun run build:llms` and
// commit the result.
test("committed llms.txt + llms-full.txt match current generator output", () => {
const { llmsTxt, llmsFullTxt } = buildLlmsFiles();
const committedLlms = readFileSync(join(repoRoot, "llms.txt"), "utf8");
const committedFull = readFileSync(join(repoRoot, "llms-full.txt"), "utf8");
const helpMsg =
"Run `bun run build:llms` and commit the updated output before shipping.";
expect(committedLlms, helpMsg).toBe(llmsTxt);
expect(committedFull, helpMsg).toBe(llmsFullTxt);
});
// Case 5 — content contract. Prevents silent removal of critical sections or
// entries from llms-config.ts. Catches "someone deleted the Debugging section."
test("content contract: llms.txt references required entry points", () => {
const { llmsTxt } = buildLlmsFiles();
expect(llmsTxt).toContain("skills/RESOLVER.md");
expect(llmsTxt).toContain("INSTALL_FOR_AGENTS.md");
expect(llmsTxt).toContain("AGENTS.md");
expect(llmsTxt).toContain("CLAUDE.md");
});
test("content contract: AGENTS.md mirrors README + INSTALL_FOR_AGENTS install path", () => {
const agents = readFileSync(join(repoRoot, "AGENTS.md"), "utf8");
expect(agents).toContain("CLAUDE.md");
expect(agents).toContain("skills/RESOLVER.md");
expect(agents).toContain("INSTALL_FOR_AGENTS.md");
expect(agents).toContain("llms.txt");
// Trust boundary is the non-obvious security concept agents need up-front.
expect(agents.toLowerCase()).toContain("trust boundary");
});
test("llms-full.txt stays within size budget", () => {
const { llmsFullTxt } = buildLlmsFiles();
const bytes = Buffer.byteLength(llmsFullTxt, "utf8");
expect(
bytes,
`llms-full.txt is ${bytes} bytes (budget ${FULL_SIZE_BUDGET}). Add includeInFull: false to large entries.`,
).toBeLessThan(FULL_SIZE_BUDGET);
});
});