From c935f1a7cb2d9d85fa064094ac1ba6c6f8a3e18c Mon Sep 17 00:00:00 2001 From: Scout Date: Sat, 1 Aug 2026 12:27:55 -0500 Subject: [PATCH 1/2] test: guard shipped drift claims in docs Add a narrow docs/reality contract for shipped Phase 1 drift checks. The guard reports the exact file and line for stale planned or not-yet-implemented claims while allowing the planned Phase 2 coverage-gap work. Agent-Actor: scout Agent-Run-Id: anvil-cycle-001-2026-08-01T17-18Z --- scripts/docs-reality-contract.test.ts | 68 ++++++++++++++++++++++++++ scripts/docs-reality-contract.ts | 69 +++++++++++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 scripts/docs-reality-contract.test.ts create mode 100644 scripts/docs-reality-contract.ts diff --git a/scripts/docs-reality-contract.test.ts b/scripts/docs-reality-contract.test.ts new file mode 100644 index 0000000..853b8af --- /dev/null +++ b/scripts/docs-reality-contract.test.ts @@ -0,0 +1,68 @@ +import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join, resolve } from "node:path"; +import { expect, test } from "bun:test"; +import { findDocsRealityDriftFailures } from "./docs-reality-contract.ts"; + +const REPO_ROOT = resolve(import.meta.dir, ".."); + +test("shipped drift phases are not described as planned in current docs", () => { + expect(findDocsRealityDriftFailures(REPO_ROOT)).toEqual([]); +}); + +test("reports the exact stale claim and source line", () => { + const fixtureRoot = mkdtempSync(join(tmpdir(), "anvil-doc-reality-")); + mkdirSync(join(fixtureRoot, "docs"), { recursive: true }); + writeFileSync( + join(fixtureRoot, "README.md"), + "Drift detection: command drift detection is planned.\n", + ); + + try { + expect(findDocsRealityDriftFailures(fixtureRoot)).toEqual([ + { + file: "README.md", + line: 1, + claim: "Drift detection: command drift detection is planned.", + }, + ]); + } finally { + rmSync(fixtureRoot, { recursive: true, force: true }); + } +}); + +test("does not mistake a planned future phase for a stale shipped claim", () => { + const fixtureRoot = mkdtempSync(join(tmpdir(), "anvil-doc-reality-")); + mkdirSync(join(fixtureRoot, "docs"), { recursive: true }); + writeFileSync( + join(fixtureRoot, "README.md"), + "Drift detection ships path, glob, date, and command checks; coverage gap analysis is planned.\n", + ); + + try { + expect(findDocsRealityDriftFailures(fixtureRoot)).toEqual([]); + } finally { + rmSync(fixtureRoot, { recursive: true, force: true }); + } +}); + +test("catches a stale shipped phase table entry", () => { + const fixtureRoot = mkdtempSync(join(tmpdir(), "anvil-doc-reality-")); + mkdirSync(join(fixtureRoot, "docs"), { recursive: true }); + writeFileSync( + join(fixtureRoot, "docs/drift-detection-design.md"), + "| Phase 1c | Command drift | Planned |\n", + ); + + try { + expect(findDocsRealityDriftFailures(fixtureRoot)).toEqual([ + { + file: "docs/drift-detection-design.md", + line: 1, + claim: "| Phase 1c | Command drift | Planned |", + }, + ]); + } finally { + rmSync(fixtureRoot, { recursive: true, force: true }); + } +}); diff --git a/scripts/docs-reality-contract.ts b/scripts/docs-reality-contract.ts new file mode 100644 index 0000000..429b83c --- /dev/null +++ b/scripts/docs-reality-contract.ts @@ -0,0 +1,69 @@ +import { existsSync, readFileSync } from "node:fs"; +import { relative, resolve } from "node:path"; + +export type DocsRealityDriftFailure = { + file: string; + line: number; + claim: string; +}; + +type ClaimPattern = { + file: string; + pattern: RegExp; +}; + +// These patterns are deliberately narrow: they guard the shipped Phase 1 +// drift checks without attempting general semantic documentation inference. +const STALE_SHIPPED_CLAIM_PATTERNS: ClaimPattern[] = [ + { + file: "README.md", + pattern: + /\b(?:path|glob|command|date) drift(?: detection)?\b[^;|.\n]{0,40}\b(?:planned|not yet implemented)\b/i, + }, + { + file: "README.md", + pattern: + /\bfull drift detection\b[^;|.\n]{0,40}\b(?:planned|not yet implemented)\b/i, + }, + { + file: "docs/drift-detection-design.md", + pattern: + /\b(?:path|glob|command|date) drift\b\s*[-:|]\s*(?:planned|not yet implemented)\b/i, + }, + { + file: "docs/drift-detection-design.md", + pattern: /\bStatus:\s*Design only\s*[—-]\s*not yet implemented\b/i, + }, +]; + +function lineNumberAt(lines: string[], index: number): number { + return index + 1; +} + +export function findDocsRealityDriftFailures( + projectRoot: string, +): DocsRealityDriftFailure[] { + const failures: DocsRealityDriftFailure[] = []; + + for (const { file, pattern } of STALE_SHIPPED_CLAIM_PATTERNS) { + const absolutePath = resolve(projectRoot, file); + if (!existsSync(absolutePath)) { + continue; + } + + const lines = readFileSync(absolutePath, "utf8").split("\n"); + lines.forEach((claim, index) => { + if (!pattern.test(claim)) { + return; + } + + failures.push({ + file: relative(projectRoot, absolutePath), + line: lineNumberAt(lines, index), + claim: claim.trim(), + }); + }); + } + + return failures; +} From e4109daf4683be9c15cd57087e34ba78f2b1b29d Mon Sep 17 00:00:00 2001 From: Scout Date: Sat, 1 Aug 2026 12:33:28 -0500 Subject: [PATCH 2/2] ci: provide GitHub token to self-audit tests The self-audit proof reruns PR signal checks through gh during CI. Wire the read-only Actions token into the test step so the checked-in clean proof remains deterministic in pull requests. Agent-Actor: scout Agent-Run-Id: anvil-cycle-001-2026-08-01T17-18Z --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d1d92fb..ee5c16e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,6 +39,8 @@ jobs: - name: Run test suite with coverage thresholds (flaky tests block merge; no quarantine) run: bun test + env: + GH_TOKEN: ${{ github.token }} - name: Run drift detection run: bun run scripts/drift-detect.ts .