diff --git a/doc/plans/2026-09-10-agent-chat.md b/doc/plans/2026-09-10-agent-chat.md index 46b441bb5a..f4ee9d451f 100644 --- a/doc/plans/2026-09-10-agent-chat.md +++ b/doc/plans/2026-09-10-agent-chat.md @@ -143,3 +143,21 @@ these fixes. Focused live checks additionally passed legacy Codex project reuse and repository handoff, legacy Claude plan revision/acceptance/handoff, and native Claude planning, Stop/reset/resume, fresh sessions, and multiple repositories. Final campaign results and broad verification are recorded below when complete. + +The next full campaign (`34640536416`) reached 18/24 passing cells and identified +three additional issues. Execution prompts now include the task's persisted plan +and selected revision on both fresh and resumed runs; a plan handed off without a +description therefore still reaches its executor. Native durable redaction keeps +explicit literal/exact acceptance identifiers while continuing to redact actual +credential-shaped values. Recovery for an older conversation generation or an +already answered turn cannot block a reset or healthy idle chat. Regression tests +also preserve recovery for current unanswered turns and unprepared failures. + +Fixture assertions now accept concrete clarification requests without requiring a +question mark. They check the approved revision and final execution output rather +than rejecting an old draft quoted in plan revision history. Restart verification +opens the canonical chat route after reconnecting, preserving the continuity and +no-unsolicited-run checks. Stable inconsistent idle states fail promptly instead +of waiting through a long timeout and hiding a product race behind a paid retry. +Focused native Claude project reuse and multiple-repository handoffs, and legacy +Claude multiple-repository handoff, passed on their first attempts with these fixes. diff --git a/server/src/__tests__/cli-invocation-safety.test.ts b/server/src/__tests__/cli-invocation-safety.test.ts index 3186fa48d2..3b7e15df40 100644 --- a/server/src/__tests__/cli-invocation-safety.test.ts +++ b/server/src/__tests__/cli-invocation-safety.test.ts @@ -1,4 +1,5 @@ -import { readdirSync, readFileSync } from "node:fs"; +import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import os from "node:os"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { describe, expect, it } from "vitest"; @@ -335,7 +336,14 @@ const SKIP_DIRS = new Set([ "tmp", ]); -const SKIP_PATH_PREFIXES = ["doc/logs/", "doc/plans/", "scripts/"]; +const SKIP_PATH_PREFIXES = [ + "doc/logs/", + "doc/plans/", + "scripts/", + // Generated paid-run transcripts contain historical copies of instructions, + // including escaped warning examples; they are not authored guidance. + "tests/runner-e2e/results/", +]; const SCAN_EXTENSIONS = new Set([ ".md", @@ -355,7 +363,7 @@ function isTestFile(relPath: string): boolean { ); } -function listGuidanceFiles(): string[] { +function listGuidanceFiles(root = repoRoot): string[] { const found: string[] = []; function walk(absDir: string, relDir: string): void { @@ -364,6 +372,7 @@ function listGuidanceFiles(): string[] { const relPath = relDir ? `${relDir}/${entry.name}` : entry.name; if (entry.isDirectory()) { if (SKIP_DIRS.has(entry.name)) continue; + if (SKIP_PATH_PREFIXES.some((prefix) => `${relPath}/`.startsWith(prefix))) continue; walk(path.join(absDir, entry.name), relPath); continue; } @@ -374,7 +383,7 @@ function listGuidanceFiles(): string[] { } } - walk(repoRoot, ""); + walk(root, ""); return found; } @@ -466,6 +475,28 @@ function scanForBrokenExecForm(): string[] { } describe("paperclipai CLI invocation safety", () => { + it("excludes generated runner evidence while preserving authored runner guidance", () => { + const root = mkdtempSync(path.join(os.tmpdir(), "paperclip-cli-guidance-")); + const sourcePaths = [ + "doc/CLI.md", + "tests/runner-e2e/README.md", + "tests/runner-e2e/catalog.ts", + ]; + try { + for (const relPath of [ + ...sourcePaths, + "tests/runner-e2e/results/campaign/attempt-1/snapshots/api-state.json", + ]) { + const absPath = path.join(root, relPath); + mkdirSync(path.dirname(absPath), { recursive: true }); + writeFileSync(absPath, "fixture"); + } + expect(listGuidanceFiles(root).sort()).toEqual(sourcePaths.sort()); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); + it("allows only exact-allowlist pnpm paperclipai commands on every guidance surface", () => { const offenders = scanForOffenders(); expect(