Files
2026-07-13 12:20:01 +08:00

329 lines
12 KiB
TypeScript

import { beforeAll, describe, expect, test } from "bun:test"
import { spawnSync } from "node:child_process"
import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs"
import { tmpdir } from "node:os"
import path from "node:path"
const SKILL_DIRS = [
path.join(__dirname, "../skills/ce-compound"),
path.join(__dirname, "../skills/ce-compound-refresh"),
] as const
function scriptPath(skillDir: string): string {
return path.join(skillDir, "scripts/validate-doc-claims.py")
}
function runValidator(
skillDir: string,
docPath: string,
): { code: number; stdout: string; stderr: string } {
const result = spawnSync("python3", [scriptPath(skillDir), docPath], {
encoding: "utf8",
})
return {
code: result.status ?? -1,
stdout: result.stdout ?? "",
stderr: result.stderr ?? "",
}
}
function sh(cwd: string, cmd: string, args: string[]): string {
const result = spawnSync(cmd, args, { cwd, encoding: "utf8" })
if (result.status !== 0) {
throw new Error(
`${cmd} ${args.join(" ")} failed in ${cwd}: ${result.stderr}`,
)
}
return (result.stdout ?? "").trim()
}
const FRONTMATTER = `---
title: "Sample doc"
date: 2026-07-07
module: ce-compound
problem_type: best_practice
component: tooling
severity: low
---
`
// Scratch git repo shared by all repo-dependent tests. Layout:
// src/real-file.ts committed and reachable from both HEAD and the
// simulated origin/main; sharedSha = that commit; localOnlySha = a later
// commit reachable from HEAD only; upstreamOnlySha = a commit only the
// simulated origin/main can reach.
let repo: string
let sharedSha: string
let localOnlySha: string
let upstreamOnlySha: string
beforeAll(() => {
repo = mkdtempSync(path.join(tmpdir(), "doc-claims-repo-"))
sh(repo, "git", ["init", "-b", "main"])
sh(repo, "git", ["config", "user.email", "test@example.com"])
sh(repo, "git", ["config", "user.name", "Test"])
mkdirSync(path.join(repo, "src"), { recursive: true })
mkdirSync(path.join(repo, "docs/solutions/workflow"), { recursive: true })
mkdirSync(path.join(repo, "docs/solutions/best-practices"), {
recursive: true,
})
writeFileSync(path.join(repo, "src/real-file.ts"), "export const x = 1\n")
writeFileSync(
path.join(repo, "docs/solutions/best-practices/linked-target.md"),
"# linked target\n",
)
writeFileSync(
path.join(repo, "docs/solutions/workflow/existing-doc.md"),
"# existing\n",
)
sh(repo, "git", ["add", "-A"])
sh(repo, "git", ["commit", "-m", "base"])
sharedSha = sh(repo, "git", ["rev-parse", "HEAD"])
// upstream-only commit: branch from base, commit, point origin/main at it
sh(repo, "git", ["checkout", "-b", "upstream-work"])
writeFileSync(path.join(repo, "src/upstream-only.ts"), "export const u = 1\n")
sh(repo, "git", ["add", "-A"])
sh(repo, "git", ["commit", "-m", "upstream only"])
upstreamOnlySha = sh(repo, "git", ["rev-parse", "HEAD"])
sh(repo, "git", ["update-ref", "refs/remotes/origin/main", upstreamOnlySha])
sh(repo, "git", ["checkout", "main"])
// local-only commit: on main, after origin/main was pinned
writeFileSync(path.join(repo, "src/local-only.ts"), "export const l = 1\n")
sh(repo, "git", ["add", "-A"])
sh(repo, "git", ["commit", "-m", "local only"])
localOnlySha = sh(repo, "git", ["rev-parse", "HEAD"])
})
let docCounter = 0
function writeRepoDoc(body: string): string {
const filePath = path.join(
repo,
`docs/solutions/workflow/doc-${docCounter++}.md`,
)
writeFileSync(filePath, FRONTMATTER + "\n" + body, "utf8")
return filePath
}
function writeBareDoc(body: string): string {
const dir = mkdtempSync(path.join(tmpdir(), "doc-claims-bare-"))
const filePath = path.join(dir, "doc.md")
writeFileSync(filePath, FRONTMATTER + "\n" + body, "utf8")
return filePath
}
describe("validate-doc-claims script", () => {
// Run every test against both skill copies — they must behave
// identically since AGENTS.md requires duplication, not sharing.
for (const skillDir of SKILL_DIRS) {
const skillName = path.basename(skillDir)
describe(`in ${skillName}`, () => {
test("passes a clean doc citing an existing path and a shared SHA", () => {
const docPath = writeRepoDoc(
"The fix lives in `src/real-file.ts` and landed in commit " +
`${sharedSha.slice(0, 12)}.\n` +
"See [the existing doc](existing-doc.md) for background.\n",
)
const result = runValidator(skillDir, docPath)
expect(result.code).toBe(0)
expect(result.stdout).toContain("OK:")
expect(result.stdout).not.toContain("FLAG")
})
test("flags a cited path that exists nowhere", () => {
const docPath = writeRepoDoc(
"The handler is `src/does-not-exist.ts` in the tree.\n",
)
const result = runValidator(skillDir, docPath)
expect(result.code).toBe(1)
expect(result.stdout).toContain("FLAG path `src/does-not-exist.ts`")
expect(result.stdout).toContain("not found")
})
test("classifies a path that only exists upstream as stale-checkout", () => {
const docPath = writeRepoDoc(
"See `src/upstream-only.ts` for the new helper.\n",
)
const result = runValidator(skillDir, docPath)
expect(result.code).toBe(1)
expect(result.stdout).toContain("FLAG path `src/upstream-only.ts`")
expect(result.stdout).toContain("exists at origin/main")
})
test("skips slash-delimited identifiers that are not path-shaped", () => {
const docPath = writeRepoDoc(
"Branched as `feat/foo` off `origin/main`, drafted by " +
"`anthropic/claude-sonnet-4-6`.\n",
)
const result = runValidator(skillDir, docPath)
expect(result.code).toBe(0)
expect(result.stdout).not.toContain("FLAG")
})
test("flags a missing extension-less token under a real repo directory", () => {
const docPath = writeRepoDoc(
"The helper is `src/nonexistent-helper` in the tree.\n",
)
const result = runValidator(skillDir, docPath)
expect(result.code).toBe(1)
expect(result.stdout).toContain("FLAG path `src/nonexistent-helper`")
expect(result.stdout).toContain("not found")
})
test("ignores placeholder and URL-like tokens", () => {
const docPath = writeRepoDoc(
"Use `path/to/your-file.ts`, `docs/<category>/file.md`, and " +
"`https://example.com/a/b` as needed.\n",
)
const result = runValidator(skillDir, docPath)
expect(result.code).toBe(0)
})
test("flags a fabricated SHA", () => {
const docPath = writeRepoDoc(
"Fixed in commit 0123456789abcdef0123.\n",
)
const result = runValidator(skillDir, docPath)
expect(result.code).toBe(1)
expect(result.stdout).toContain("FLAG sha 0123456789abcdef0123")
expect(result.stdout).toContain("does not resolve")
})
test("flags a HEAD-only SHA as rewritable on merge", () => {
const docPath = writeRepoDoc(
`Landed in ${localOnlySha.slice(0, 12)} on this branch.\n`,
)
const result = runValidator(skillDir, docPath)
expect(result.code).toBe(1)
expect(result.stdout).toContain(`FLAG sha ${localOnlySha.slice(0, 12)}`)
expect(result.stdout).toContain("local-only commit")
})
test("flags an upstream-only SHA as predating-the-merge (the stale-branch bug)", () => {
const docPath = writeRepoDoc(
`Fixed by ${upstreamOnlySha.slice(0, 12)} which merged upstream.\n`,
)
const result = runValidator(skillDir, docPath)
expect(result.code).toBe(1)
expect(result.stdout).toContain(
`FLAG sha ${upstreamOnlySha.slice(0, 12)}`,
)
expect(result.stdout).toContain("predates the merge")
})
test("does not treat dates or decimal ids as SHAs", () => {
const docPath = writeRepoDoc(
"On 20260707 we bumped build 123456789 without incident.\n",
)
const result = runValidator(skillDir, docPath)
expect(result.code).toBe(0)
})
test("flags dangling learning-number scaffold", () => {
const docPath = writeRepoDoc(
"This complements Learnings 3, 4, 5 from the same batch.\n",
)
const result = runValidator(skillDir, docPath)
expect(result.code).toBe(1)
expect(result.stdout).toContain("FLAG scaffold")
expect(result.stdout).toContain("Learnings 3")
})
test("flags unresolved placeholder tokens", () => {
const docPath = writeRepoDoc("Cross-reference: {{DOC:3}}.\n")
const result = runValidator(skillDir, docPath)
expect(result.code).toBe(1)
expect(result.stdout).toContain("FLAG scaffold")
expect(result.stdout).toContain("{{DOC:3}}")
})
test("resolves a `../` code-formatted link label from the doc's location", () => {
const docPath = writeRepoDoc(
"See [`../best-practices/linked-target.md`]" +
"(../best-practices/linked-target.md) for the pattern.\n",
)
const result = runValidator(skillDir, docPath)
expect(result.code).toBe(0)
expect(result.stdout).not.toContain("FLAG")
})
test("flags a `../` cited path whose doc-relative target is missing", () => {
const docPath = writeRepoDoc(
"See `../best-practices/does-not-exist.md` for background.\n",
)
const result = runValidator(skillDir, docPath)
expect(result.code).toBe(1)
expect(result.stdout).toContain(
"FLAG path `../best-practices/does-not-exist.md`",
)
expect(result.stdout).toContain("not found")
})
test("skips a `../` token that escapes the repository", () => {
// Four levels up from docs/solutions/workflow lands outside the repo.
const docPath = writeRepoDoc(
"The temp copy was `../../../../outside-repo.md` during the run.\n",
)
const result = runValidator(skillDir, docPath)
expect(result.code).toBe(0)
expect(result.stdout).not.toContain("FLAG")
})
test("flags a relative markdown link that does not resolve", () => {
const docPath = writeRepoDoc(
"See [the missing doc](../missing/nope.md) for details.\n",
)
const result = runValidator(skillDir, docPath)
expect(result.code).toBe(1)
expect(result.stdout).toContain("FLAG link (../missing/nope.md)")
})
test("reports staleness INFO when HEAD is behind the upstream ref", () => {
// HEAD (main) does not contain upstream-only work, so rev-list
// HEAD..origin/main is non-zero in the scratch repo.
const docPath = writeRepoDoc("Nothing cited here.\n")
const result = runValidator(skillDir, docPath)
expect(result.stdout).toContain("INFO: worktree is")
expect(result.stdout).toContain("behind origin/main")
})
test("still checks scaffold and links outside a git repository", () => {
const docPath = writeBareDoc(
"This continues Learning 2 — see [gone](./gone.md).\n",
)
const result = runValidator(skillDir, docPath)
expect(result.code).toBe(1)
expect(result.stdout).toContain("not a git repository")
expect(result.stdout).toContain("FLAG scaffold")
expect(result.stdout).toContain("FLAG link (./gone.md)")
})
test("exits 2 (usage error) on missing file", () => {
const result = runValidator(
skillDir,
"/tmp/this-file-does-not-exist-claims.md",
)
expect(result.code).toBe(2)
expect(result.stderr).toContain("file not found")
})
test("exits 2 (usage error) on missing argument", () => {
const result = spawnSync("python3", [scriptPath(skillDir)], {
encoding: "utf8",
})
expect(result.status).toBe(2)
expect(result.stderr).toContain("usage")
})
})
}
test("script content is identical across skill copies (per AGENTS.md duplication rule)", () => {
const [a, b] = SKILL_DIRS
const aContent = readFileSync(scriptPath(a), "utf8")
const bContent = readFileSync(scriptPath(b), "utf8")
expect(aContent).toBe(bContent)
})
})