mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 11:13:21 +00:00
* docs: refuse literal tool counts in living prose docs:check now refuses a count of ten or more tools in README, AGENTS, CONTRIBUTING, docs/ (except dated releases, specs, verification and ADR records), the authored skill and the public website. The catalog owns that number and every hand-written copy goes stale when a tool is added. The three current statements (240, 172 and 254 tools, all measured on earlier builds) now refer to the catalog of the measured build instead. * fix(docs): exclude generated pages from prose checks * fix(docs): rerun the tool-count check when any scanned prose changes The docs:check:built cache inputs and the CI docs lane did not cover AGENTS.md, CONTRIBUTING.md, every .agents/skills tree or website/public, so an edit there could reuse a cached pass or skip the docs job. The scanner now owns one isLivingProsePath predicate; the CI plan selects the docs lane with it, and a test binds the turbo inputs to the scanned roots.
156 lines
5.8 KiB
TypeScript
156 lines
5.8 KiB
TypeScript
import { copyFile, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
import { dirname, join, matchesGlob } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
|
|
import {
|
|
LIVING_PROSE_ROOTS,
|
|
documentationFactIssues,
|
|
isLivingProsePath,
|
|
literalToolCountIssues,
|
|
} from "../../../scripts/lib/docs-facts.mjs";
|
|
import type { ProductCatalog } from "../../../scripts/lib/product-catalog.mjs";
|
|
|
|
const root = fileURLToPath(new URL("../../..", import.meta.url));
|
|
const temporaryRoots: string[] = [];
|
|
|
|
const README_FACT_FIXTURE_PATHS = [
|
|
"README.md",
|
|
"docs/readme/README_zh.md",
|
|
"docs/readme/README_ja.md",
|
|
"docs/readme/README_ko.md",
|
|
"docs/readme/README_ar.md",
|
|
"website/locales/languages.json",
|
|
"docs/getting-started/installation.md",
|
|
".agents/skills/reverse-engineer-anything/references/connection-and-recovery.md",
|
|
"AGENTS.md",
|
|
".github/pull_request_template.md",
|
|
] as const;
|
|
|
|
const README_LINK_DRIFT_MUTATIONS = [
|
|
[
|
|
"(../getting-started/installation.md#supported-agents)",
|
|
"(../getting-started/installation.md#missing-agents)",
|
|
],
|
|
["https://rea.tools/zh-CN/guides/", "https://rea.tools/zh-CN/missing/"],
|
|
["https://rea.tools/zh-CN/guides/", "https://rea.tools/zh-TW/guides/"],
|
|
[
|
|
"https://rea.tools/zh-CN/guides/",
|
|
"https://rea.tools/zh-CN/guides/?other=1",
|
|
],
|
|
[
|
|
'src="../assets/rea-hopper-analysis.png"',
|
|
'src="../assets/missing-hopper.png"',
|
|
],
|
|
['href="https://discord.gg/GkcryMnJDM"', 'href="https://discord.gg/missing"'],
|
|
] as const;
|
|
|
|
const seedReadmeFactFixture = async (directory: string) => {
|
|
for (const path of README_FACT_FIXTURE_PATHS) {
|
|
const destination = join(directory, path);
|
|
await mkdir(dirname(destination), { recursive: true });
|
|
await copyFile(join(root, path), destination);
|
|
}
|
|
await mkdir(join(directory, "skills/reverse-engineer-anything"), {
|
|
recursive: true,
|
|
});
|
|
};
|
|
|
|
afterEach(async () => {
|
|
await Promise.all(
|
|
temporaryRoots
|
|
.splice(0)
|
|
.map((path) => rm(path, { recursive: true, force: true })),
|
|
);
|
|
});
|
|
|
|
describe("literal tool counts", () => {
|
|
it("refuses catalog-size restatements in living prose only", async () => {
|
|
const directory = await createTestTempDirectory("rea-tool-count-facts-");
|
|
temporaryRoots.push(directory);
|
|
const write = async (path: string, content: string) => {
|
|
await mkdir(dirname(join(directory, path)), { recursive: true });
|
|
await writeFile(join(directory, path), content);
|
|
};
|
|
await write("README.md", "REA exposes 255 tools.\n");
|
|
await write(
|
|
"docs/guides/a.md",
|
|
"One line.\nThe client saw all 1,024 MCP tools.\nIt registered 0 tools.\n",
|
|
);
|
|
await write("website/public/index.html", "<p>240 public tools</p>");
|
|
await write("docs/guides/rea.md", "The client saw 207 REA tools.\n");
|
|
await write(
|
|
"docs/.vitepress/dist/verification/run.html",
|
|
"<p>234 tools</p>",
|
|
);
|
|
await write("docs/verification/run.md", "The catalog had 240 tools.\n");
|
|
await write("docs/releases/1.0.md", "Adds 12 tools.\n");
|
|
await write("docs/guides/b.md", "Six initial tools, then 34 loaded.\n");
|
|
await expect(literalToolCountIssues(directory)).resolves.toEqual([
|
|
'README.md:1: literal tool count "255 tools"; link the generated catalog instead',
|
|
'docs/guides/a.md:2: literal tool count "1,024 MCP tools"; link the generated catalog instead',
|
|
'docs/guides/rea.md:1: literal tool count "207 REA tools"; link the generated catalog instead',
|
|
'website/public/index.html:1: literal tool count "240 public tools"; link the generated catalog instead',
|
|
]);
|
|
});
|
|
|
|
it("invalidates the cached documentation check for every scanned root", async () => {
|
|
const turbo = JSON.parse(
|
|
await readFile(join(root, "turbo.json"), "utf8"),
|
|
) as {
|
|
tasks: Record<string, { inputs: readonly (string | object)[] }>;
|
|
};
|
|
const globs = turbo.tasks["docs:check:built"]!.inputs.filter(
|
|
(input): input is string => typeof input === "string",
|
|
);
|
|
const cached = (path: string) =>
|
|
globs.some((glob) => !glob.startsWith("!") && matchesGlob(path, glob)) &&
|
|
!globs.some(
|
|
(glob) => glob.startsWith("!") && matchesGlob(path, glob.slice(1)),
|
|
);
|
|
const samples = LIVING_PROSE_ROOTS.flatMap((start) =>
|
|
isLivingProsePath(start)
|
|
? [start]
|
|
: [`${start}/nested/page.md`, `${start}/nested/page.html`],
|
|
);
|
|
expect(samples.every(isLivingProsePath)).toBe(true);
|
|
expect(samples.filter((path) => !cached(path))).toEqual([]);
|
|
});
|
|
|
|
it("finds none in the repository's living prose", async () => {
|
|
await expect(literalToolCountIssues(root)).resolves.toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe("translated README facts", () => {
|
|
it("keeps translated readmes linked to canonical setup and tool details", async () => {
|
|
const directory = await createTestTempDirectory("rea-readme-facts-");
|
|
temporaryRoots.push(directory);
|
|
await seedReadmeFactFixture(directory);
|
|
const translatedPath = join(directory, "docs/readme/README_zh.md");
|
|
const translated = await readFile(translatedPath, "utf8");
|
|
const catalog: ProductCatalog = JSON.parse(
|
|
await readFile(join(root, "docs/public/product-catalog.json"), "utf8"),
|
|
);
|
|
await expect(documentationFactIssues(directory, catalog)).resolves.toEqual(
|
|
[],
|
|
);
|
|
for (const [original, changed] of README_LINK_DRIFT_MUTATIONS) {
|
|
expect(translated).toContain(original);
|
|
await writeFile(
|
|
translatedPath,
|
|
translated.replace(original, changed),
|
|
"utf8",
|
|
);
|
|
await expect(
|
|
documentationFactIssues(directory, catalog),
|
|
).resolves.toEqual([
|
|
"docs/readme/README_zh.md: documentation links differ from README.md",
|
|
]);
|
|
}
|
|
});
|
|
});
|