From e8f389beb6911229c477b8e73d022dbf5341f727 Mon Sep 17 00:00:00 2001 From: Devin Foley Date: Fri, 2 Oct 2026 16:52:55 -0700 Subject: [PATCH] test: exercise authored-file discovery through a real Git index Co-Authored-By: Paperclip --- .../__tests__/cli-invocation-safety.test.ts | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/server/src/__tests__/cli-invocation-safety.test.ts b/server/src/__tests__/cli-invocation-safety.test.ts index abd8a68fe3b..e465e7a57e9 100644 --- a/server/src/__tests__/cli-invocation-safety.test.ts +++ b/server/src/__tests__/cli-invocation-safety.test.ts @@ -370,7 +370,11 @@ function listGuidanceFiles(rootDir = repoRoot): string[] { const found: string[] = []; // Include tracked files and new authored files, while respecting gitignored // local settings. A tracked file remains scanned even if an ignore matches. - const authored = rootDir === repoRoot ? new Set(execFileSync("git", ["ls-files", "--cached", "--others", "--exclude-standard", "-z"], { cwd: rootDir, encoding: "utf8" }).split("\0")) : null; + const authored = new Set(execFileSync( + "git", + ["ls-files", "--cached", "--others", "--exclude-standard", "-z"], + { cwd: rootDir, encoding: "utf8" }, + ).split("\0")); function walk(absDir: string, relDir: string): void { for (const entry of readdirSync(absDir, { withFileTypes: true })) { @@ -386,7 +390,7 @@ function listGuidanceFiles(rootDir = repoRoot): string[] { if (!SCAN_EXTENSIONS.has(path.extname(entry.name))) continue; if (isTestFile(relPath)) continue; if (SKIP_PATH_PREFIXES.some((prefix) => relPath.startsWith(prefix))) continue; - if (!authored || authored.has(relPath)) found.push(relPath); + if (authored.has(relPath)) found.push(relPath); } } @@ -490,6 +494,7 @@ describe("paperclipai CLI invocation safety", () => { "tests/runner-e2e/catalog.ts", ]; try { + execFileSync("git", ["init", "--quiet"], { cwd: root }); for (const relPath of [ ...sourcePaths, "tests/runner-e2e/results/campaign/attempt-1/snapshots/api-state.json", @@ -507,6 +512,7 @@ describe("paperclipai CLI invocation safety", () => { it("excludes root runtime recordings but still scans unsafe docs and source guidance", () => { const fixtureRoot = mkdtempSync(path.join(os.tmpdir(), "paperclip-cli-guidance-")); try { + execFileSync("git", ["init", "--quiet"], { cwd: fixtureRoot }); const fixtures = [ "doc/CLI.md", "src/guidance.ts", @@ -532,6 +538,26 @@ describe("paperclipai CLI invocation safety", () => { } }); + it("scans tracked and ordinary untracked guidance but excludes ignored untracked files", () => { + const root = mkdtempSync(path.join(os.tmpdir(), "paperclip-cli-guidance-")); + try { + execFileSync("git", ["init", "--quiet"], { cwd: root }); + writeFileSync(path.join(root, ".gitignore"), "ignored.md\ntracked.md\n"); + for (const name of ["ignored.md", "untracked.md", "tracked.md"]) { + writeFileSync(path.join(root, name), "pnpm paperclipai issue get issue-1\n"); + } + execFileSync("git", ["add", "--force", "tracked.md"], { cwd: root }); + + const files = listGuidanceFiles(root).sort(); + expect(files).toEqual(["tracked.md", "untracked.md"]); + for (const name of files) { + expect(scanText(name, readFileSync(path.join(root, name), "utf8"))).toHaveLength(1); + } + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); + it("allows only exact-allowlist pnpm paperclipai commands on every guidance surface", () => { const offenders = scanForOffenders(); expect(