diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 80482db72..4c69756eb 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -172,6 +172,7 @@ - Fixed the `ask` tool's "Other (type your own)" free-text editor (prompt-style `HookEditorComponent`) ignoring Ctrl+Q and Ctrl+Enter, so Windows Terminal users who learned the `app.message.followUp` chord from the main editor (#1903 / fixed by #1905) got zero feedback on submit. The hook-style and main-editor surfaces honored `matchesAppFollowUp`; the prompt-style handler did not, leaving plain Enter as the sole submit path and Ctrl+Enter falling through to Editor as a newline (silently swallowed by WT). `#handlePromptStyleInput` now checks `matchesAppFollowUp` first — mirroring `#handleHookStyleInput` — and the hint reads `enter or ctrl+q submit` so the chord is discoverable. ([#3353](https://github.com/can1357/oh-my-pi/issues/3353)) - Fixed the TUI freezing when a tool approval prompt fires while `/settings` (or the Extensions/Agents dashboard) is open. The fullscreen overlay's close handler restored focus to the editor it had captured at open time, but `ExtensionUiController` had since swapped the editor out of the editor slot for the approval prompt — so on exit the visible prompt sat unreachable while keystrokes routed to the now-unmounted editor (no Enter/Up/Down/Esc response, only Ctrl+C escaped). `SelectorController` now restores focus to whatever currently owns the editor slot via a `focusActiveEditorArea()` helper, applied to settings, extensions dashboard, and agents dashboard close paths. ([#3349](https://github.com/can1357/oh-my-pi/issues/3349)) - Fixed `/settings` coercing enum/text values to display strings before handing them to the TUI list, preventing YAML numeric enum values from reaching native truncation ([#3338](https://github.com/can1357/oh-my-pi/issues/3338)). +- Migrated 203 test files from `fs.rm`/`fs.rmSync` to `removeWithRetries`/`removeSyncWithRetries` (356 call sites) to reduce EBUSY test failures on Windows. `removeWithRetries` is now exported from `@oh-my-pi/pi-utils` - Fixed all extension loading silently failing on the cross-compiled `omp-darwin-arm64` release binary (downloaded directly or via a Homebrew tap wrapper) because `__computeBunfsPackageRoot` mis-handled `import.meta.dir = "//root/omp-darwin-arm64"`. Bun 1.3.14 reports `/` for the compiled entry's `import.meta.dir`, but the pre-fix function joined `metaDir + "packages"` and produced `/root/omp-darwin-arm64/packages` — the binary basename was baked into every bunfs path, so the TypeBox/legacy-pi shims and every `@oh-my-pi/pi-*` package-root override failed `existsSync` validation and `resolveCanonicalPiSpecifier` fell through to a bunfs `Bun.resolveSync` that also could not find the module. The function now detects the bunfs-root + binary-basename shape (`path.basename(path.dirname(metaDir)) === "root"`) and strips the trailing binary segment by slicing the original `metaDir`; the production bunfs shim join path also preserves Bun's bunfs-native `//root` / `B:\~BUN\root` prefix that `path.join` would otherwise collapse. ([#3329](https://github.com/can1357/oh-my-pi/issues/3329)) - Fixed llama.cpp discovery to prefer per-model `/v1/models` `meta.n_ctx`/`meta.n_ctx_train` values, refresh selected models after lazy load, and bypass fresh-cache reuse so server restarts update context windows. ([#3310](https://github.com/can1357/oh-my-pi/issues/3310)) - Fixed `task.maxConcurrency: 0` serializing subagent spawns instead of running them unbounded. The settings UI labels `0` as "Unlimited", but the session-scoped spawn `Semaphore` clamped `max` via `Math.max(1, max)`, so the second subagent body in a batch always waited for the first to release the seat. The constructor now treats `max <= 0` (and any non-finite input) as unbounded via `Number.POSITIVE_INFINITY`, matching the eval `parallel()`/`pipeline()` worker-pool semantics ([#3305](https://github.com/can1357/oh-my-pi/issues/3305)). @@ -196,6 +197,7 @@ - Reused shared TUI mouse-routing helpers across fullscreen overlay selectors. + ## [16.1.16] - 2026-06-23 ### Breaking Changes diff --git a/packages/coding-agent/test/acp-builtins.test.ts b/packages/coding-agent/test/acp-builtins.test.ts index 272a7b139..970b62429 100644 --- a/packages/coding-agent/test/acp-builtins.test.ts +++ b/packages/coding-agent/test/acp-builtins.test.ts @@ -12,6 +12,7 @@ import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import type { AgentSession } from "@oh-my-pi/pi-coding-agent/session/agent-session"; import type { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; import { executeAcpBuiltinSlashCommand } from "@oh-my-pi/pi-coding-agent/slash-commands/acp-builtins"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; interface FakeAcpBuiltinSession { fastMode: boolean; @@ -577,7 +578,7 @@ describe("wave 3 commands", () => { expect(output[0]).toBe(`Wrote todos to ${target}`); expect(await fs.readFile(target, "utf8")).toBe("# Work\n- [ ] Ship it\n"); } finally { - await fs.rm(tempRoot, { recursive: true, force: true }); + await removeWithRetries(tempRoot); } }); @@ -594,7 +595,7 @@ describe("wave 3 commands", () => { expect(output[0]).toBe(`Wrote todos to ${target}`); expect(await fs.readFile(target, "utf8")).toBe("# Work\n- [ ] Ship it\n"); } finally { - await fs.rm(tempRoot, { recursive: true, force: true }); + await removeWithRetries(tempRoot); } }); @@ -613,7 +614,7 @@ describe("wave 3 commands", () => { { name: "Imported", tasks: [{ content: "Active task", status: "in_progress" }] }, ]); } finally { - await fs.rm(tempRoot, { recursive: true, force: true }); + await removeWithRetries(tempRoot); } }); @@ -633,7 +634,7 @@ describe("wave 3 commands", () => { { name: "Default", tasks: [{ content: "From cwd", status: "in_progress" }] }, ]); } finally { - await fs.rm(tempRoot, { recursive: true, force: true }); + await removeWithRetries(tempRoot); } }); @@ -651,7 +652,7 @@ describe("wave 3 commands", () => { expect(output[0]).toContain(`Could not parse ${target}:`); expect(session._todoPhases).toEqual([]); } finally { - await fs.rm(tempRoot, { recursive: true, force: true }); + await removeWithRetries(tempRoot); } }); diff --git a/packages/coding-agent/test/advisor/transcript-recorder.test.ts b/packages/coding-agent/test/advisor/transcript-recorder.test.ts index 23849266b..7beed7168 100644 --- a/packages/coding-agent/test/advisor/transcript-recorder.test.ts +++ b/packages/coding-agent/test/advisor/transcript-recorder.test.ts @@ -20,6 +20,7 @@ import { ADVISOR_TRANSCRIPT_FILENAME, AdvisorTranscriptRecorder, } from "@oh-my-pi/pi-coding-agent/advisor/transcript-recorder"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; interface AdvisorEntry { type?: string; @@ -38,7 +39,7 @@ async function withTempDir(fn: (dir: string) => Promise): Promise { try { return await fn(dir); } finally { - await fs.rm(dir, { recursive: true, force: true }); + await removeWithRetries(dir); } } diff --git a/packages/coding-agent/test/agent-dashboard-create-editor.test.ts b/packages/coding-agent/test/agent-dashboard-create-editor.test.ts index d23745a9a..090b703e3 100644 --- a/packages/coding-agent/test/agent-dashboard-create-editor.test.ts +++ b/packages/coding-agent/test/agent-dashboard-create-editor.test.ts @@ -6,6 +6,7 @@ import type { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { AgentDashboard } from "@oh-my-pi/pi-coding-agent/modes/components/agent-dashboard"; import { initTheme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme"; import * as discovery from "@oh-my-pi/pi-coding-agent/task/discovery"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; const ANSI_PATTERN = /\x1b\[[0-?]*[ -/]*[@-~]/g; const tempDirs: string[] = []; @@ -55,7 +56,7 @@ function stubStdoutGeometry(cols: number): { setRows(n: number): void; restore() afterEach(async () => { vi.restoreAllMocks(); - await Promise.all(tempDirs.splice(0).map(dir => fs.rm(dir, { recursive: true, force: true }))); + await Promise.all(tempDirs.splice(0).map(dir => removeWithRetries(dir))); }); describe("AgentDashboard create editor", () => { diff --git a/packages/coding-agent/test/agent-hub-advisor-scroll.test.ts b/packages/coding-agent/test/agent-hub-advisor-scroll.test.ts index ea00815e8..0dcec8535 100644 --- a/packages/coding-agent/test/agent-hub-advisor-scroll.test.ts +++ b/packages/coding-agent/test/agent-hub-advisor-scroll.test.ts @@ -16,6 +16,7 @@ import { AgentTranscriptViewer } from "@oh-my-pi/pi-coding-agent/modes/component import { initTheme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme"; import { AgentRegistry } from "@oh-my-pi/pi-coding-agent/registry/agent-registry"; import { CURRENT_SESSION_VERSION } from "@oh-my-pi/pi-coding-agent/session/session-entries"; +import { removeSyncWithRetries } from "@oh-my-pi/pi-utils"; const TS = new Date().toISOString(); @@ -111,7 +112,7 @@ function withViewer(fn: (viewer: AgentTranscriptViewer) => void): void { try { fn(makeViewer(file)); } finally { - fs.rmSync(dir, { recursive: true, force: true }); + removeSyncWithRetries(dir); } } @@ -180,7 +181,7 @@ describe("AgentTranscriptViewer", () => { viewer.handleInput("g"); expect(body()).toContain("PROMPTMARKER"); - fs.rmSync(file); + removeSyncWithRetries(file); // Poll until the viewer's own poll timer re-stats and clears (deadline-bounded). const deadline = Date.now() + 5000; while (body().includes("PROMPTMARKER") && Date.now() < deadline) { @@ -189,7 +190,7 @@ describe("AgentTranscriptViewer", () => { expect(body()).not.toContain("PROMPTMARKER"); } finally { viewer.dispose(); - fs.rmSync(dir, { recursive: true, force: true }); + removeSyncWithRetries(dir); } }); diff --git a/packages/coding-agent/test/agent-session-bash-detach.test.ts b/packages/coding-agent/test/agent-session-bash-detach.test.ts index 9ab432cc5..36b91ad17 100644 --- a/packages/coding-agent/test/agent-session-bash-detach.test.ts +++ b/packages/coding-agent/test/agent-session-bash-detach.test.ts @@ -51,7 +51,7 @@ import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; import { convertToLlm } from "@oh-my-pi/pi-coding-agent/session/messages"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; import { BashTool, type ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; /** Scripted assistant turn that issues a single `bash` tool call. */ function bashCall(command: string, callId: string): MockResponse { @@ -198,7 +198,7 @@ describe("BashTool through AgentSession runs children in their own session (e2e) authStorage?.close(); authStorage = undefined; if (fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true, force: true }); + removeSyncWithRetries(tempDir); } }); diff --git a/packages/coding-agent/test/agent-session-branching.test.ts b/packages/coding-agent/test/agent-session-branching.test.ts index 4cb5c478d..7b915b7e4 100644 --- a/packages/coding-agent/test/agent-session-branching.test.ts +++ b/packages/coding-agent/test/agent-session-branching.test.ts @@ -19,7 +19,7 @@ import { AgentSession } from "@oh-my-pi/pi-coding-agent/session/agent-session"; import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; import { createTools, type ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; import { e2eApiKey } from "./utilities"; describe.skipIf(!e2eApiKey("ANTHROPIC_API_KEY"))("AgentSession branching", () => { @@ -41,7 +41,7 @@ describe.skipIf(!e2eApiKey("ANTHROPIC_API_KEY"))("AgentSession branching", () => authStorage?.close(); authStorage = undefined; if (tempDir && fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true }); + removeSyncWithRetries(tempDir); } }); diff --git a/packages/coding-agent/test/agent-session-compaction.test.ts b/packages/coding-agent/test/agent-session-compaction.test.ts index 0d01d06a1..fbca54705 100644 --- a/packages/coding-agent/test/agent-session-compaction.test.ts +++ b/packages/coding-agent/test/agent-session-compaction.test.ts @@ -19,7 +19,7 @@ import { AgentSession, type AgentSessionEvent } from "@oh-my-pi/pi-coding-agent/ import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; import { createTools, type ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; import { e2eApiKey } from "./utilities"; describe.skipIf(!e2eApiKey("ANTHROPIC_API_KEY"))("AgentSession compaction e2e", () => { @@ -45,7 +45,7 @@ describe.skipIf(!e2eApiKey("ANTHROPIC_API_KEY"))("AgentSession compaction e2e", authStorage?.close(); authStorage = undefined; if (tempDir && fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true }); + removeSyncWithRetries(tempDir); } }); diff --git a/packages/coding-agent/test/agent-session-concurrent.test.ts b/packages/coding-agent/test/agent-session-concurrent.test.ts index 87cf5cb7c..2660f7d84 100644 --- a/packages/coding-agent/test/agent-session-concurrent.test.ts +++ b/packages/coding-agent/test/agent-session-concurrent.test.ts @@ -22,7 +22,7 @@ import { AgentSession } from "@oh-my-pi/pi-coding-agent/session/agent-session"; import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; import { convertToLlm } from "@oh-my-pi/pi-coding-agent/session/messages"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; import { type } from "arktype"; import { createAssistantMessage } from "./helpers/agent-session-setup"; @@ -62,7 +62,7 @@ describe("AgentSession concurrent prompt guard", () => { authStorage.close(); } if (tempDir && fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true }); + removeSyncWithRetries(tempDir); } vi.restoreAllMocks(); AsyncJobManager.resetForTests(); @@ -982,7 +982,7 @@ describe("AgentSession TTSR resume gate", () => { authStorage.close(); } if (tempDir && fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true }); + removeSyncWithRetries(tempDir); } vi.restoreAllMocks(); }); diff --git a/packages/coding-agent/test/agent-session-magic-keywords.test.ts b/packages/coding-agent/test/agent-session-magic-keywords.test.ts index 5f4fd01a3..d0e09a7ae 100644 --- a/packages/coding-agent/test/agent-session-magic-keywords.test.ts +++ b/packages/coding-agent/test/agent-session-magic-keywords.test.ts @@ -12,6 +12,7 @@ import { AgentSession } from "@oh-my-pi/pi-coding-agent/session/agent-session"; import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; import { AUTO_THINKING } from "@oh-my-pi/pi-coding-agent/thinking"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; async function createMagicKeywordSession(root: string): Promise<{ session: AgentSession; @@ -55,7 +56,7 @@ describe("AgentSession magic keyword settings", () => { vi.restoreAllMocks(); if (session) await session.dispose(); authStorage?.close(); - await fs.rm(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 }).catch(() => undefined); + await removeWithRetries(root).catch(() => undefined); session = undefined; authStorage = undefined; }); diff --git a/packages/coding-agent/test/agent-session-mcp-discovery.test.ts b/packages/coding-agent/test/agent-session-mcp-discovery.test.ts index b253ac11e..36c44fffd 100644 --- a/packages/coding-agent/test/agent-session-mcp-discovery.test.ts +++ b/packages/coding-agent/test/agent-session-mcp-discovery.test.ts @@ -16,6 +16,7 @@ import type { CustomTool } from "@oh-my-pi/pi-coding-agent/extensibility/custom- import { AgentSession } from "@oh-my-pi/pi-coding-agent/session/agent-session"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; import type { OutputMeta } from "@oh-my-pi/pi-coding-agent/tools/output-meta"; +import { removeSyncWithRetries } from "@oh-my-pi/pi-utils"; import { type } from "arktype"; function createModel(): Model<"openai-responses"> { @@ -149,7 +150,7 @@ describe("AgentSession MCP discovery", () => { await session.dispose(); } for (const tempDir of tempDirs.splice(0)) { - fs.rmSync(tempDir, { recursive: true, force: true }); + removeSyncWithRetries(tempDir); } }); diff --git a/packages/coding-agent/test/agent-session-openai-responses-replay.test.ts b/packages/coding-agent/test/agent-session-openai-responses-replay.test.ts index b47612a50..607cb3913 100644 --- a/packages/coding-agent/test/agent-session-openai-responses-replay.test.ts +++ b/packages/coding-agent/test/agent-session-openai-responses-replay.test.ts @@ -19,7 +19,7 @@ import type { AgentSession } from "@oh-my-pi/pi-coding-agent/session/agent-sessi import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; import type { SessionEntry, SessionMessageEntry } from "@oh-my-pi/pi-coding-agent/session/session-entries"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; function createUsage(): Usage { return { @@ -272,7 +272,7 @@ describe("AgentSession OpenAI Responses replay boundaries", () => { afterAll(() => { sharedModelRegistry?.authStorage.close(); if (sharedRegistryDir && fs.existsSync(sharedRegistryDir)) { - fs.rmSync(sharedRegistryDir, { recursive: true, force: true }); + removeSyncWithRetries(sharedRegistryDir); } }); @@ -283,7 +283,7 @@ describe("AgentSession OpenAI Responses replay boundaries", () => { while (tempDirs.length > 0) { const tempDir = tempDirs.pop(); if (tempDir && fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true, force: true }); + removeSyncWithRetries(tempDir); } } }); diff --git a/packages/coding-agent/test/agent-session-queued-steer-delivery.test.ts b/packages/coding-agent/test/agent-session-queued-steer-delivery.test.ts index e1d62eb3b..d69ff2573 100644 --- a/packages/coding-agent/test/agent-session-queued-steer-delivery.test.ts +++ b/packages/coding-agent/test/agent-session-queued-steer-delivery.test.ts @@ -24,7 +24,7 @@ import { AgentSession } from "@oh-my-pi/pi-coding-agent/session/agent-session"; import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; import { USER_INTERRUPT_LABEL } from "@oh-my-pi/pi-coding-agent/session/messages"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; const COLLAB_PROMPT_TYPE = "collab-prompt"; @@ -49,7 +49,7 @@ describe("AgentSession queued steer delivery", () => { for (const authStorage of authStorages.splice(0)) { authStorage.close(); } - fs.rmSync(tempDir, { recursive: true, force: true }); + removeSyncWithRetries(tempDir); }); async function createSession(responses: MockResponse[]): Promise { diff --git a/packages/coding-agent/test/agent-session-resolve-reminder.test.ts b/packages/coding-agent/test/agent-session-resolve-reminder.test.ts index bc27100c1..f9ea9529d 100644 --- a/packages/coding-agent/test/agent-session-resolve-reminder.test.ts +++ b/packages/coding-agent/test/agent-session-resolve-reminder.test.ts @@ -13,7 +13,7 @@ import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manage import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; import { queueResolveHandler, ResolveTool } from "@oh-my-pi/pi-coding-agent/tools/resolve"; import { buildNamedToolChoice } from "@oh-my-pi/pi-coding-agent/utils/tool-choice"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; describe("AgentSession resolve reminder", () => { let session: AgentSession; @@ -65,7 +65,7 @@ describe("AgentSession resolve reminder", () => { authStorage?.close(); authStorage = undefined; if (fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true, force: true }); + removeSyncWithRetries(tempDir); } }); diff --git a/packages/coding-agent/test/artifacts-sanitization.test.ts b/packages/coding-agent/test/artifacts-sanitization.test.ts index 7d824c772..f1308e390 100644 --- a/packages/coding-agent/test/artifacts-sanitization.test.ts +++ b/packages/coding-agent/test/artifacts-sanitization.test.ts @@ -1,8 +1,8 @@ import { afterEach, describe, expect, it } from "bun:test"; -import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; import { ArtifactManager } from "@oh-my-pi/pi-coding-agent/session/artifacts"; +import { removeSyncWithRetries } from "@oh-my-pi/pi-utils"; describe("ArtifactManager tool-type sanitization", () => { const dirs: string[] = []; @@ -15,7 +15,7 @@ describe("ArtifactManager tool-type sanitization", () => { afterEach(() => { for (const dir of dirs.splice(0)) { - fs.rmSync(dir, { recursive: true, force: true }); + removeSyncWithRetries(dir); } }); diff --git a/packages/coding-agent/test/auth-broker-import.test.ts b/packages/coding-agent/test/auth-broker-import.test.ts index d5b2dd660..cea621e58 100644 --- a/packages/coding-agent/test/auth-broker-import.test.ts +++ b/packages/coding-agent/test/auth-broker-import.test.ts @@ -5,7 +5,7 @@ import * as path from "node:path"; import { AuthStorage, SqliteAuthCredentialStore } from "@oh-my-pi/pi-ai"; import { type AuthBrokerServerHandle, startAuthBroker } from "@oh-my-pi/pi-ai/auth-broker"; import { runAuthBrokerCommand } from "@oh-my-pi/pi-coding-agent/cli/auth-broker-cli"; -import { getAgentDbPath, setAgentDir } from "@oh-my-pi/pi-utils"; +import { getAgentDbPath, removeWithRetries, setAgentDir } from "@oh-my-pi/pi-utils"; const ORIGINAL_STDOUT_WRITE = process.stdout.write.bind(process.stdout); @@ -34,8 +34,8 @@ describe("auth-broker import (CLIProxyAPI)", () => { process.stdout.write = ORIGINAL_STDOUT_WRITE; if (originalAgentDir === undefined) delete process.env.OMP_AGENT_DIR; else process.env.OMP_AGENT_DIR = originalAgentDir; - await fs.rm(agentDir, { recursive: true, force: true }); - await fs.rm(cliproxyDir, { recursive: true, force: true }); + await removeWithRetries(agentDir); + await removeWithRetries(cliproxyDir); }); async function writeCliProxyJson(name: string, body: Record): Promise { @@ -222,9 +222,9 @@ describe("auth-broker import (broker-routed)", () => { await handle?.close(); brokerStorage?.close(); brokerStore?.close(); - await fs.rm(agentDir, { recursive: true, force: true }); - await fs.rm(brokerAgentDir, { recursive: true, force: true }); - await fs.rm(cliproxyDir, { recursive: true, force: true }); + await removeWithRetries(agentDir); + await removeWithRetries(brokerAgentDir); + await removeWithRetries(cliproxyDir); for (const key of ["OMP_AUTH_BROKER_URL", "OMP_AUTH_BROKER_TOKEN"] as const) { if (savedEnv[key] === undefined) delete process.env[key]; else process.env[key] = savedEnv[key]; diff --git a/packages/coding-agent/test/auth-broker-snapshot-cache.test.ts b/packages/coding-agent/test/auth-broker-snapshot-cache.test.ts index 9e43f52bc..bbf2cbde0 100644 --- a/packages/coding-agent/test/auth-broker-snapshot-cache.test.ts +++ b/packages/coding-agent/test/auth-broker-snapshot-cache.test.ts @@ -11,6 +11,7 @@ import { writeAuthBrokerSnapshotCache, } from "@oh-my-pi/pi-ai/auth-broker"; import { discoverAuthStorage } from "@oh-my-pi/pi-coding-agent/sdk"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; const ENV_KEYS = [ "OMP_AUTH_BROKER_URL", @@ -68,7 +69,7 @@ describe("discoverAuthStorage auth-broker snapshot cache", () => { if (savedEnv[key] === undefined) delete process.env[key]; else process.env[key] = savedEnv[key]; } - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); }); test("boots from a fresh encrypted cache when the broker is down", async () => { diff --git a/packages/coding-agent/test/auth-storage-minimax-login.test.ts b/packages/coding-agent/test/auth-storage-minimax-login.test.ts index 117aad50a..c8231ed42 100644 --- a/packages/coding-agent/test/auth-storage-minimax-login.test.ts +++ b/packages/coding-agent/test/auth-storage-minimax-login.test.ts @@ -4,7 +4,7 @@ import * as os from "node:os"; import * as path from "node:path"; import type { FetchImpl } from "@oh-my-pi/pi-ai"; import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; describe("AuthStorage MiniMax login", () => { let tempDir: string; @@ -37,7 +37,7 @@ describe("AuthStorage MiniMax login", () => { vi.restoreAllMocks(); authStorage.close(); if (tempDir && fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true }); + removeSyncWithRetries(tempDir); } }); diff --git a/packages/coding-agent/test/auth-storage-rotation.test.ts b/packages/coding-agent/test/auth-storage-rotation.test.ts index e0433506c..c1278e9b3 100644 --- a/packages/coding-agent/test/auth-storage-rotation.test.ts +++ b/packages/coding-agent/test/auth-storage-rotation.test.ts @@ -6,7 +6,7 @@ import type { UsageProvider } from "@oh-my-pi/pi-ai"; import * as oauth from "@oh-my-pi/pi-ai/oauth"; import type { OAuthCredentials } from "@oh-my-pi/pi-ai/oauth/types"; import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; describe("AuthStorage account rotation", () => { let tempDir: string; @@ -62,7 +62,7 @@ describe("AuthStorage account rotation", () => { vi.restoreAllMocks(); authStorage.close(); if (tempDir && fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true }); + removeSyncWithRetries(tempDir); } }); diff --git a/packages/coding-agent/test/autolearn-discovery.test.ts b/packages/coding-agent/test/autolearn-discovery.test.ts index b1541a31f..95ecc0170 100644 --- a/packages/coding-agent/test/autolearn-discovery.test.ts +++ b/packages/coding-agent/test/autolearn-discovery.test.ts @@ -5,6 +5,7 @@ import * as path from "node:path"; import { getManagedSkillsDir } from "@oh-my-pi/pi-coding-agent/autolearn/managed-skills"; import "@oh-my-pi/pi-coding-agent/discovery"; import { loadSkills } from "@oh-my-pi/pi-coding-agent/extensibility/skills"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; import { getAgentDir, setAgentDir } from "@oh-my-pi/pi-utils/dirs"; async function writeSkill(dir: string, name: string, description: string): Promise { @@ -37,7 +38,7 @@ describe("managed-skills discovery", () => { afterEach(async () => { spyOn(os, "homedir").mockRestore(); setAgentDir(originalAgentDir); - await fs.rm(tempHome, { recursive: true, force: true }); + await removeWithRetries(tempHome); }); it("surfaces a managed skill tagged with the omp-managed provider", async () => { diff --git a/packages/coding-agent/test/autolearn-learn-local.test.ts b/packages/coding-agent/test/autolearn-learn-local.test.ts index 7501e970e..66adf0142 100644 --- a/packages/coding-agent/test/autolearn-learn-local.test.ts +++ b/packages/coding-agent/test/autolearn-learn-local.test.ts @@ -11,6 +11,7 @@ import { import { localBackend } from "@oh-my-pi/pi-coding-agent/memory-backend/local-backend"; import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; import { LearnTool } from "@oh-my-pi/pi-coding-agent/tools/learn"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; Bun.env.PI_PYTHON_SKIP_CHECK = "1"; @@ -27,7 +28,7 @@ describe("learned-lesson storage (local backend)", () => { learnedFile = path.join(getMemoryRoot(agentDir, projCwd), "learned.md"); }); afterEach(async () => { - await fs.rm(tmp, { recursive: true, force: true }); + await removeWithRetries(tmp); }); it("appends a bullet, normalizes whitespace, and inlines context", async () => { @@ -153,7 +154,7 @@ describe("learned-lesson read-back", () => { agentDir = path.join(tmp, "agent"); }); afterEach(async () => { - await fs.rm(tmp, { recursive: true, force: true }); + await removeWithRetries(tmp); }); it("injects lessons even when no consolidated summary exists", async () => { @@ -228,7 +229,7 @@ describe("learn tool (local backend)", () => { learnedFile = path.join(getMemoryRoot(agentDir, projCwd), "learned.md"); }); afterEach(async () => { - await fs.rm(tmp, { recursive: true, force: true }); + await removeWithRetries(tmp); }); function localSession(): ToolSession { diff --git a/packages/coding-agent/test/autolearn-managed-skills.test.ts b/packages/coding-agent/test/autolearn-managed-skills.test.ts index 7ce8453e4..f2a79e087 100644 --- a/packages/coding-agent/test/autolearn-managed-skills.test.ts +++ b/packages/coding-agent/test/autolearn-managed-skills.test.ts @@ -10,7 +10,7 @@ import { toSkillFrontmatter, writeManagedSkill, } from "@oh-my-pi/pi-coding-agent/autolearn/managed-skills"; -import { parseFrontmatter } from "@oh-my-pi/pi-utils"; +import { parseFrontmatter, removeWithRetries } from "@oh-my-pi/pi-utils"; import { getAgentDir, setAgentDir } from "@oh-my-pi/pi-utils/dirs"; describe("managed-skills primitives", () => { @@ -27,7 +27,7 @@ describe("managed-skills primitives", () => { afterEach(async () => { spyOn(os, "homedir").mockRestore(); setAgentDir(originalAgentDir); - await fs.rm(tempHome, { recursive: true, force: true }); + await removeWithRetries(tempHome); }); const skillFile = (name: string) => path.join(getManagedSkillsDir(), name, "SKILL.md"); @@ -142,7 +142,7 @@ describe("managed-skills primitives", () => { // Nothing was written through the link. expect(await Bun.file(path.join(outside, "SKILL.md")).exists()).toBe(false); } finally { - await fs.rm(outside, { recursive: true, force: true }); + await removeWithRetries(outside); } }); @@ -170,7 +170,7 @@ describe("managed-skills primitives", () => { ).rejects.toThrow(/managed-skills root is a symlink/); expect(await Bun.file(path.join(realRoot, "demo", "SKILL.md")).exists()).toBe(false); } finally { - await fs.rm(realRoot, { recursive: true, force: true }); + await removeWithRetries(realRoot); } }); @@ -203,14 +203,14 @@ describe("managed-skills primitives", () => { const target = path.join(outside, "target.md"); await Bun.write(target, "outside content"); try { - await fs.rm(skillFile("linky")); + await removeWithRetries(skillFile("linky")); await fs.symlink(target, skillFile("linky")); await expect( writeManagedSkill({ action: "update", name: "linky", description: "d", body: "hacked" }), ).rejects.toThrow(/symlink/); expect(await Bun.file(target).text()).toBe("outside content"); } finally { - await fs.rm(outside, { recursive: true, force: true }); + await removeWithRetries(outside); } }); @@ -218,7 +218,7 @@ describe("managed-skills primitives", () => { await writeManagedSkill({ action: "create", name: "hardlink", description: "d", body: "managed content" }); const outside = path.join(tempHome, "authored-hardlink.md"); await Bun.write(outside, "user-authored content"); - await fs.rm(skillFile("hardlink")); + await removeWithRetries(skillFile("hardlink")); await fs.link(outside, skillFile("hardlink")); await expect( @@ -248,7 +248,7 @@ describe("managed-skills primitives", () => { // The symlink target's contents are untouched. expect(await Bun.file(path.join(outside, "keep.txt")).exists()).toBe(true); } finally { - await fs.rm(outside, { recursive: true, force: true }); + await removeWithRetries(outside); } }); }); diff --git a/packages/coding-agent/test/autolearn-tools-gating.test.ts b/packages/coding-agent/test/autolearn-tools-gating.test.ts index c3167b2f0..af16a5fd8 100644 --- a/packages/coding-agent/test/autolearn-tools-gating.test.ts +++ b/packages/coding-agent/test/autolearn-tools-gating.test.ts @@ -10,6 +10,7 @@ import type { MnemopiSessionState } from "@oh-my-pi/pi-coding-agent/mnemopi/stat import { createTools, type ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; import { LearnTool } from "@oh-my-pi/pi-coding-agent/tools/learn"; import { ManageSkillTool } from "@oh-my-pi/pi-coding-agent/tools/manage-skill"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; import { getAgentDir, setAgentDir } from "@oh-my-pi/pi-utils/dirs"; import { type } from "arktype"; @@ -119,7 +120,7 @@ describe("manage_skill execute", () => { spyOn(os, "homedir").mockRestore(); setAgentDir(originalAgentDir); resetActiveSkillsForTests(); - await fs.rm(tempHome, { recursive: true, force: true }); + await removeWithRetries(tempHome); }); const tool = () => ManageSkillTool.createIf(makeSession({ "autolearn.enabled": true }))!; @@ -212,7 +213,7 @@ describe("learn execute", () => { afterEach(async () => { spyOn(os, "homedir").mockRestore(); setAgentDir(originalAgentDir); - await fs.rm(tempHome, { recursive: true, force: true }); + await removeWithRetries(tempHome); }); it("stores a lesson to memory without writing a skill when no skill payload", async () => { diff --git a/packages/coding-agent/test/autoresearch-git.test.ts b/packages/coding-agent/test/autoresearch-git.test.ts index 503c2926e..083da0631 100644 --- a/packages/coding-agent/test/autoresearch-git.test.ts +++ b/packages/coding-agent/test/autoresearch-git.test.ts @@ -2,6 +2,7 @@ import { afterEach, describe, expect, it } from "bun:test"; import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; import { ensureAutoresearchBranch } from "../src/autoresearch/git"; import type { ExtensionAPI } from "../src/extensibility/extensions"; import * as jj from "../src/utils/jj"; @@ -41,7 +42,7 @@ const stubApi = {} as unknown as ExtensionAPI; afterEach(async () => { jj.repo.clearRootCache(); - await Promise.all(tempDirs.splice(0).map(dir => fs.rm(dir, { recursive: true, force: true }))); + await Promise.all(tempDirs.splice(0).map(dir => removeWithRetries(dir))); }); describe("ensureAutoresearchBranch jj guardrails", () => { diff --git a/packages/coding-agent/test/block-images.test.ts b/packages/coding-agent/test/block-images.test.ts index bb8d8319e..24419c9b7 100644 --- a/packages/coding-agent/test/block-images.test.ts +++ b/packages/coding-agent/test/block-images.test.ts @@ -6,6 +6,7 @@ import { processFileArguments } from "@oh-my-pi/pi-coding-agent/cli/file-process import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; import { ReadTool } from "@oh-my-pi/pi-coding-agent/tools/read"; +import { removeSyncWithRetries } from "@oh-my-pi/pi-utils"; // 1x1 red PNG image as base64 (smallest valid PNG) const TINY_PNG_BASE64 = @@ -31,7 +32,7 @@ describe("blockImages setting", () => { }); afterEach(() => { - fs.rmSync(testDir, { recursive: true, force: true }); + removeSyncWithRetries(testDir); }); it("should include image blocks when inspect_image is disabled", async () => { @@ -74,7 +75,7 @@ describe("blockImages setting", () => { }); afterEach(() => { - fs.rmSync(testDir, { recursive: true, force: true }); + removeSyncWithRetries(testDir); }); it("should always process images (filtering happens at convertToLlm layer)", async () => { diff --git a/packages/coding-agent/test/cli/ttsr-cli.test.ts b/packages/coding-agent/test/cli/ttsr-cli.test.ts index 56320a0df..680ab8d72 100644 --- a/packages/coding-agent/test/cli/ttsr-cli.test.ts +++ b/packages/coding-agent/test/cli/ttsr-cli.test.ts @@ -10,7 +10,7 @@ import { type TtsrTestArgs, } from "@oh-my-pi/pi-coding-agent/cli/ttsr-cli"; import { resetSettingsForTest } from "@oh-my-pi/pi-coding-agent/config/settings"; -import { getProjectAgentDir, getProjectDir, setProjectDir } from "@oh-my-pi/pi-utils"; +import { getProjectAgentDir, getProjectDir, removeSyncWithRetries, setProjectDir } from "@oh-my-pi/pi-utils"; let testTmpDir: string; @@ -20,7 +20,7 @@ beforeAll(() => { afterAll(() => { if (testTmpDir && fs.existsSync(testTmpDir)) { - fs.rmSync(testTmpDir, { force: true, recursive: true }); + removeSyncWithRetries(testTmpDir); } }); @@ -92,7 +92,7 @@ async function writeTempSnippet(content: string, ext: string): Promise { function cleanupTmp(): void { if (!testTmpDir || !fs.existsSync(testTmpDir)) return; for (const entry of fs.readdirSync(testTmpDir)) { - fs.rmSync(path.join(testTmpDir, entry), { force: true, recursive: true }); + removeSyncWithRetries(path.join(testTmpDir, entry)); } } diff --git a/packages/coding-agent/test/core/apply-patch-adverserial.test.ts b/packages/coding-agent/test/core/apply-patch-adverserial.test.ts index a29063130..e80141a6a 100644 --- a/packages/coding-agent/test/core/apply-patch-adverserial.test.ts +++ b/packages/coding-agent/test/core/apply-patch-adverserial.test.ts @@ -3,6 +3,7 @@ import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; import { ApplyPatchError, applyPatch } from "@oh-my-pi/pi-coding-agent/edit"; +import { removeSyncWithRetries } from "@oh-my-pi/pi-utils"; describe("applyPatch adversarial inputs", () => { let tempDir: string; @@ -14,7 +15,7 @@ describe("applyPatch adversarial inputs", () => { afterEach(() => { try { - fs.rmSync(tempDir, { recursive: true, force: true }); + removeSyncWithRetries(tempDir); } catch { // Ignore cleanup errors } diff --git a/packages/coding-agent/test/core/block-replace.test.ts b/packages/coding-agent/test/core/block-replace.test.ts index 2dde1ad6b..78b230659 100644 --- a/packages/coding-agent/test/core/block-replace.test.ts +++ b/packages/coding-agent/test/core/block-replace.test.ts @@ -10,6 +10,7 @@ import { getFileSnapshotStore, } from "@oh-my-pi/pi-coding-agent/edit"; import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; beforeAll(async () => { resetSettingsForTest(); @@ -21,7 +22,7 @@ async function withTempDir(fn: (tempDir: string) => Promise): Promise { resetSettingsForTest(); @@ -43,7 +44,7 @@ async function withTempDir(fn: (tempDir: string) => Promise): Promise { @@ -55,7 +56,7 @@ async function withTempDir(fn: (tempDir: string) => Promise): Promise = {}): ToolSession { return { @@ -33,7 +34,7 @@ describe("CursorExecHandlers.grep bridge", () => { }); afterEach(async () => { - await fs.rm(cwd, { recursive: true, force: true }); + await removeWithRetries(cwd); }); it("maps caseInsensitive parameter correctly through the grep bridge", async () => { diff --git a/packages/coding-agent/test/debug/dap-launch-failures.test.ts b/packages/coding-agent/test/debug/dap-launch-failures.test.ts index 5a48eb999..b39f93f86 100644 --- a/packages/coding-agent/test/debug/dap-launch-failures.test.ts +++ b/packages/coding-agent/test/debug/dap-launch-failures.test.ts @@ -14,6 +14,7 @@ import type { } from "@oh-my-pi/pi-coding-agent/dap/types"; import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; import { DebugTool } from "@oh-my-pi/pi-coding-agent/tools/debug"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; const TEST_ADAPTER: DapResolvedAdapter = { name: "lldb-dap", @@ -360,7 +361,7 @@ describe("DAP launch failure handling", () => { expect(client.isAlive()).toBe(true); } finally { await client?.dispose(); - await fs.rm(cwd, { recursive: true, force: true }); + await removeWithRetries(cwd); } }); }); @@ -385,7 +386,7 @@ describe("DebugTool launch validation", () => { /launch program resolves to a directory.*python/, ); } finally { - await fs.rm(cwd, { recursive: true, force: true }); + await removeWithRetries(cwd); } } finally { launchSpy.mockRestore(); @@ -428,7 +429,7 @@ describe("DebugTool launch validation", () => { expect(opts.extraLaunchArguments).toEqual({ mode: "debug" }); expect(opts.program).toBe(path.join(cwd, "cmd")); } finally { - await fs.rm(cwd, { recursive: true, force: true }); + await removeWithRetries(cwd); } } finally { sessionLaunchSpy.mockRestore(); @@ -465,7 +466,7 @@ describe("DebugTool launch validation", () => { expect(opts.adapter.name).toBe("dlv"); expect(opts.extraLaunchArguments).toEqual({ mode: "debug" }); } finally { - await fs.rm(cwd, { recursive: true, force: true }); + await removeWithRetries(cwd); } } finally { sessionLaunchSpy.mockRestore(); @@ -504,7 +505,7 @@ describe("DebugTool launch validation", () => { const [opts] = sessionLaunchSpy.mock.calls[0]!; expect(opts.extraLaunchArguments).toEqual({ mode: "exec" }); } finally { - await fs.rm(cwd, { recursive: true, force: true }); + await removeWithRetries(cwd); } } finally { sessionLaunchSpy.mockRestore(); @@ -531,7 +532,7 @@ describe("DebugTool launch validation", () => { tool.execute("call", { action: "launch", program: "main.py", adapter: "debugpy" }), ).rejects.toThrow(/debugpy.*python not found in PATH/); } finally { - await fs.rm(cwd, { recursive: true, force: true }); + await removeWithRetries(cwd); } } finally { launchSpy.mockRestore(); @@ -556,7 +557,7 @@ describe("DebugTool launch validation", () => { /debugpy.*python not found in PATH/, ); } finally { - await fs.rm(cwd, { recursive: true, force: true }); + await removeWithRetries(cwd); } } finally { attachSpy.mockRestore(); @@ -582,7 +583,7 @@ describe("DebugTool launch validation", () => { /No debugger adapter available/, ); } finally { - await fs.rm(cwd, { recursive: true, force: true }); + await removeWithRetries(cwd); } } finally { launchSpy.mockRestore(); diff --git a/packages/coding-agent/test/debug/raw-sse-report-bundle.test.ts b/packages/coding-agent/test/debug/raw-sse-report-bundle.test.ts index a5366213e..bd13b25b0 100644 --- a/packages/coding-agent/test/debug/raw-sse-report-bundle.test.ts +++ b/packages/coding-agent/test/debug/raw-sse-report-bundle.test.ts @@ -6,7 +6,7 @@ import type { Model } from "@oh-my-pi/pi-ai"; import { buildModel } from "@oh-my-pi/pi-catalog/build"; import { RawSseDebugBuffer } from "@oh-my-pi/pi-coding-agent/debug/raw-sse-buffer"; import { createReportBundle } from "@oh-my-pi/pi-coding-agent/debug/report-bundle"; -import { getConfigRootDir, setAgentDir } from "@oh-my-pi/pi-utils"; +import { getConfigRootDir, removeWithRetries, setAgentDir } from "@oh-my-pi/pi-utils"; const model: Model<"anthropic-messages"> = buildModel({ id: "claude-test", @@ -39,7 +39,7 @@ afterEach(async () => { delete process.env.PI_CODING_AGENT_DIR; } if (cleanupRoot) { - await fs.rm(cleanupRoot, { recursive: true, force: true }); + await removeWithRetries(cleanupRoot); cleanupRoot = undefined; } }); diff --git a/packages/coding-agent/test/discovery/agent-discovery-disabled-providers.test.ts b/packages/coding-agent/test/discovery/agent-discovery-disabled-providers.test.ts index 4efe97225..c360939f7 100644 --- a/packages/coding-agent/test/discovery/agent-discovery-disabled-providers.test.ts +++ b/packages/coding-agent/test/discovery/agent-discovery-disabled-providers.test.ts @@ -10,6 +10,7 @@ import { disableProvider, enableProvider } from "@oh-my-pi/pi-coding-agent/capab import { clearCache as clearFsCache } from "@oh-my-pi/pi-coding-agent/capability/fs"; import { clearClaudePluginRootsCache } from "@oh-my-pi/pi-coding-agent/discovery/helpers"; import { discoverAgents } from "@oh-my-pi/pi-coding-agent/task/discovery"; +import { removeSyncWithRetries } from "@oh-my-pi/pi-utils"; const PLUGIN_AGENT_MD = [ "---", @@ -59,7 +60,7 @@ describe("discoverAgents — claude-plugins disabled provider", () => { }); afterEach(() => { - fs.rmSync(tempHome, { recursive: true, force: true }); + removeSyncWithRetries(tempHome); // Restore global state so other tests in the suite are not affected. enableProvider("claude-plugins"); clearFsCache(); diff --git a/packages/coding-agent/test/discovery/agents-monorepo-skills.test.ts b/packages/coding-agent/test/discovery/agents-monorepo-skills.test.ts index 15778c643..558e9eb21 100644 --- a/packages/coding-agent/test/discovery/agents-monorepo-skills.test.ts +++ b/packages/coding-agent/test/discovery/agents-monorepo-skills.test.ts @@ -20,6 +20,7 @@ import { loadFilesFromDir, scanSkillsFromDir, } from "@oh-my-pi/pi-coding-agent/discovery/helpers"; +import { removeSyncWithRetries } from "@oh-my-pi/pi-utils"; const PROVIDER_ID = "agents"; @@ -55,7 +56,7 @@ describe("agents provider project-level discovery", () => { afterEach(() => { clearCache(); - fs.rmSync(tempDir, { recursive: true, force: true }); + removeSyncWithRetries(tempDir); }); // ========================================================================= diff --git a/packages/coding-agent/test/discovery/at-imports.test.ts b/packages/coding-agent/test/discovery/at-imports.test.ts index 7c3279e9f..1899583fe 100644 --- a/packages/coding-agent/test/discovery/at-imports.test.ts +++ b/packages/coding-agent/test/discovery/at-imports.test.ts @@ -4,6 +4,7 @@ import * as os from "node:os"; import * as path from "node:path"; import { clearCache as clearFsCache } from "@oh-my-pi/pi-coding-agent/capability/fs"; import { expandAtImports, MAX_AT_IMPORT_DEPTH } from "@oh-my-pi/pi-coding-agent/discovery/at-imports"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; /** * Behavior contract for the @-import expander used by every AGENTS.md / @@ -19,7 +20,7 @@ describe("expandAtImports", () => { afterEach(async () => { clearFsCache(); - await fs.rm(tmp, { recursive: true, force: true }); + await removeWithRetries(tmp); }); const writeFile = async (relPath: string, content: string): Promise => { @@ -62,7 +63,7 @@ describe("expandAtImports", () => { }); expect(expanded).toContain("See use 2 spaces"); } finally { - await fs.rm(fakeHome, { recursive: true, force: true }); + await removeWithRetries(fakeHome); } }); diff --git a/packages/coding-agent/test/discovery/builtin-rules-md.test.ts b/packages/coding-agent/test/discovery/builtin-rules-md.test.ts index d1f62d3f3..ff39625f5 100644 --- a/packages/coding-agent/test/discovery/builtin-rules-md.test.ts +++ b/packages/coding-agent/test/discovery/builtin-rules-md.test.ts @@ -17,7 +17,7 @@ import { type Rule, ruleCapability } from "@oh-my-pi/pi-coding-agent/capability/ import type { LoadContext } from "@oh-my-pi/pi-coding-agent/capability/types"; // Register all discovery providers as a side effect. import "@oh-my-pi/pi-coding-agent/discovery"; -import { getConfigRootDir, setAgentDir } from "@oh-my-pi/pi-utils"; +import { getConfigRootDir, removeSyncWithRetries, setAgentDir } from "@oh-my-pi/pi-utils"; let tempDir: string; let home: string; @@ -59,7 +59,7 @@ afterEach(() => { setAgentDir(fallbackAgentDir); delete process.env.PI_CODING_AGENT_DIR; } - fs.rmSync(tempDir, { recursive: true, force: true }); + removeSyncWithRetries(tempDir); }); test("user ~/.omp/agent/RULES.md becomes an alwaysApply rule", async () => { diff --git a/packages/coding-agent/test/discovery/claude-commands.test.ts b/packages/coding-agent/test/discovery/claude-commands.test.ts index 77fc675ef..21896c659 100644 --- a/packages/coding-agent/test/discovery/claude-commands.test.ts +++ b/packages/coding-agent/test/discovery/claude-commands.test.ts @@ -6,6 +6,7 @@ import { clearCache as clearFsCache } from "@oh-my-pi/pi-coding-agent/capability import { type SlashCommand, slashCommandCapability } from "@oh-my-pi/pi-coding-agent/capability/slash-command"; import { resetSettingsForTest } from "@oh-my-pi/pi-coding-agent/config/settings"; import { loadCapability } from "@oh-my-pi/pi-coding-agent/discovery"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; async function writeFile(filePath: string, content: string): Promise { await fs.mkdir(path.dirname(filePath), { recursive: true }); @@ -39,7 +40,7 @@ describe("Claude Code slash command discovery", () => { } else { process.env.HOME = originalHome; } - await fs.rm(root, { recursive: true, force: true }); + await removeWithRetries(root); }); test("loads subdirectory commands under both basename and namespace names", async () => { diff --git a/packages/coding-agent/test/discovery/claude-plugins.test.ts b/packages/coding-agent/test/discovery/claude-plugins.test.ts index 589e3272d..5d201955d 100644 --- a/packages/coding-agent/test/discovery/claude-plugins.test.ts +++ b/packages/coding-agent/test/discovery/claude-plugins.test.ts @@ -11,6 +11,7 @@ import { } from "@oh-my-pi/pi-coding-agent/discovery/helpers"; import { loadSlashCommands } from "@oh-my-pi/pi-coding-agent/extensibility/slash-commands"; import { discoverAgents } from "@oh-my-pi/pi-coding-agent/task/discovery"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; import "@oh-my-pi/pi-coding-agent/discovery/claude-plugins"; import type { Skill } from "@oh-my-pi/pi-coding-agent/capability/skill"; import type { SlashCommand } from "@oh-my-pi/pi-coding-agent/capability/slash-command"; @@ -80,7 +81,7 @@ describe("listClaudePluginRoots", () => { } else { process.env.HOME = originalHome; } - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); }); test("returns empty roots when no registry file exists", async () => { @@ -597,7 +598,7 @@ describe("discoverAgents plugin precedence", () => { afterEach(async () => { clearClaudePluginRootsCache(); - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); }); test("prefers project-scoped plugin agent over user-scoped plugin agent", async () => { diff --git a/packages/coding-agent/test/discovery/disabled-extensions.test.ts b/packages/coding-agent/test/discovery/disabled-extensions.test.ts index 22581f339..743d5a8e9 100644 --- a/packages/coding-agent/test/discovery/disabled-extensions.test.ts +++ b/packages/coding-agent/test/discovery/disabled-extensions.test.ts @@ -5,6 +5,7 @@ import * as path from "node:path"; import { type ContextFile, contextFileCapability } from "@oh-my-pi/pi-coding-agent/capability/context-file"; import { resetSettingsForTest, Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { initializeWithSettings, loadCapability } from "@oh-my-pi/pi-coding-agent/discovery"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; describe("disabledExtensions runtime filtering", () => { let tempDir = ""; @@ -39,8 +40,8 @@ describe("disabledExtensions runtime filtering", () => { } else { process.env.HOME = originalHome; } - await fs.rm(tempHomeDir, { recursive: true, force: true }); - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempHomeDir); + await removeWithRetries(tempDir); }); test("hides disabled context files from runtime loads by default", async () => { diff --git a/packages/coding-agent/test/discovery/github-copilot.test.ts b/packages/coding-agent/test/discovery/github-copilot.test.ts index e8b99618b..8d3c07ba8 100644 --- a/packages/coding-agent/test/discovery/github-copilot.test.ts +++ b/packages/coding-agent/test/discovery/github-copilot.test.ts @@ -19,6 +19,7 @@ import type { Instruction } from "@oh-my-pi/pi-coding-agent/capability/instructi import type { Prompt } from "@oh-my-pi/pi-coding-agent/capability/prompt"; import { type Rule, resetActiveRulesForTests, setActiveRules } from "@oh-my-pi/pi-coding-agent/capability/rule"; import { RuleProtocolHandler } from "@oh-my-pi/pi-coding-agent/internal-urls/rule-protocol"; +import { removeSyncWithRetries } from "@oh-my-pi/pi-utils"; import "@oh-my-pi/pi-coding-agent/capability/context-file"; import "@oh-my-pi/pi-coding-agent/capability/instruction"; import "@oh-my-pi/pi-coding-agent/capability/prompt"; @@ -57,7 +58,7 @@ describe("github discovery — Copilot user-global surface", () => { if (savedEnv[key] === undefined) delete process.env[key]; else process.env[key] = savedEnv[key]; } - if (tempDir) fs.rmSync(tempDir, { recursive: true, force: true }); + if (tempDir) removeSyncWithRetries(tempDir); }); test("loads user-global ~/.copilot/copilot-instructions.md via COPILOT_HOME (#1913)", async () => { diff --git a/packages/coding-agent/test/discovery/github-skills.test.ts b/packages/coding-agent/test/discovery/github-skills.test.ts index 019d16129..aa2fe6100 100644 --- a/packages/coding-agent/test/discovery/github-skills.test.ts +++ b/packages/coding-agent/test/discovery/github-skills.test.ts @@ -16,6 +16,7 @@ import * as path from "node:path"; import { loadCapability } from "@oh-my-pi/pi-coding-agent/capability"; import { clearCache } from "@oh-my-pi/pi-coding-agent/capability/fs"; import type { Skill } from "@oh-my-pi/pi-coding-agent/capability/skill"; +import { removeSyncWithRetries } from "@oh-my-pi/pi-utils"; import "@oh-my-pi/pi-coding-agent/capability/skill"; import "@oh-my-pi/pi-coding-agent/discovery/github"; @@ -37,7 +38,7 @@ describe("github discovery — skills", () => { afterEach(() => { clearCache(); - fs.rmSync(tempDir, { recursive: true, force: true }); + removeSyncWithRetries(tempDir); }); test("discovers .github/skills//SKILL.md via the github provider", async () => { diff --git a/packages/coding-agent/test/discovery/mcp-json.test.ts b/packages/coding-agent/test/discovery/mcp-json.test.ts index 4d9d1afdf..dc088bba7 100644 --- a/packages/coding-agent/test/discovery/mcp-json.test.ts +++ b/packages/coding-agent/test/discovery/mcp-json.test.ts @@ -4,6 +4,7 @@ import * as os from "node:os"; import * as path from "node:path"; import { type MCPServer, mcpCapability } from "@oh-my-pi/pi-coding-agent/capability/mcp"; import { loadCapability } from "@oh-my-pi/pi-coding-agent/discovery"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; async function loadStandaloneMcpConfig(cwd: string): Promise { const result = await loadCapability(mcpCapability.id, { @@ -43,7 +44,7 @@ describe("standalone mcp.json oauth env expansion", () => { }); afterEach(async () => { - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); for (const [key, value] of Object.entries(originalEnv)) { if (value === undefined) { delete process.env[key]; diff --git a/packages/coding-agent/test/discovery/mcp-profile.test.ts b/packages/coding-agent/test/discovery/mcp-profile.test.ts index 3257cf38b..e94dae2c6 100644 --- a/packages/coding-agent/test/discovery/mcp-profile.test.ts +++ b/packages/coding-agent/test/discovery/mcp-profile.test.ts @@ -19,7 +19,7 @@ import * as path from "node:path"; import { clearCache as clearFsCache } from "@oh-my-pi/pi-coding-agent/capability/fs"; import { type MCPServer, mcpCapability } from "@oh-my-pi/pi-coding-agent/capability/mcp"; import { loadCapability } from "@oh-my-pi/pi-coding-agent/discovery"; -import { getConfigRootDir, setAgentDir } from "@oh-my-pi/pi-utils"; +import { getConfigRootDir, removeWithRetries, setAgentDir } from "@oh-my-pi/pi-utils"; const originalAgentDirEnv = process.env.PI_CODING_AGENT_DIR; const fallbackAgentDir = path.join(getConfigRootDir(), "agent"); @@ -60,8 +60,8 @@ describe("native user-level MCP discovery follows the active profile", () => { } if (originalHome === undefined) delete process.env.HOME; else process.env.HOME = originalHome; - await fs.rm(tempHome, { recursive: true, force: true }); - await fs.rm(projectDir, { recursive: true, force: true }); + await removeWithRetries(tempHome); + await removeWithRetries(projectDir); }); test("active profile loads its own user server, not the default profile's", async () => { @@ -89,7 +89,7 @@ describe("native user-level MCP discovery follows the active profile", () => { expect(profileServer?._source.level).toBe("user"); expect(profileServer?._source.path).toBe(path.join(profileAgentDir, "mcp.json")); - await fs.rm(profileAgentDir, { recursive: true, force: true }); + await removeWithRetries(profileAgentDir); }); test("default profile loads the user server from ~/.omp/agent", async () => { diff --git a/packages/coding-agent/test/discovery/monorepo-skills.test.ts b/packages/coding-agent/test/discovery/monorepo-skills.test.ts index 4e00e4af9..dde8811b6 100644 --- a/packages/coding-agent/test/discovery/monorepo-skills.test.ts +++ b/packages/coding-agent/test/discovery/monorepo-skills.test.ts @@ -13,6 +13,7 @@ import { clearCache } from "@oh-my-pi/pi-coding-agent/capability/fs"; import type { Skill } from "@oh-my-pi/pi-coding-agent/capability/skill"; import type { LoadContext, LoadResult } from "@oh-my-pi/pi-coding-agent/capability/types"; import { scanSkillsFromDir } from "@oh-my-pi/pi-coding-agent/discovery/helpers"; +import { removeSyncWithRetries } from "@oh-my-pi/pi-utils"; function writeSkill(dir: string, name: string, description: string): void { const skillDir = path.join(dir, name); @@ -42,7 +43,7 @@ describe("monorepo skill discovery", () => { afterEach(() => { clearCache(); - fs.rmSync(tempDir, { recursive: true, force: true }); + removeSyncWithRetries(tempDir); }); test("finds skills in ancestor .omp/skills/ directories", async () => { diff --git a/packages/coding-agent/test/discovery/omp-plugins.test.ts b/packages/coding-agent/test/discovery/omp-plugins.test.ts index 9c6f7e6cc..129589148 100644 --- a/packages/coding-agent/test/discovery/omp-plugins.test.ts +++ b/packages/coding-agent/test/discovery/omp-plugins.test.ts @@ -32,7 +32,7 @@ import { clearOmpExtensionCliRoots, injectOmpExtensionCliRoots, } from "@oh-my-pi/pi-coding-agent/discovery/omp-extension-roots"; -import { getConfigRootDir, setAgentDir } from "@oh-my-pi/pi-utils"; +import { getConfigRootDir, removeSyncWithRetries, setAgentDir } from "@oh-my-pi/pi-utils"; const PROVIDER_ID = "omp-plugins"; @@ -108,7 +108,7 @@ afterEach(() => { setAgentDir(fallbackAgentDir); delete process.env.PI_CODING_AGENT_DIR; } - fs.rmSync(tempDir, { recursive: true, force: true }); + removeSyncWithRetries(tempDir); }); function ctx(): LoadContext { diff --git a/packages/coding-agent/test/discovery/opencode.test.ts b/packages/coding-agent/test/discovery/opencode.test.ts index 6a9094201..ac3f90b03 100644 --- a/packages/coding-agent/test/discovery/opencode.test.ts +++ b/packages/coding-agent/test/discovery/opencode.test.ts @@ -4,6 +4,7 @@ import * as os from "node:os"; import * as path from "node:path"; import { type MCPServer, mcpCapability } from "@oh-my-pi/pi-coding-agent/capability/mcp"; import { loadCapability } from "@oh-my-pi/pi-coding-agent/discovery"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; async function loadOpenCodeMcpConfig(cwd: string): Promise { const result = await loadCapability(mcpCapability.id, { @@ -21,7 +22,7 @@ describe("OpenCode MCP discovery", () => { }); afterEach(async () => { - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); }); test("normalizes array commands and OpenCode environment fields", async () => { diff --git a/packages/coding-agent/test/discovery/profile-isolation.test.ts b/packages/coding-agent/test/discovery/profile-isolation.test.ts index d82131695..05bf19ffc 100644 --- a/packages/coding-agent/test/discovery/profile-isolation.test.ts +++ b/packages/coding-agent/test/discovery/profile-isolation.test.ts @@ -21,7 +21,7 @@ import { clearCache as clearFsCache } from "@oh-my-pi/pi-coding-agent/capability import { type Skill, skillCapability } from "@oh-my-pi/pi-coding-agent/capability/skill"; import { type SlashCommand, slashCommandCapability } from "@oh-my-pi/pi-coding-agent/capability/slash-command"; import { loadCapability } from "@oh-my-pi/pi-coding-agent/discovery"; -import { getConfigRootDir, setAgentDir } from "@oh-my-pi/pi-utils"; +import { getConfigRootDir, removeWithRetries, setAgentDir } from "@oh-my-pi/pi-utils"; const originalAgentDirEnv = process.env.PI_CODING_AGENT_DIR; const fallbackAgentDir = path.join(getConfigRootDir(), "agent"); @@ -74,9 +74,9 @@ describe("native user-level config discovery follows the active profile", () => } if (originalHome === undefined) delete process.env.HOME; else process.env.HOME = originalHome; - await fs.rm(tempHome, { recursive: true, force: true }); - await fs.rm(projectDir, { recursive: true, force: true }); - await fs.rm(profileAgentDir, { recursive: true, force: true }); + await removeWithRetries(tempHome); + await removeWithRetries(projectDir); + await removeWithRetries(profileAgentDir); }); test("slash commands resolve from the profile, not the default agent dir", async () => { diff --git a/packages/coding-agent/test/edit-acp-bridge.test.ts b/packages/coding-agent/test/edit-acp-bridge.test.ts index 7d3049973..252cfcb5c 100644 --- a/packages/coding-agent/test/edit-acp-bridge.test.ts +++ b/packages/coding-agent/test/edit-acp-bridge.test.ts @@ -10,6 +10,7 @@ import type { WritethroughCallback } from "@oh-my-pi/pi-coding-agent/lsp"; import type { PlanModeState } from "@oh-my-pi/pi-coding-agent/plan-mode/state"; import type { ClientBridge } from "@oh-my-pi/pi-coding-agent/session/client-bridge"; import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; // ─── Shared helpers ─────────────────────────────────────────────────────────── @@ -80,7 +81,7 @@ describe("HashlineFilesystem ACP fs routing", () => { afterEach(async () => { resetSettingsForTest(); - await fs.rm(tmpDir, { recursive: true, force: true }); + await removeWithRetries(tmpDir); }); it("routes plain workspace writes through the bridge and skips writethrough", async () => { @@ -170,7 +171,7 @@ describe("executeReplaceSingle ACP fs routing", () => { afterEach(async () => { resetSettingsForTest(); - await fs.rm(tmpDir, { recursive: true, force: true }); + await removeWithRetries(tmpDir); }); it("routes plain workspace writes through the bridge and skips writethrough", async () => { @@ -243,7 +244,7 @@ describe("executePatchSingle ACP fs routing", () => { afterEach(async () => { resetSettingsForTest(); - await fs.rm(tmpDir, { recursive: true, force: true }); + await removeWithRetries(tmpDir); }); it("routes plain workspace writes through the bridge and skips writethrough", async () => { diff --git a/packages/coding-agent/test/edit-auto-generated-regressions.test.ts b/packages/coding-agent/test/edit-auto-generated-regressions.test.ts index cf0eed6eb..09f0df6a4 100644 --- a/packages/coding-agent/test/edit-auto-generated-regressions.test.ts +++ b/packages/coding-agent/test/edit-auto-generated-regressions.test.ts @@ -29,7 +29,7 @@ import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manage import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; import * as autoGeneratedGuard from "@oh-my-pi/pi-coding-agent/tools/auto-generated-guard"; import { ToolError } from "@oh-my-pi/pi-coding-agent/tools/tool-errors"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; import { type } from "arktype"; function createAssistantMessage(content: AssistantMessage["content"], stopReason: StopReason): AssistantMessage { @@ -187,7 +187,7 @@ beforeEach(() => { }); afterEach(async () => { - if (tempDir) fs.rmSync(tempDir, { recursive: true, force: true }); + if (tempDir) removeSyncWithRetries(tempDir); }); it("auto-generated streaming abort fires even when edit.streamingAbort is disabled", async () => { diff --git a/packages/coding-agent/test/edit-diff.test.ts b/packages/coding-agent/test/edit-diff.test.ts index ff4dd440b..51de5e49d 100644 --- a/packages/coding-agent/test/edit-diff.test.ts +++ b/packages/coding-agent/test/edit-diff.test.ts @@ -10,6 +10,7 @@ import { DEFAULT_FUZZY_THRESHOLD, findMatch, } from "@oh-my-pi/pi-coding-agent/edit"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; describe("findMatch", () => { describe("exact matching", () => { @@ -227,7 +228,7 @@ describe("computeHashlineDiff", () => { afterEach(async () => { if (tempDir) { - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); } }); @@ -308,7 +309,7 @@ describe("computeEditDiff", () => { afterEach(async () => { if (tempDir) { - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); } }); diff --git a/packages/coding-agent/test/edit-patch-unchanged-error.test.ts b/packages/coding-agent/test/edit-patch-unchanged-error.test.ts index 94a2ddeaf..813df9c90 100644 --- a/packages/coding-agent/test/edit-patch-unchanged-error.test.ts +++ b/packages/coding-agent/test/edit-patch-unchanged-error.test.ts @@ -6,6 +6,7 @@ import { resetSettingsForTest, Settings } from "@oh-my-pi/pi-coding-agent/config import { DEFAULT_FUZZY_THRESHOLD, executePatchSingle } from "@oh-my-pi/pi-coding-agent/edit"; import type { FileDiagnosticsResult } from "@oh-my-pi/pi-coding-agent/lsp"; import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; function makeSession(cwd: string): ToolSession { return { @@ -47,7 +48,7 @@ beforeEach(async () => { afterEach(async () => { resetSettingsForTest(); - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); }); describe("executePatchSingle — post-write verification error path", () => { diff --git a/packages/coding-agent/test/edit-per-file-diff-content.test.ts b/packages/coding-agent/test/edit-per-file-diff-content.test.ts index 738f7bd38..8ccf01bcf 100644 --- a/packages/coding-agent/test/edit-per-file-diff-content.test.ts +++ b/packages/coding-agent/test/edit-per-file-diff-content.test.ts @@ -12,6 +12,7 @@ import { } from "@oh-my-pi/pi-coding-agent/edit"; import { writethroughNoop } from "@oh-my-pi/pi-coding-agent/lsp"; import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; // ─── Minimal ToolSession stub ──────────────────────────────────────────────── @@ -47,7 +48,7 @@ beforeEach(async () => { afterEach(async () => { resetSettingsForTest(); - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); }); // ─── executePatchSingle ─────────────────────────────────────────────────────── diff --git a/packages/coding-agent/test/edit-streaming-preview.test.ts b/packages/coding-agent/test/edit-streaming-preview.test.ts index 0ad5bec3c..a25c8c37f 100644 --- a/packages/coding-agent/test/edit-streaming-preview.test.ts +++ b/packages/coding-agent/test/edit-streaming-preview.test.ts @@ -4,6 +4,7 @@ import * as os from "node:os"; import * as path from "node:path"; import { computeFileHash, formatHashlineHeader, InMemorySnapshotStore } from "@oh-my-pi/hashline"; import { dropIncompleteLastEdit, EDIT_MODE_STRATEGIES } from "@oh-my-pi/pi-coding-agent/edit"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; describe("dropIncompleteLastEdit", () => { test("keeps all entries when partialJson is undefined", () => { @@ -60,7 +61,7 @@ describe("hashline streaming preview (multi-section)", () => { }); afterEach(async () => { - await fs.rm(tmpDir, { recursive: true, force: true }); + await removeWithRetries(tmpDir); }); const ctx = (cwd: string) => ({ cwd, signal: new AbortController().signal, snapshots }); @@ -114,7 +115,7 @@ describe("hashline streaming preview (single-op trailing payload)", () => { }); afterEach(async () => { - await fs.rm(tmpDir, { recursive: true, force: true }); + await removeWithRetries(tmpDir); }); const ctx = (cwd: string, isStreaming = true) => ({ @@ -211,7 +212,7 @@ describe("hashline streaming preview (monotonic growth)", () => { }); afterEach(async () => { - await fs.rm(tmpDir, { recursive: true, force: true }); + await removeWithRetries(tmpDir); }); const ctx = (cwd: string) => ({ cwd, signal: new AbortController().signal, snapshots, isStreaming: true }); @@ -257,7 +258,7 @@ describe("apply_patch streaming preview (trailing partial line)", () => { }); afterEach(async () => { - await fs.rm(tmpDir, { recursive: true, force: true }); + await removeWithRetries(tmpDir); }); const ctx = (cwd: string, isStreaming: boolean) => ({ diff --git a/packages/coding-agent/test/edit/seen-line-guard.test.ts b/packages/coding-agent/test/edit/seen-line-guard.test.ts index 476977340..d6d2ab7b8 100644 --- a/packages/coding-agent/test/edit/seen-line-guard.test.ts +++ b/packages/coding-agent/test/edit/seen-line-guard.test.ts @@ -7,6 +7,7 @@ import { type ExecuteHashlineSingleOptions, executeHashlineSingle } from "@oh-my import { canonicalSnapshotKey, getFileSnapshotStore } from "@oh-my-pi/pi-coding-agent/edit/file-snapshot-store"; import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; import { ReadTool } from "@oh-my-pi/pi-coding-agent/tools/read"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; import { GrepTool } from "../../src/tools/grep"; function createSession(cwd: string): ToolSession { @@ -78,7 +79,7 @@ describe("read → edit seen-line guard", () => { tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "seen-line-guard-")); }); afterEach(async () => { - await fs.rm(tmpDir, { recursive: true, force: true }); + await removeWithRetries(tmpDir); }); it("records the displayed range as seen and excludes far lines", async () => { @@ -180,7 +181,7 @@ describe("search → edit seen-line guard", () => { tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "seen-line-search-")); }); afterEach(async () => { - await fs.rm(tmpDir, { recursive: true, force: true }); + await removeWithRetries(tmpDir); }); function searchSession(cwd: string): ToolSession { diff --git a/packages/coding-agent/test/export-subsessions.test.ts b/packages/coding-agent/test/export-subsessions.test.ts index 88ae34fd7..551039724 100644 --- a/packages/coding-agent/test/export-subsessions.test.ts +++ b/packages/coding-agent/test/export-subsessions.test.ts @@ -2,6 +2,7 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test"; import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; import { collectSubSessions } from "../src/export/html"; /** @@ -41,7 +42,7 @@ describe("collectSubSessions", () => { }); afterEach(async () => { - await fs.rm(root, { recursive: true, force: true }); + await removeWithRetries(root); }); test("collects nested subagent sessions with parent links and leaf ids", async () => { diff --git a/packages/coding-agent/test/extensibility/custom-commands/review.test.ts b/packages/coding-agent/test/extensibility/custom-commands/review.test.ts index ceef22805..9bd845b08 100644 --- a/packages/coding-agent/test/extensibility/custom-commands/review.test.ts +++ b/packages/coding-agent/test/extensibility/custom-commands/review.test.ts @@ -10,6 +10,7 @@ import type { PrDiffPayload, ViewLookupResult } from "@oh-my-pi/pi-coding-agent/ import * as gh from "@oh-my-pi/pi-coding-agent/tools/gh"; import * as git from "@oh-my-pi/pi-coding-agent/utils/git"; import * as jj from "@oh-my-pi/pi-coding-agent/utils/jj"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; const SAMPLE_JJ_DIFF = `diff --git a/src/workspace.ts b/src/workspace.ts --- a/src/workspace.ts @@ -86,7 +87,7 @@ describe("ReviewCommand", () => { afterEach(async () => { vi.restoreAllMocks(); if (tmpDir) { - await fs.rm(tmpDir, { recursive: true, force: true }); + await removeWithRetries(tmpDir); tmpDir = undefined; } }); @@ -183,7 +184,7 @@ describe("ReviewCommand", () => { const result = await command.execute([], ctx); expect(result).toBeUndefined(); - await fs.rm(dir, { recursive: true, force: true }); + await removeWithRetries(dir); tmpDir = undefined; } }); diff --git a/packages/coding-agent/test/extensibility/legacy-pi-ai-type-remap.test.ts b/packages/coding-agent/test/extensibility/legacy-pi-ai-type-remap.test.ts index 81afbcf5e..d27295d68 100644 --- a/packages/coding-agent/test/extensibility/legacy-pi-ai-type-remap.test.ts +++ b/packages/coding-agent/test/extensibility/legacy-pi-ai-type-remap.test.ts @@ -10,6 +10,7 @@ import { loadLegacyPiModule, } from "@oh-my-pi/pi-coding-agent/extensibility/plugins/legacy-pi-compat"; import { Type as TypeBoxShimType } from "@oh-my-pi/pi-coding-agent/extensibility/typebox"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; // pi-ai 15.1.0 removed the runtime `Type` export from `@oh-my-pi/pi-ai`'s // package root. Legacy extensions (and their aliased-scope variants such as @@ -29,7 +30,7 @@ afterEach(() => { afterAll(async () => { for (const dir of tempRoots) { - await fs.rm(dir, { recursive: true, force: true }); + await removeWithRetries(dir); } }); diff --git a/packages/coding-agent/test/extensibility/legacy-pi-inplace-load.test.ts b/packages/coding-agent/test/extensibility/legacy-pi-inplace-load.test.ts index dd9b9d82c..f29f02989 100644 --- a/packages/coding-agent/test/extensibility/legacy-pi-inplace-load.test.ts +++ b/packages/coding-agent/test/extensibility/legacy-pi-inplace-load.test.ts @@ -7,6 +7,7 @@ import { __rewriteLegacyExtensionSourceForTests, loadLegacyPiModule, } from "@oh-my-pi/pi-coding-agent/extensibility/plugins/legacy-pi-compat"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; // Issue #1674: legacy Pi extensions load browser-UI assets (HTML/CSS) at module // init via `readFileSync(join(__dirname, "ui.html"))`. The compat layer must run @@ -19,7 +20,7 @@ const tempRoots: string[] = []; afterAll(async () => { for (const dir of tempRoots) { - await fs.rm(dir, { recursive: true, force: true }); + await removeWithRetries(dir); } }); diff --git a/packages/coding-agent/test/extensibility/typebox-remap.test.ts b/packages/coding-agent/test/extensibility/typebox-remap.test.ts index 220993ae2..0a29f547b 100644 --- a/packages/coding-agent/test/extensibility/typebox-remap.test.ts +++ b/packages/coding-agent/test/extensibility/typebox-remap.test.ts @@ -7,6 +7,7 @@ import { loadLegacyPiModule, } from "@oh-my-pi/pi-coding-agent/extensibility/plugins/legacy-pi-compat"; import { Type as TypeBoxShimType } from "@oh-my-pi/pi-coding-agent/extensibility/typebox"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; // The remap installs a Bun.plugin onResolve hook plus an explicit // rewrite branch inside `rewriteBareImportsForLegacyExtension` that @@ -19,7 +20,7 @@ const tempRoots: string[] = []; afterAll(async () => { for (const dir of tempRoots) { - await fs.rm(dir, { recursive: true, force: true }); + await removeWithRetries(dir); } }); diff --git a/packages/coding-agent/test/file-lock.test.ts b/packages/coding-agent/test/file-lock.test.ts index be8e65699..5b37e30a8 100644 --- a/packages/coding-agent/test/file-lock.test.ts +++ b/packages/coding-agent/test/file-lock.test.ts @@ -3,6 +3,7 @@ import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; import { __internalsForTesting, withFileLock } from "@oh-my-pi/pi-coding-agent/config/file-lock"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; const { tryAcquireLock, releaseLock, readLockInfo, isLockStale, getLockPath } = __internalsForTesting; @@ -16,7 +17,7 @@ async function mkRoot(): Promise { afterAll(async () => { for (const root of ROOTS) { - await fs.rm(root, { recursive: true, force: true }).catch(() => {}); + await removeWithRetries(root).catch(() => {}); } }); @@ -55,7 +56,7 @@ describe("file-lock token ownership (F1)", () => { const stale = await isLockStale(lockPath, 10_000); expect(stale).toBe(false); - await fs.rm(lockPath, { recursive: true }); + await removeWithRetries(lockPath); }); test("withFileLock serializes N concurrent writers without lost updates", async () => { diff --git a/packages/coding-agent/test/file-mentions.test.ts b/packages/coding-agent/test/file-mentions.test.ts index 4437cb497..a49b56c5d 100644 --- a/packages/coding-agent/test/file-mentions.test.ts +++ b/packages/coding-agent/test/file-mentions.test.ts @@ -3,12 +3,13 @@ import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; import { extractFileMentions, generateFileMentionMessages } from "@oh-my-pi/pi-coding-agent/utils/file-mentions"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; const tempDirs: string[] = []; afterEach(async () => { for (const dir of tempDirs.splice(0, tempDirs.length)) { - await fs.rm(dir, { recursive: true, force: true }); + await removeWithRetries(dir); } }); diff --git a/packages/coding-agent/test/git-reftable.test.ts b/packages/coding-agent/test/git-reftable.test.ts index 53bb58de3..8796c7643 100644 --- a/packages/coding-agent/test/git-reftable.test.ts +++ b/packages/coding-agent/test/git-reftable.test.ts @@ -2,6 +2,7 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; import { $ } from "bun"; import * as git from "../src/utils/git"; @@ -55,9 +56,9 @@ describe.skipIf(!supportsReftable)("git reftable support", () => { afterAll(async () => { await $`git worktree remove ${worktreeDir} -f`.cwd(sharedRepoDir).quiet().nothrow(); - await fs.rm(worktreeDir, { recursive: true, force: true }).catch(() => {}); - await fs.rm(sharedRepoDir, { recursive: true, force: true }).catch(() => {}); - await fs.rm(configRepoDir, { recursive: true, force: true }).catch(() => {}); + await removeWithRetries(worktreeDir).catch(() => {}); + await removeWithRetries(sharedRepoDir).catch(() => {}); + await removeWithRetries(configRepoDir).catch(() => {}); }); test("resolves references in a reftable repository", async () => { diff --git a/packages/coding-agent/test/hindsight-bank.test.ts b/packages/coding-agent/test/hindsight-bank.test.ts index fadc00add..952bbee3d 100644 --- a/packages/coding-agent/test/hindsight-bank.test.ts +++ b/packages/coding-agent/test/hindsight-bank.test.ts @@ -5,6 +5,7 @@ import * as path from "node:path"; import { computeBankScope, deriveBankId, ensureBankExists } from "@oh-my-pi/pi-coding-agent/hindsight/bank"; import { HindsightApi } from "@oh-my-pi/pi-coding-agent/hindsight/client"; import type { HindsightConfig } from "@oh-my-pi/pi-coding-agent/hindsight/config"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; // Isolate `git` invocations in this file from the host's global config — // `~/.gitconfig` commit signing or template hooks would otherwise turn the @@ -178,7 +179,7 @@ describe("computeBankScope", () => { }); afterAll(async () => { - if (baseDir) await fs.rm(baseDir, { recursive: true, force: true }); + if (baseDir) await removeWithRetries(baseDir); }); it("emits the same project tag from the primary checkout and a linked worktree", () => { diff --git a/packages/coding-agent/test/history-storage-drain.test.ts b/packages/coding-agent/test/history-storage-drain.test.ts index 9cdc0a3b5..ac90a2e7e 100644 --- a/packages/coding-agent/test/history-storage-drain.test.ts +++ b/packages/coding-agent/test/history-storage-drain.test.ts @@ -3,6 +3,7 @@ import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; import { HistoryStorage } from "@oh-my-pi/pi-coding-agent/session/history-storage"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; let tempDir = ""; @@ -28,7 +29,7 @@ afterEach(async () => { HistoryStorage.resetInstance(); vi.useRealTimers(); if (tempDir) { - await fs.rm(tempDir, { recursive: true, force: true }).catch(() => {}); + await removeWithRetries(tempDir).catch(() => {}); tempDir = ""; } }); diff --git a/packages/coding-agent/test/image-input.test.ts b/packages/coding-agent/test/image-input.test.ts index 70b0f9925..dd3c5449d 100644 --- a/packages/coding-agent/test/image-input.test.ts +++ b/packages/coding-agent/test/image-input.test.ts @@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, it } from "bun:test"; import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; -import { readImageMetadata } from "@oh-my-pi/pi-utils"; +import { readImageMetadata, removeSyncWithRetries } from "@oh-my-pi/pi-utils"; describe("readImageMetadata", () => { let testDir: string; @@ -12,7 +12,7 @@ describe("readImageMetadata", () => { }); afterEach(() => { - fs.rmSync(testDir, { recursive: true, force: true }); + removeSyncWithRetries(testDir); }); it("reads PNG metadata from header", async () => { diff --git a/packages/coding-agent/test/input-controller-large-paste.test.ts b/packages/coding-agent/test/input-controller-large-paste.test.ts index 355607c75..683a612ce 100644 --- a/packages/coding-agent/test/input-controller-large-paste.test.ts +++ b/packages/coding-agent/test/input-controller-large-paste.test.ts @@ -1,3 +1,4 @@ +import { removeWithRetries } from "@oh-my-pi/pi-utils"; /** * Large-paste menu: when a paste reaches the configured `paste.largeMenuThreshold` line count, * the editor's `onLargePaste` hook routes through `InputController.handleLargePaste`, which offers @@ -121,7 +122,7 @@ describe("InputController.presentLargePasteMenu file attachment", () => { let dir: string | undefined; afterEach(async () => { - if (dir) await fs.rm(dir, { recursive: true, force: true }); + if (dir) await removeWithRetries(dir); dir = undefined; }); diff --git a/packages/coding-agent/test/install-command.test.ts b/packages/coding-agent/test/install-command.test.ts index d41bced68..d561eaa6f 100644 --- a/packages/coding-agent/test/install-command.test.ts +++ b/packages/coding-agent/test/install-command.test.ts @@ -17,6 +17,7 @@ import * as os from "node:os"; import * as path from "node:path"; import { commands, isSubcommand, resolveCliArgv } from "@oh-my-pi/pi-coding-agent/cli-commands"; import { looksLikeLocalPath } from "@oh-my-pi/pi-coding-agent/commands/install"; +import { removeSyncWithRetries } from "@oh-my-pi/pi-utils"; describe("install command is registered as a top-level subcommand", () => { test("CLI runner sees `install` as a known command", () => { @@ -66,7 +67,7 @@ describe("looksLikeLocalPath", () => { expect(looksLikeLocalPath("vendored-ext", tempDir)).toBe(true); expect(looksLikeLocalPath("missing-pkg", tempDir)).toBe(false); } finally { - fs.rmSync(tempDir, { recursive: true, force: true }); + removeSyncWithRetries(tempDir); } }); }); diff --git a/packages/coding-agent/test/internal-urls/history-protocol.test.ts b/packages/coding-agent/test/internal-urls/history-protocol.test.ts index 348b4f7c7..01f833c67 100644 --- a/packages/coding-agent/test/internal-urls/history-protocol.test.ts +++ b/packages/coding-agent/test/internal-urls/history-protocol.test.ts @@ -17,13 +17,14 @@ import { HistoryProtocolHandler } from "@oh-my-pi/pi-coding-agent/internal-urls/ import { AgentRegistry } from "@oh-my-pi/pi-coding-agent/registry/agent-registry"; import type { AgentSession } from "@oh-my-pi/pi-coding-agent/session/agent-session"; import { CURRENT_SESSION_VERSION } from "@oh-my-pi/pi-coding-agent/session/session-entries"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; async function withTempDir(fn: (dir: string) => Promise): Promise { const dir = await fs.mkdtemp(path.join(os.tmpdir(), "history-protocol-")); try { return await fn(dir); } finally { - await fs.rm(dir, { recursive: true, force: true }); + await removeWithRetries(dir); } } diff --git a/packages/coding-agent/test/internal-urls/issue-pr-protocol.test.ts b/packages/coding-agent/test/internal-urls/issue-pr-protocol.test.ts index 359796938..2b4cbdc7d 100644 --- a/packages/coding-agent/test/internal-urls/issue-pr-protocol.test.ts +++ b/packages/coding-agent/test/internal-urls/issue-pr-protocol.test.ts @@ -13,6 +13,7 @@ import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { InternalUrlRouter } from "@oh-my-pi/pi-coding-agent/internal-urls"; import { resetForTests as resetCacheForTests } from "@oh-my-pi/pi-coding-agent/tools/github-cache"; import * as git from "@oh-my-pi/pi-coding-agent/utils/git"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; let tempDir: string; let originalEnv: string | undefined; @@ -42,7 +43,7 @@ afterEach(async () => { process.env.GH_TOKEN = originalGhToken; } vi.restoreAllMocks(); - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); }); function issuePayload(number: number, body: string, commentBodies: string[] = []) { diff --git a/packages/coding-agent/test/internal-urls/local-protocol.test.ts b/packages/coding-agent/test/internal-urls/local-protocol.test.ts index 7b8891686..c6c3d36e7 100644 --- a/packages/coding-agent/test/internal-urls/local-protocol.test.ts +++ b/packages/coding-agent/test/internal-urls/local-protocol.test.ts @@ -8,13 +8,14 @@ import { resolveLocalRoot, resolveLocalUrlToPath, } from "@oh-my-pi/pi-coding-agent/internal-urls"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; async function withTempDir(fn: (dir: string) => Promise): Promise { const dir = await fs.mkdtemp(path.join(os.tmpdir(), "local-protocol-")); try { return await fn(dir); } finally { - await fs.rm(dir, { recursive: true, force: true }); + await removeWithRetries(dir); } } diff --git a/packages/coding-agent/test/internal-urls/memory-protocol.test.ts b/packages/coding-agent/test/internal-urls/memory-protocol.test.ts index a42fb7159..d3bcf0619 100644 --- a/packages/coding-agent/test/internal-urls/memory-protocol.test.ts +++ b/packages/coding-agent/test/internal-urls/memory-protocol.test.ts @@ -6,7 +6,7 @@ import { InternalUrlRouter } from "@oh-my-pi/pi-coding-agent/internal-urls"; import { getMemoryRoot } from "@oh-my-pi/pi-coding-agent/memories"; import { AgentRegistry } from "@oh-my-pi/pi-coding-agent/registry/agent-registry"; import type { AgentSession } from "@oh-my-pi/pi-coding-agent/session/agent-session"; -import { getAgentDir, setAgentDir } from "@oh-my-pi/pi-utils"; +import { getAgentDir, removeWithRetries, setAgentDir } from "@oh-my-pi/pi-utils"; interface MemoryFixture { cwd: string; @@ -42,7 +42,7 @@ async function withMemoryFixture(fn: (fixture: MemoryFixture) => Promise): await fn({ cwd, memoryRoot, agentDir, cleanupRoot }); } finally { setAgentDir(previousAgentDir); - await fs.rm(cleanupRoot, { recursive: true, force: true }); + await removeWithRetries(cleanupRoot); } } diff --git a/packages/coding-agent/test/internal-urls/vault-protocol.test.ts b/packages/coding-agent/test/internal-urls/vault-protocol.test.ts index c601a55a1..9ede1e461 100644 --- a/packages/coding-agent/test/internal-urls/vault-protocol.test.ts +++ b/packages/coding-agent/test/internal-urls/vault-protocol.test.ts @@ -10,13 +10,14 @@ import { VaultProtocolHandler, } from "@oh-my-pi/pi-coding-agent/internal-urls"; import * as vaultProtocol from "@oh-my-pi/pi-coding-agent/internal-urls/vault-protocol"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; async function withTempDir(fn: (dir: string) => Promise): Promise { const dir = await fs.mkdtemp(path.join(os.tmpdir(), "vault-protocol-")); try { return await fn(dir); } finally { - await fs.rm(dir, { recursive: true, force: true }); + await removeWithRetries(dir); } } diff --git a/packages/coding-agent/test/issue-1401-repro.test.ts b/packages/coding-agent/test/issue-1401-repro.test.ts index 4339b9f17..7e1d09522 100644 --- a/packages/coding-agent/test/issue-1401-repro.test.ts +++ b/packages/coding-agent/test/issue-1401-repro.test.ts @@ -3,6 +3,7 @@ import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; import { processFileArguments } from "@oh-my-pi/pi-coding-agent/cli/file-processor"; +import { removeSyncWithRetries } from "@oh-my-pi/pi-utils"; function createPdfWithText(text: string): string { const chunks: string[] = []; @@ -47,7 +48,7 @@ describe("processFileArguments", () => { }); afterEach(() => { - fs.rmSync(testDir, { recursive: true, force: true }); + removeSyncWithRetries(testDir); }); it("converts PDF file arguments before adding them to the prompt", async () => { diff --git a/packages/coding-agent/test/issue-1528-discovery-default-max-tokens.test.ts b/packages/coding-agent/test/issue-1528-discovery-default-max-tokens.test.ts index b01b75f20..df5cd2794 100644 --- a/packages/coding-agent/test/issue-1528-discovery-default-max-tokens.test.ts +++ b/packages/coding-agent/test/issue-1528-discovery-default-max-tokens.test.ts @@ -5,7 +5,7 @@ import * as path from "node:path"; import type { FetchImpl } from "@oh-my-pi/pi-ai/types"; import { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry"; import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; /** * Issue #1528: auto-discovered OpenAI-compatible models defaulted to @@ -31,7 +31,7 @@ describe("issue #1528 discovery maxTokens default", () => { afterEach(() => { authStorage.close(); if (tempDir && fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true }); + removeSyncWithRetries(tempDir); } }); diff --git a/packages/coding-agent/test/issue-845-repro.test.ts b/packages/coding-agent/test/issue-845-repro.test.ts index 9c732220d..71415c170 100644 --- a/packages/coding-agent/test/issue-845-repro.test.ts +++ b/packages/coding-agent/test/issue-845-repro.test.ts @@ -3,6 +3,7 @@ import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; import { resolveUpdateMethodForTest } from "@oh-my-pi/pi-coding-agent/cli/update-cli"; +import { removeSyncWithRetries } from "@oh-my-pi/pi-utils"; // Issue #845: on Windows with Bun installed via Scoop, ~/.bun is a junction // to scoop\persist\Oven-sh.Bun\.bun. `bun pm bin -g` and the omp path that @@ -34,7 +35,7 @@ describe("issue-845: resolveUpdateMethod follows symlinks/junctions", () => { }); afterAll(() => { - fs.rmSync(tmpRoot, { recursive: true, force: true }); + removeSyncWithRetries(tmpRoot); }); it("classifies omp reached through a symlinked bin dir as bun-managed", () => { diff --git a/packages/coding-agent/test/issue-851-repro.test.ts b/packages/coding-agent/test/issue-851-repro.test.ts index 3c2ec0baf..d334dc664 100644 --- a/packages/coding-agent/test/issue-851-repro.test.ts +++ b/packages/coding-agent/test/issue-851-repro.test.ts @@ -5,6 +5,7 @@ import * as path from "node:path"; import { loadCapability } from "@oh-my-pi/pi-coding-agent/capability"; import { clearCache as clearFsCache } from "@oh-my-pi/pi-coding-agent/capability/fs"; import { clearClaudePluginRootsCache } from "@oh-my-pi/pi-coding-agent/discovery/helpers"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; import "@oh-my-pi/pi-coding-agent/discovery/claude-plugins"; import type { MCPServer } from "@oh-my-pi/pi-coding-agent/capability/mcp"; @@ -27,7 +28,7 @@ describe("issue-851: claude-plugins loads flat .mcp.json shape", () => { vi.restoreAllMocks(); if (originalHome === undefined) delete process.env.HOME; else process.env.HOME = originalHome; - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); }); async function setupPlugin(pluginId: string, mcpJson: unknown): Promise { diff --git a/packages/coding-agent/test/issue-956-repro.test.ts b/packages/coding-agent/test/issue-956-repro.test.ts index f60495567..f5f7f39fd 100644 --- a/packages/coding-agent/test/issue-956-repro.test.ts +++ b/packages/coding-agent/test/issue-956-repro.test.ts @@ -5,7 +5,7 @@ import * as path from "node:path"; import * as mcpClient from "@oh-my-pi/pi-coding-agent/mcp/client"; import { MCPCommandController } from "@oh-my-pi/pi-coding-agent/modes/controllers/mcp-command-controller"; import { initTheme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme"; -import { getConfigRootDir, getProjectDir, setAgentDir, setProjectDir } from "@oh-my-pi/pi-utils"; +import { getConfigRootDir, getProjectDir, removeWithRetries, setAgentDir, setProjectDir } from "@oh-my-pi/pi-utils"; const originalProjectDir = getProjectDir(); const originalAgentDir = process.env.PI_CODING_AGENT_DIR; @@ -52,8 +52,8 @@ describe("issue #956: interactive /mcp test", () => { setAgentDir(fallbackAgentDir); delete process.env.PI_CODING_AGENT_DIR; } - await fs.rm(projectDir, { recursive: true, force: true }); - await fs.rm(agentDir, { recursive: true, force: true }); + await removeWithRetries(projectDir); + await removeWithRetries(agentDir); }); it("tests a connected server discovered from standalone .mcp.json", async () => { diff --git a/packages/coding-agent/test/issue-970-custom-provider-discovery.test.ts b/packages/coding-agent/test/issue-970-custom-provider-discovery.test.ts index 89f1cfa7c..37450ef76 100644 --- a/packages/coding-agent/test/issue-970-custom-provider-discovery.test.ts +++ b/packages/coding-agent/test/issue-970-custom-provider-discovery.test.ts @@ -12,7 +12,7 @@ import { ModelSelectorComponent } from "@oh-my-pi/pi-coding-agent/modes/componen import { getThemeByName, setThemeInstance } from "@oh-my-pi/pi-coding-agent/modes/theme/theme"; import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; import type { TUI } from "@oh-my-pi/pi-tui"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; function normalizeRenderedText(text: string): string { return stripVTControlCharacters(text).replace(/\s+/g, " ").trim(); @@ -78,7 +78,7 @@ describe("issue #970 custom provider discovery", () => { afterEach(() => { authStorage.close(); if (tempDir && fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true }); + removeSyncWithRetries(tempDir); } }); diff --git a/packages/coding-agent/test/issue-983-multi-file-extension.test.ts b/packages/coding-agent/test/issue-983-multi-file-extension.test.ts index bbced2f92..6237066da 100644 --- a/packages/coding-agent/test/issue-983-multi-file-extension.test.ts +++ b/packages/coding-agent/test/issue-983-multi-file-extension.test.ts @@ -3,6 +3,7 @@ import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; import { discoverAndLoadExtensions } from "@oh-my-pi/pi-coding-agent/extensibility/extensions/loader"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; const TOOL_NAME = "legacy-multi-file-tool"; @@ -10,7 +11,7 @@ describe("issue #983: multi-file legacy Pi extensions", () => { const tempDirs: string[] = []; afterEach(async () => { - await Promise.all(tempDirs.splice(0).map(dir => fs.rm(dir, { recursive: true, force: true }))); + await Promise.all(tempDirs.splice(0).map(dir => removeWithRetries(dir))); }); it("loads legacy Pi extensions whose sibling TypeScript files import each other via relative paths", async () => { diff --git a/packages/coding-agent/test/keybindings-migration.test.ts b/packages/coding-agent/test/keybindings-migration.test.ts index dbcafef51..ec113449f 100644 --- a/packages/coding-agent/test/keybindings-migration.test.ts +++ b/packages/coding-agent/test/keybindings-migration.test.ts @@ -5,6 +5,7 @@ import * as path from "node:path"; import { KeybindingsManager } from "@oh-my-pi/pi-coding-agent/config/keybindings"; import { matchesAppFollowUp } from "@oh-my-pi/pi-coding-agent/modes/utils/keybinding-matchers"; import { setKeybindings } from "@oh-my-pi/pi-tui"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; import { YAML } from "bun"; function ctrl(key: string): string { @@ -55,7 +56,7 @@ describe("KeybindingsManager.create", () => { expect(writtenConfig).not.toHaveProperty("selectModelTemporary"); expect(await Bun.file(jsonPath).exists()).toBe(true); } finally { - await fs.rm(agentDir, { recursive: true, force: true }); + await removeWithRetries(agentDir); } }); @@ -92,7 +93,7 @@ describe("KeybindingsManager.create", () => { }); expect(await Bun.file(jsonPath).exists()).toBe(true); } finally { - await fs.rm(agentDir, { recursive: true, force: true }); + await removeWithRetries(agentDir); } }); @@ -118,7 +119,7 @@ describe("KeybindingsManager.create", () => { expect(manager.getKeys("app.session.fork")).toEqual(["ctrl+f"]); expect(manager.getKeys("app.clipboard.copyPrompt")).toEqual(["alt+c", "ctrl+shift+c"]); } finally { - await fs.rm(agentDir, { recursive: true, force: true }); + await removeWithRetries(agentDir); } }); @@ -144,7 +145,7 @@ describe("KeybindingsManager.create", () => { expect(manager.getKeys("app.plan.toggle")).toEqual(["alt+shift+p"]); expect(await Bun.file(canonicalPath).exists()).toBe(false); } finally { - await fs.rm(agentDir, { recursive: true, force: true }); + await removeWithRetries(agentDir); } }); @@ -184,7 +185,7 @@ describe("KeybindingsManager.create", () => { // of the box, without breaking users on Kitty/iTerm2/WezTerm/Ghostty. expect(manager.getKeys("app.message.followUp")).toEqual(["ctrl+q", "ctrl+enter"]); } finally { - await fs.rm(agentDir, { recursive: true, force: true }); + await removeWithRetries(agentDir); } }); diff --git a/packages/coding-agent/test/lm-studio-fix.test.ts b/packages/coding-agent/test/lm-studio-fix.test.ts index 87a6b102e..209644d34 100644 --- a/packages/coding-agent/test/lm-studio-fix.test.ts +++ b/packages/coding-agent/test/lm-studio-fix.test.ts @@ -5,7 +5,7 @@ import * as path from "node:path"; import type { FetchImpl } from "@oh-my-pi/pi-ai/types"; import { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry"; import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; describe("ModelRegistry LM Studio Fixes", () => { let tempDir: string; @@ -22,7 +22,7 @@ describe("ModelRegistry LM Studio Fixes", () => { afterEach(() => { authStorage.close(); if (tempDir && fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true }); + removeSyncWithRetries(tempDir); } }); diff --git a/packages/coding-agent/test/lsp-format-options.test.ts b/packages/coding-agent/test/lsp-format-options.test.ts index bf122fb08..be99aec02 100644 --- a/packages/coding-agent/test/lsp-format-options.test.ts +++ b/packages/coding-agent/test/lsp-format-options.test.ts @@ -3,7 +3,7 @@ import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; import { detectIndentFromContent, resolveFormatOptions } from "@oh-my-pi/pi-coding-agent/lsp/format-options"; -import { getProjectDir, Snowflake, setProjectDir } from "@oh-my-pi/pi-utils"; +import { getProjectDir, removeWithRetries, Snowflake, setProjectDir } from "@oh-my-pi/pi-utils"; /** * Regression coverage for issue #2329 — the LSP format-on-write path used to @@ -68,7 +68,7 @@ describe("resolveFormatOptions", () => { afterEach(async () => { setProjectDir(previousProjectDir); - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); }); it("falls back to 2-space indent when no .editorconfig and no content signal exist", () => { diff --git a/packages/coding-agent/test/main-interactive-input.test.ts b/packages/coding-agent/test/main-interactive-input.test.ts index ba7e6adcc..3f1acf42b 100644 --- a/packages/coding-agent/test/main-interactive-input.test.ts +++ b/packages/coding-agent/test/main-interactive-input.test.ts @@ -6,11 +6,12 @@ import { applyResolvedSystemPromptInputs, submitInteractiveInput } from "@oh-my- import type { SubmittedUserInput } from "@oh-my-pi/pi-coding-agent/modes/types"; import type { CreateAgentSessionOptions } from "@oh-my-pi/pi-coding-agent/sdk"; import { discoverTitleSystemPromptFile } from "@oh-my-pi/pi-coding-agent/system-prompt"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; const cleanupDirs: string[] = []; afterEach(async () => { - await Promise.all(cleanupDirs.splice(0).map(dir => fs.rm(dir, { recursive: true, force: true }))); + await Promise.all(cleanupDirs.splice(0).map(dir => removeWithRetries(dir))); }); function createInput(overrides: Partial = {}): SubmittedUserInput { diff --git a/packages/coding-agent/test/marketplace/cache.test.ts b/packages/coding-agent/test/marketplace/cache.test.ts index 681ca591a..047094ded 100644 --- a/packages/coding-agent/test/marketplace/cache.test.ts +++ b/packages/coding-agent/test/marketplace/cache.test.ts @@ -3,7 +3,6 @@ import * as fs from "node:fs"; import * as fsp from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; - import { cachePlugin, cleanOrphanedCache, @@ -12,6 +11,7 @@ import { isValidVersionForCache, removeCachedPlugin, } from "@oh-my-pi/pi-coding-agent/extensibility/plugins/marketplace"; +import { removeSyncWithRetries } from "@oh-my-pi/pi-utils"; // ── Helpers ───────────────────────────────────────────────────────────────── @@ -107,7 +107,7 @@ describe("cachePlugin, isCached, removeCachedPlugin", () => { }); afterEach(() => { - fs.rmSync(tmpDir, { recursive: true, force: true }); + removeSyncWithRetries(tmpDir); }); it("isCached returns false before caching", async () => { @@ -174,7 +174,7 @@ describe("cleanOrphanedCache", () => { }); afterEach(() => { - fs.rmSync(tmpDir, { recursive: true, force: true }); + removeSyncWithRetries(tmpDir); }); it("returns { removed: 0 } when cacheDir does not exist", async () => { diff --git a/packages/coding-agent/test/marketplace/discovery.test.ts b/packages/coding-agent/test/marketplace/discovery.test.ts index 8356e014f..2b99dc503 100644 --- a/packages/coding-agent/test/marketplace/discovery.test.ts +++ b/packages/coding-agent/test/marketplace/discovery.test.ts @@ -26,6 +26,7 @@ import { readInstalledPluginsRegistry, writeInstalledPluginsRegistry, } from "@oh-my-pi/pi-coding-agent/extensibility/plugins/marketplace"; +import { removeSyncWithRetries } from "@oh-my-pi/pi-utils"; // ── Inline validator ─────────────────────────────────────────────────────────── // @@ -79,7 +80,7 @@ beforeEach(() => { }); afterEach(() => { - fs.rmSync(tmpHome, { recursive: true, force: true }); + removeSyncWithRetries(tmpHome); }); // ── Path contract ───────────────────────────────────────────────────────────── diff --git a/packages/coding-agent/test/marketplace/fetcher.test.ts b/packages/coding-agent/test/marketplace/fetcher.test.ts index b4a1dc6fd..e72c84f1e 100644 --- a/packages/coding-agent/test/marketplace/fetcher.test.ts +++ b/packages/coding-agent/test/marketplace/fetcher.test.ts @@ -2,12 +2,12 @@ import { afterEach, beforeEach, describe, expect, it } from "bun:test"; import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; - import { classifySource, fetchMarketplace, parseMarketplaceCatalog, } from "@oh-my-pi/pi-coding-agent/extensibility/plugins/marketplace"; +import { removeSyncWithRetries } from "@oh-my-pi/pi-utils"; // Fixture lives at test/marketplace/fixtures/valid-marketplace/ const FIXTURE_DIR = path.join(import.meta.dir, "fixtures", "valid-marketplace"); @@ -156,7 +156,7 @@ describe("fetchMarketplace", () => { }); afterEach(() => { - fs.rmSync(tmpDir, { recursive: true, force: true }); + removeSyncWithRetries(tmpDir); }); it("resolves catalog from fixture directory", async () => { diff --git a/packages/coding-agent/test/marketplace/manager.test.ts b/packages/coding-agent/test/marketplace/manager.test.ts index 0394c9f59..9c52894e7 100644 --- a/packages/coding-agent/test/marketplace/manager.test.ts +++ b/packages/coding-agent/test/marketplace/manager.test.ts @@ -8,6 +8,7 @@ import { MarketplaceManager, readInstalledPluginsRegistry, } from "@oh-my-pi/pi-coding-agent/extensibility/plugins/marketplace"; +import { removeSyncWithRetries } from "@oh-my-pi/pi-utils"; // Minimal marketplace fixture, built once into a temp dir (see beforeAll). It carries only // what these tests assert — one plugin entry plus a plugin.json for the version-fallback path — @@ -99,7 +100,7 @@ describe("MarketplaceManager", () => { }); afterAll(() => { - fs.rmSync(FIXTURE_DIR, { recursive: true, force: true }); + removeSyncWithRetries(FIXTURE_DIR); }); beforeEach(() => { @@ -107,7 +108,7 @@ describe("MarketplaceManager", () => { }); afterEach(() => { - fs.rmSync(ctx.tmpDir, { recursive: true, force: true }); + removeSyncWithRetries(ctx.tmpDir); }); // ── Marketplace lifecycle ────────────────────────────────────────────── @@ -570,7 +571,7 @@ describe("MarketplaceManager", () => { noProjectManager.installPlugin("hello-plugin", "test-marketplace", { scope: "project" }), ).rejects.toThrow(/project directory/); } finally { - fs.rmSync(tmp, { recursive: true, force: true }); + removeSyncWithRetries(tmp); } }); diff --git a/packages/coding-agent/test/marketplace/project-scope.test.ts b/packages/coding-agent/test/marketplace/project-scope.test.ts index 572425029..d8e3e4a5e 100644 --- a/packages/coding-agent/test/marketplace/project-scope.test.ts +++ b/packages/coding-agent/test/marketplace/project-scope.test.ts @@ -24,6 +24,7 @@ import { readInstalledPluginsRegistry, writeInstalledPluginsRegistry, } from "@oh-my-pi/pi-coding-agent/extensibility/plugins/marketplace"; +import { removeSyncWithRetries } from "@oh-my-pi/pi-utils"; // ── Fixtures ────────────────────────────────────────────────────────────────── @@ -47,7 +48,7 @@ describe("resolveActiveProjectRegistryPath", () => { }); afterEach(() => { - fs.rmSync(tmpDir, { recursive: true, force: true }); + removeSyncWithRetries(tmpDir); }); it("walk-up finds nearest .omp/ directory", async () => { @@ -158,8 +159,8 @@ describe("listClaudePluginRoots — project shadows user", () => { afterEach(() => { // Cache is keyed by home:projectPath — must clear between tests. clearClaudePluginRootsCache(); - fs.rmSync(tmpHome, { recursive: true, force: true }); - fs.rmSync(tmpProject, { recursive: true, force: true }); + removeSyncWithRetries(tmpHome); + removeSyncWithRetries(tmpProject); }); it("project entry shadows user entry when plugin IDs match", async () => { diff --git a/packages/coding-agent/test/marketplace/registry.test.ts b/packages/coding-agent/test/marketplace/registry.test.ts index fc261fc8f..9fde36296 100644 --- a/packages/coding-agent/test/marketplace/registry.test.ts +++ b/packages/coding-agent/test/marketplace/registry.test.ts @@ -23,6 +23,7 @@ import { writeInstalledPluginsRegistry, writeMarketplacesRegistry, } from "@oh-my-pi/pi-coding-agent/extensibility/plugins/marketplace"; +import { removeSyncWithRetries } from "@oh-my-pi/pi-utils"; // Inline the parseClaudePluginsRegistry validation logic to avoid pulling // in discovery/helpers.ts which transitively imports @oh-my-pi/pi-natives. @@ -207,7 +208,7 @@ describe("registry file I/O", () => { }); afterEach(() => { - fs.rmSync(tmpDir, { recursive: true, force: true }); + removeSyncWithRetries(tmpDir); }); // ── Marketplaces registry ──────────────────────────────────────── diff --git a/packages/coding-agent/test/marketplace/source-resolver.test.ts b/packages/coding-agent/test/marketplace/source-resolver.test.ts index 071dba90a..8cb599032 100644 --- a/packages/coding-agent/test/marketplace/source-resolver.test.ts +++ b/packages/coding-agent/test/marketplace/source-resolver.test.ts @@ -2,9 +2,9 @@ import { afterEach, beforeEach, describe, expect, it } from "bun:test"; import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; - import type { MarketplacePluginEntry } from "@oh-my-pi/pi-coding-agent/extensibility/plugins/marketplace"; import { resolvePluginSource } from "@oh-my-pi/pi-coding-agent/extensibility/plugins/marketplace"; +import { removeSyncWithRetries } from "@oh-my-pi/pi-utils"; // Fixture: a cloned marketplace with a single plugin at ./plugins/hello-plugin const FIXTURE_DIR = path.resolve(import.meta.dir, "fixtures/valid-marketplace"); @@ -22,7 +22,7 @@ describe("resolvePluginSource", () => { }); afterEach(() => { - fs.rmSync(tmpDir, { recursive: true, force: true }); + removeSyncWithRetries(tmpDir); }); it("resolves relative source to absolute plugin directory", async () => { diff --git a/packages/coding-agent/test/markit-converters.test.ts b/packages/coding-agent/test/markit-converters.test.ts index e1ec31717..b5776a34c 100644 --- a/packages/coding-agent/test/markit-converters.test.ts +++ b/packages/coding-agent/test/markit-converters.test.ts @@ -13,6 +13,7 @@ import * as os from "node:os"; import * as path from "node:path"; import { convertBufferWithMarkit, convertFileWithMarkit } from "@oh-my-pi/pi-coding-agent/utils/markit"; import { zip } from "@oh-my-pi/pi-coding-agent/utils/zip"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; const enc = (s: string): Uint8Array => new TextEncoder().encode(s); const WML = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"; @@ -124,7 +125,7 @@ describe("markit converters", () => { expect(written).toHaveLength(1); expect(result.content).toContain(`](${path.join(imageDir, written[0]!)})`); } finally { - await fs.rm(dir, { recursive: true, force: true }); + await removeWithRetries(dir); } }); diff --git a/packages/coding-agent/test/mcp-command-reauth.test.ts b/packages/coding-agent/test/mcp-command-reauth.test.ts index 7050f1055..9a43e57b0 100644 --- a/packages/coding-agent/test/mcp-command-reauth.test.ts +++ b/packages/coding-agent/test/mcp-command-reauth.test.ts @@ -9,7 +9,14 @@ import * as oauthFlow from "@oh-my-pi/pi-coding-agent/mcp/oauth-flow"; import type { MCPServerConfig } from "@oh-my-pi/pi-coding-agent/mcp/types"; import { MCPCommandController } from "@oh-my-pi/pi-coding-agent/modes/controllers/mcp-command-controller"; import { initTheme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme"; -import { getConfigRootDir, getMCPConfigPath, getProjectDir, setAgentDir, setProjectDir } from "@oh-my-pi/pi-utils"; +import { + getConfigRootDir, + getMCPConfigPath, + getProjectDir, + removeWithRetries, + setAgentDir, + setProjectDir, +} from "@oh-my-pi/pi-utils"; const RAW_SERVER_URL = `https://\${MCP_HOST}/mcp`; const EXPANDED_SERVER_URL = "https://mcp.example.com/mcp"; @@ -126,8 +133,8 @@ describe("/mcp auth commands", () => { setAgentDir(fallbackAgentDir); delete process.env.PI_CODING_AGENT_DIR; } - await fs.rm(projectDir, { recursive: true, force: true }); - await fs.rm(agentDir, { recursive: true, force: true }); + await removeWithRetries(projectDir); + await removeWithRetries(agentDir); }); test("stores definition-only OAuth credentials under the expanded URL key", async () => { diff --git a/packages/coding-agent/test/mcp-command-toggle.test.ts b/packages/coding-agent/test/mcp-command-toggle.test.ts index 5f940ab5f..05593d8e8 100644 --- a/packages/coding-agent/test/mcp-command-toggle.test.ts +++ b/packages/coding-agent/test/mcp-command-toggle.test.ts @@ -6,7 +6,14 @@ import type { SourceMeta } from "@oh-my-pi/pi-coding-agent/capability/types"; import type { MCPServerConfig } from "@oh-my-pi/pi-coding-agent/mcp/types"; import { MCPCommandController } from "@oh-my-pi/pi-coding-agent/modes/controllers/mcp-command-controller"; import { initTheme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme"; -import { getConfigRootDir, getMCPConfigPath, getProjectDir, setAgentDir, setProjectDir } from "@oh-my-pi/pi-utils"; +import { + getConfigRootDir, + getMCPConfigPath, + getProjectDir, + removeWithRetries, + setAgentDir, + setProjectDir, +} from "@oh-my-pi/pi-utils"; const originalProjectDir = getProjectDir(); const originalAgentDir = process.env.PI_CODING_AGENT_DIR; @@ -97,8 +104,8 @@ describe("/mcp enable and disable", () => { vi.restoreAllMocks(); setProjectDir(originalProjectDir); restoreAgentDir(); - await fs.rm(projectDir, { recursive: true, force: true }); - await fs.rm(agentDir, { recursive: true, force: true }); + await removeWithRetries(projectDir); + await removeWithRetries(agentDir); }); test("disabling one configured server does not reload other MCP servers", async () => { diff --git a/packages/coding-agent/test/mcp-connection-status-events.test.ts b/packages/coding-agent/test/mcp-connection-status-events.test.ts index bb32f7f15..034b5fca3 100644 --- a/packages/coding-agent/test/mcp-connection-status-events.test.ts +++ b/packages/coding-agent/test/mcp-connection-status-events.test.ts @@ -5,6 +5,7 @@ import * as path from "node:path"; import { MCPManager } from "@oh-my-pi/pi-coding-agent/mcp/manager"; import type { McpConnectionStatusEvent } from "@oh-my-pi/pi-coding-agent/mcp/startup-events"; import type { MCPServerConfig } from "@oh-my-pi/pi-coding-agent/mcp/types"; +import { removeSyncWithRetries } from "@oh-my-pi/pi-utils"; const FIXTURE_PATH = path.join(import.meta.dir, "fixtures", "many-tools-mcp.ts"); const BUN_EXEC = process.execPath; @@ -17,7 +18,7 @@ describe("MCPManager connection status events", () => { }); afterEach(() => { - fs.rmSync(workDir, { recursive: true, force: true }); + removeSyncWithRetries(workDir); }); it("emits connecting, connected, and failed updates for startup status", async () => { diff --git a/packages/coding-agent/test/mcp-discovered-server-reauth.test.ts b/packages/coding-agent/test/mcp-discovered-server-reauth.test.ts index 0f276b81d..51acc83af 100644 --- a/packages/coding-agent/test/mcp-discovered-server-reauth.test.ts +++ b/packages/coding-agent/test/mcp-discovered-server-reauth.test.ts @@ -21,6 +21,7 @@ import { describe, expect, it } from "bun:test"; import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; +import { removeSyncWithRetries } from "@oh-my-pi/pi-utils"; import type { SourceMeta } from "../src/capability/types"; import { readMCPConfigFile, updateMCPServer, validateServerName } from "../src/mcp/config-writer"; import { MCPManager } from "../src/mcp/manager"; @@ -63,7 +64,7 @@ describe("MCP discovered-server reauth", () => { expect(manager.getSource(NAMESPACED_NAME)).toEqual(source); } finally { await manager.disconnectAll(); - fs.rmSync(workDir, { recursive: true, force: true }); + removeSyncWithRetries(workDir); } }, 15_000); @@ -101,7 +102,7 @@ describe("MCP discovered-server reauth", () => { const readBack = await readMCPConfigFile(filePath); expect(readBack.mcpServers?.[NAMESPACED_NAME]).toEqual(persisted); } finally { - fs.rmSync(workDir, { recursive: true, force: true }); + removeSyncWithRetries(workDir); } }); }); diff --git a/packages/coding-agent/test/mcp-dispose-disconnect-bounded.test.ts b/packages/coding-agent/test/mcp-dispose-disconnect-bounded.test.ts index eecff1787..c1152a67a 100644 --- a/packages/coding-agent/test/mcp-dispose-disconnect-bounded.test.ts +++ b/packages/coding-agent/test/mcp-dispose-disconnect-bounded.test.ts @@ -23,7 +23,7 @@ import { afterEach, beforeEach, describe, expect, it } from "bun:test"; import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; -import { withTimeout } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, withTimeout } from "@oh-my-pi/pi-utils"; import { MCPManager } from "../src/mcp/manager"; import type { MCPStdioServerConfig } from "../src/mcp/types"; @@ -38,7 +38,7 @@ describe("owned-manager dispose disconnect is bounded (PR #2839)", () => { }); afterEach(() => { - fs.rmSync(workDir, { recursive: true, force: true }); + removeSyncWithRetries(workDir); }); it("bounds the owned disconnect when a transport close stalls", async () => { diff --git a/packages/coding-agent/test/mcp-reconnect-storm.test.ts b/packages/coding-agent/test/mcp-reconnect-storm.test.ts index 73682cf73..5f984bc9b 100644 --- a/packages/coding-agent/test/mcp-reconnect-storm.test.ts +++ b/packages/coding-agent/test/mcp-reconnect-storm.test.ts @@ -18,6 +18,7 @@ import * as os from "node:os"; import * as path from "node:path"; import { MCPManager } from "@oh-my-pi/pi-coding-agent/mcp/manager"; import type { MCPStdioServerConfig } from "@oh-my-pi/pi-coding-agent/mcp/types"; +import { removeSyncWithRetries } from "@oh-my-pi/pi-utils"; const FIXTURE_PATH = path.join(import.meta.dir, "fixtures", "crash-after-init-mcp.ts"); const BUN_EXEC = process.execPath; @@ -33,7 +34,7 @@ describe("MCP reconnect storm (issue #1592)", () => { }); afterEach(() => { - fs.rmSync(workDir, { recursive: true, force: true }); + removeSyncWithRetries(workDir); }); function countSpawns(): number { diff --git a/packages/coding-agent/test/mcp-resource-templates-missing.test.ts b/packages/coding-agent/test/mcp-resource-templates-missing.test.ts index c94fe6f08..72eca394b 100644 --- a/packages/coding-agent/test/mcp-resource-templates-missing.test.ts +++ b/packages/coding-agent/test/mcp-resource-templates-missing.test.ts @@ -14,6 +14,7 @@ import { afterEach, beforeEach, describe, expect, it } from "bun:test"; import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; +import { removeSyncWithRetries } from "@oh-my-pi/pi-utils"; import { listResourceTemplates } from "../src/mcp/client"; import { MCPManager } from "../src/mcp/manager"; import type { MCPServerConnection, MCPStdioServerConfig, MCPTransport } from "../src/mcp/types"; @@ -79,7 +80,7 @@ describe("MCPManager loads resources for a templates-less server", () => { }); afterEach(() => { - fs.rmSync(workDir, { recursive: true, force: true }); + removeSyncWithRetries(workDir); }); it("keeps concrete resources when resources/templates/list is unimplemented", async () => { diff --git a/packages/coding-agent/test/mcp-startup-no-block.test.ts b/packages/coding-agent/test/mcp-startup-no-block.test.ts index cce80f621..e9d21ac07 100644 --- a/packages/coding-agent/test/mcp-startup-no-block.test.ts +++ b/packages/coding-agent/test/mcp-startup-no-block.test.ts @@ -20,6 +20,7 @@ import { afterEach, beforeEach, describe, expect, it } from "bun:test"; import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; +import { removeSyncWithRetries } from "@oh-my-pi/pi-utils"; import { MCPManager } from "../src/mcp/manager"; import type { MCPStdioServerConfig } from "../src/mcp/types"; @@ -34,7 +35,7 @@ describe("MCP startup (issue #2100)", () => { }); afterEach(() => { - fs.rmSync(workDir, { recursive: true, force: true }); + removeSyncWithRetries(workDir); }); it("returns promptly when a configured MCP server stalls on initialize", async () => { diff --git a/packages/coding-agent/test/mcp-stdio-transport.test.ts b/packages/coding-agent/test/mcp-stdio-transport.test.ts index 86ec04bdc..f9cc753ae 100644 --- a/packages/coding-agent/test/mcp-stdio-transport.test.ts +++ b/packages/coding-agent/test/mcp-stdio-transport.test.ts @@ -2,8 +2,8 @@ import { afterEach, describe, expect, it } from "bun:test"; import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; - import { resolveStdioSpawnCommand, StdioTransport, writeFrame } from "@oh-my-pi/pi-coding-agent/mcp/transports/stdio"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; describe("resolveStdioSpawnCommand", () => { it("resolves bare Windows commands through PATHEXT and wraps .cmd shims with cmd.exe", async () => { @@ -35,7 +35,7 @@ describe("resolveStdioSpawnCommand", () => { expect(result.windowsHide).toBe(true); expect(result.detached).toBe(false); } finally { - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); } }); @@ -65,8 +65,8 @@ describe("resolveStdioSpawnCommand", () => { expect(result.windowsHide).toBe(true); expect(result.detached).toBe(false); } finally { - await fs.rm(projectDir, { recursive: true, force: true }); - await fs.rm(globalDir, { recursive: true, force: true }); + await removeWithRetries(projectDir); + await removeWithRetries(globalDir); } }); @@ -117,7 +117,7 @@ describe("resolveStdioSpawnCommand", () => { expect(result.windowsHide).toBe(false); expect(result.detached).toBe(false); } finally { - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); } }); @@ -160,7 +160,7 @@ describe("resolveStdioSpawnCommand", () => { expect(result.windowsHide).toBe(true); expect(result.detached).toBe(false); } finally { - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); } }); @@ -193,7 +193,7 @@ describe("resolveStdioSpawnCommand", () => { expect(result.windowsHide).toBe(true); expect(result.detached).toBe(false); } finally { - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); } }); @@ -226,7 +226,7 @@ describe("resolveStdioSpawnCommand", () => { expect(result.windowsHide).toBe(true); expect(result.detached).toBe(false); } finally { - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); } }); @@ -267,7 +267,7 @@ describe("resolveStdioSpawnCommand", () => { expect(result.windowsHide).toBe(true); expect(result.detached).toBe(false); } finally { - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); } }); diff --git a/packages/coding-agent/test/memories/instructions.test.ts b/packages/coding-agent/test/memories/instructions.test.ts index ce35d1f77..a5dd03236 100644 --- a/packages/coding-agent/test/memories/instructions.test.ts +++ b/packages/coding-agent/test/memories/instructions.test.ts @@ -4,13 +4,14 @@ import * as os from "node:os"; import * as path from "node:path"; import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { buildMemoryToolDeveloperInstructions, getMemoryRoot } from "@oh-my-pi/pi-coding-agent/memories"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; async function withTempDir(fn: (dir: string) => Promise): Promise { const dir = await fs.mkdtemp(path.join(os.tmpdir(), "memory-instructions-")); try { return await fn(dir); } finally { - await fs.rm(dir, { recursive: true, force: true }); + await removeWithRetries(dir); } } diff --git a/packages/coding-agent/test/mnemopi-bank-derivation.test.ts b/packages/coding-agent/test/mnemopi-bank-derivation.test.ts index 70bbb1cb4..8961f0c4c 100644 --- a/packages/coding-agent/test/mnemopi-bank-derivation.test.ts +++ b/packages/coding-agent/test/mnemopi-bank-derivation.test.ts @@ -4,7 +4,7 @@ import { mkdirSync } from "node:fs"; import * as fs from "node:fs/promises"; import * as path from "node:path"; import { computeMnemopiBankScope, extendRecallWithLegacyBanks } from "@oh-my-pi/pi-coding-agent/mnemopi/config"; -import { TempDir } from "@oh-my-pi/pi-utils"; +import { removeWithRetries, TempDir } from "@oh-my-pi/pi-utils"; // Set up a fixture filesystem we can reuse across the two regression // suites — same shape as `~/.omp/memories/mnemopi/` on a real install. @@ -72,7 +72,7 @@ describe("computeMnemopiBankScope (#2412)", () => { const withAncestorGit = computeMnemopiBankScope(undefined, project, "per-project").bank; expect(withAncestorGit).toBe(withoutGit); - await fs.rm(baseDir.join("projects", ".git")); + await removeWithRetries(baseDir.join("projects", ".git")); const afterGitRemoved = computeMnemopiBankScope(undefined, project, "per-project").bank; expect(afterGitRemoved).toBe(withoutGit); } finally { diff --git a/packages/coding-agent/test/model-discovery.test.ts b/packages/coding-agent/test/model-discovery.test.ts index 043bbe2d3..679ebf076 100644 --- a/packages/coding-agent/test/model-discovery.test.ts +++ b/packages/coding-agent/test/model-discovery.test.ts @@ -9,7 +9,7 @@ import type { OpenAICompat } from "@oh-my-pi/pi-catalog/types"; import { kNoAuth, ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry"; import { resetSettingsForTest } from "@oh-my-pi/pi-coding-agent/config/settings"; import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; describe("ModelRegistry runtime discovery", () => { let tempDir: string; @@ -57,7 +57,7 @@ describe("ModelRegistry runtime discovery", () => { } authStorage.close(); if (tempDir && fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true }); + removeSyncWithRetries(tempDir); } }); diff --git a/packages/coding-agent/test/model-registry-command-values.test.ts b/packages/coding-agent/test/model-registry-command-values.test.ts index ef7df83d3..10b8a6ca4 100644 --- a/packages/coding-agent/test/model-registry-command-values.test.ts +++ b/packages/coding-agent/test/model-registry-command-values.test.ts @@ -4,7 +4,7 @@ import * as os from "node:os"; import * as path from "node:path"; import { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry"; import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; function stdoutCommand(value: string): string { return `${JSON.stringify(process.execPath)} -e ${JSON.stringify(`process.stdout.write(${JSON.stringify(value)})`)}`; @@ -26,7 +26,7 @@ describe("ModelRegistry command-resolved models.yml values", () => { authStorage.close(); if (!tempDir || !fs.existsSync(tempDir)) return; try { - fs.rmSync(tempDir, { recursive: true, force: true }); + removeSyncWithRetries(tempDir); } catch (error) { if ((error as NodeJS.ErrnoException).code !== "EBUSY") throw error; } diff --git a/packages/coding-agent/test/model-registry-runtime-cleanup.test.ts b/packages/coding-agent/test/model-registry-runtime-cleanup.test.ts index 538849cf7..26d0c2757 100644 --- a/packages/coding-agent/test/model-registry-runtime-cleanup.test.ts +++ b/packages/coding-agent/test/model-registry-runtime-cleanup.test.ts @@ -5,7 +5,7 @@ import * as path from "node:path"; import { type AssistantMessageEventStream, clearCustomApis, getCustomApi } from "@oh-my-pi/pi-ai"; import { ModelRegistry, type ProviderConfigInput } from "@oh-my-pi/pi-coding-agent/config/model-registry"; import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; describe("ModelRegistry runtime source cleanup", () => { let tempDir: string; @@ -37,7 +37,7 @@ describe("ModelRegistry runtime source cleanup", () => { clearCustomApis(); authStorage.close(); if (tempDir && fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true, force: true }); + removeSyncWithRetries(tempDir); } }); diff --git a/packages/coding-agent/test/model-registry-runtime-provider.test.ts b/packages/coding-agent/test/model-registry-runtime-provider.test.ts index 8e8e76ff6..7563ea3f8 100644 --- a/packages/coding-agent/test/model-registry-runtime-provider.test.ts +++ b/packages/coding-agent/test/model-registry-runtime-provider.test.ts @@ -13,7 +13,7 @@ import { getOAuthProviders, unregisterOAuthProviders } from "@oh-my-pi/pi-ai/oau import type { OAuthCredentials } from "@oh-my-pi/pi-ai/oauth/types"; import { ModelRegistry, type ProviderConfigInput } from "@oh-my-pi/pi-coding-agent/config/model-registry"; import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; describe("ModelRegistry runtime provider registration", () => { let tempDir: string; @@ -44,7 +44,7 @@ describe("ModelRegistry runtime provider registration", () => { } authStorage.close(); if (tempDir && fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true, force: true }); + removeSyncWithRetries(tempDir); } }); diff --git a/packages/coding-agent/test/model-registry.test.ts b/packages/coding-agent/test/model-registry.test.ts index 78e9764c0..76435f52d 100644 --- a/packages/coding-agent/test/model-registry.test.ts +++ b/packages/coding-agent/test/model-registry.test.ts @@ -9,7 +9,7 @@ import { writeModelCache } from "@oh-my-pi/pi-catalog/model-cache"; import { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry"; import { resetSettingsForTest, Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; describe("ModelRegistry", () => { let tempDir: string; @@ -67,7 +67,7 @@ describe("ModelRegistry", () => { } authStorage.close(); if (tempDir && fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true }); + removeSyncWithRetries(tempDir); } }); @@ -95,7 +95,7 @@ describe("ModelRegistry", () => { afterAll(() => { sharedAuth.close(); - fs.rmSync(sharedDir, { recursive: true, force: true }); + removeSyncWithRetries(sharedDir); if (bootOllamaBaseUrl === undefined) delete Bun.env.OLLAMA_BASE_URL; else Bun.env.OLLAMA_BASE_URL = bootOllamaBaseUrl; if (bootOllamaHost === undefined) delete Bun.env.OLLAMA_HOST; diff --git a/packages/coding-agent/test/modes/controllers/omfg-controller.test.ts b/packages/coding-agent/test/modes/controllers/omfg-controller.test.ts index d6442e7b1..794c8860d 100644 --- a/packages/coding-agent/test/modes/controllers/omfg-controller.test.ts +++ b/packages/coding-agent/test/modes/controllers/omfg-controller.test.ts @@ -9,6 +9,7 @@ import { OmfgController } from "@oh-my-pi/pi-coding-agent/modes/controllers/omfg import { initTheme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme"; import type { InteractiveModeContext } from "@oh-my-pi/pi-coding-agent/modes/types"; import { Container, type TUI } from "@oh-my-pi/pi-tui"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; const PROJECT_OPTION = "This project (.omp/rules)"; const GLOBAL_OPTION = "Global — all projects (~/.omp/agent/rules)"; @@ -165,7 +166,7 @@ afterEach(async () => { while (tempRoots.length > 0) { const root = tempRoots.pop(); if (root) { - await fs.rm(root, { recursive: true, force: true }); + await removeWithRetries(root); } } }); diff --git a/packages/coding-agent/test/modes/controllers/todo-command-controller.test.ts b/packages/coding-agent/test/modes/controllers/todo-command-controller.test.ts index 562d09a5b..c5171c1db 100644 --- a/packages/coding-agent/test/modes/controllers/todo-command-controller.test.ts +++ b/packages/coding-agent/test/modes/controllers/todo-command-controller.test.ts @@ -5,6 +5,7 @@ import * as path from "node:path"; import { TodoCommandController } from "@oh-my-pi/pi-coding-agent/modes/controllers/todo-command-controller"; import type { InteractiveModeContext } from "@oh-my-pi/pi-coding-agent/modes/types"; import { type TodoPhase, USER_TODO_EDIT_CUSTOM_TYPE } from "@oh-my-pi/pi-coding-agent/tools"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; function createContext(cwd: string, phases: TodoPhase[]): InteractiveModeContext { return { @@ -32,7 +33,7 @@ describe("TodoCommandController", () => { let tempRoot = ""; afterEach(async () => { - if (tempRoot) await fs.rm(tempRoot, { recursive: true, force: true }); + if (tempRoot) await removeWithRetries(tempRoot); tempRoot = ""; }); diff --git a/packages/coding-agent/test/plan-mode/plan-handoff.test.ts b/packages/coding-agent/test/plan-mode/plan-handoff.test.ts index 7727c929a..000a44eb3 100644 --- a/packages/coding-agent/test/plan-mode/plan-handoff.test.ts +++ b/packages/coding-agent/test/plan-mode/plan-handoff.test.ts @@ -4,6 +4,7 @@ import * as os from "node:os"; import * as path from "node:path"; import type { LocalProtocolOptions } from "@oh-my-pi/pi-coding-agent/internal-urls"; import { loadOverallPlanReference } from "@oh-my-pi/pi-coding-agent/plan-mode/plan-handoff"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; describe("loadOverallPlanReference", () => { let tmpDir: string; @@ -21,7 +22,7 @@ describe("loadOverallPlanReference", () => { }); afterEach(async () => { - await fs.rm(tmpDir, { recursive: true, force: true }); + await removeWithRetries(tmpDir); }); it("returns the plan path and full contents when the reference file exists", async () => { diff --git a/packages/coding-agent/test/plugin-config.test.ts b/packages/coding-agent/test/plugin-config.test.ts index 7f3c5726e..208a9cdb7 100644 --- a/packages/coding-agent/test/plugin-config.test.ts +++ b/packages/coding-agent/test/plugin-config.test.ts @@ -4,6 +4,7 @@ import * as os from "node:os"; import * as path from "node:path"; import { PluginManager } from "@oh-my-pi/pi-coding-agent/extensibility/plugins/manager"; import * as piUtils from "@oh-my-pi/pi-utils"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; describe("plugin config", () => { let tmpRoot: string; @@ -23,7 +24,7 @@ describe("plugin config", () => { afterEach(async () => { mock.restore(); - await fs.rm(tmpRoot, { recursive: true, force: true }); + await removeWithRetries(tmpRoot); }); async function writeLegacyLockfile(pluginName: string): Promise { diff --git a/packages/coding-agent/test/plugin-extensions-discovery.test.ts b/packages/coding-agent/test/plugin-extensions-discovery.test.ts index b309b8b26..bbfccbef7 100644 --- a/packages/coding-agent/test/plugin-extensions-discovery.test.ts +++ b/packages/coding-agent/test/plugin-extensions-discovery.test.ts @@ -3,7 +3,7 @@ import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; import { discoverAndLoadExtensions } from "@oh-my-pi/pi-coding-agent/extensibility/extensions/loader"; -import { getAgentDir, getPluginsDir, setAgentDir, TempDir } from "@oh-my-pi/pi-utils"; +import { getAgentDir, getPluginsDir, removeSyncWithRetries, setAgentDir, TempDir } from "@oh-my-pi/pi-utils"; const currentPiCodingAgentPath = Bun.resolveSync("@oh-my-pi/pi-coding-agent", import.meta.dir); const currentPiExtensionsPath = Bun.resolveSync("@oh-my-pi/pi-coding-agent/extensibility/extensions", import.meta.dir); @@ -81,7 +81,7 @@ describe("plugin extension discovery", () => { } originalXdg.clear(); setAgentDir(originalAgentDir); - fs.rmSync(tempHome, { recursive: true, force: true }); + removeSyncWithRetries(tempHome); }); it("loads installed plugin extensions declared in package.json", async () => { @@ -97,7 +97,7 @@ describe("plugin extension discovery", () => { const pluginsDir = getPluginsDir(); const pluginDir = path.join(pluginsDir, "node_modules", "legacy-pi-plugin"); const extensionPath = path.join(pluginDir, "dist", "extension.ts"); - fs.rmSync(path.join(pluginsDir, "node_modules"), { recursive: true, force: true }); + removeSyncWithRetries(path.join(pluginsDir, "node_modules")); fs.mkdirSync(path.dirname(extensionPath), { recursive: true }); fs.writeFileSync( path.join(pluginsDir, "package.json"), @@ -160,7 +160,7 @@ describe("plugin extension discovery", () => { const pluginsDir = getPluginsDir(); const pluginDir = path.join(pluginsDir, "node_modules", "package-import-plugin"); const extensionPath = path.join(pluginDir, "src", "index.ts"); - fs.rmSync(path.join(pluginsDir, "node_modules"), { recursive: true, force: true }); + removeSyncWithRetries(path.join(pluginsDir, "node_modules")); fs.mkdirSync(path.join(pluginDir, "src", "feature"), { recursive: true }); fs.writeFileSync( path.join(pluginsDir, "package.json"), @@ -218,7 +218,7 @@ describe("plugin extension discovery", () => { const pluginsDir = getPluginsDir(); const pluginDir = path.join(pluginsDir, "node_modules", "conditional-import-plugin"); const extensionPath = path.join(pluginDir, "src", "index.ts"); - fs.rmSync(path.join(pluginsDir, "node_modules"), { recursive: true, force: true }); + removeSyncWithRetries(path.join(pluginsDir, "node_modules")); fs.mkdirSync(path.join(pluginDir, "node"), { recursive: true }); fs.mkdirSync(path.join(pluginDir, "import"), { recursive: true }); fs.writeFileSync( @@ -280,7 +280,7 @@ describe("plugin extension discovery", () => { const pluginsDir = getPluginsDir(); const pluginDir = path.join(pluginsDir, "node_modules", "json-import-plugin"); const extensionPath = path.join(pluginDir, "src", "index.ts"); - fs.rmSync(path.join(pluginsDir, "node_modules"), { recursive: true, force: true }); + removeSyncWithRetries(path.join(pluginsDir, "node_modules")); fs.mkdirSync(path.join(pluginDir, "src"), { recursive: true }); fs.writeFileSync( path.join(pluginsDir, "package.json"), @@ -329,7 +329,7 @@ describe("plugin extension discovery", () => { const pluginsDir = getPluginsDir(); const pluginDir = path.join(pluginsDir, "node_modules", "null-exact-import-plugin"); const extensionPath = path.join(pluginDir, "src", "index.ts"); - fs.rmSync(path.join(pluginsDir, "node_modules"), { recursive: true, force: true }); + removeSyncWithRetries(path.join(pluginsDir, "node_modules")); fs.mkdirSync(path.join(pluginDir, "src"), { recursive: true }); fs.writeFileSync( path.join(pluginsDir, "package.json"), @@ -379,7 +379,7 @@ describe("plugin extension discovery", () => { const pluginsDir = getPluginsDir(); const pluginDir = path.join(pluginsDir, "node_modules", "null-conditional-import-plugin"); const extensionPath = path.join(pluginDir, "src", "index.ts"); - fs.rmSync(path.join(pluginsDir, "node_modules"), { recursive: true, force: true }); + removeSyncWithRetries(path.join(pluginsDir, "node_modules")); fs.mkdirSync(path.join(pluginDir, "src"), { recursive: true }); fs.writeFileSync( path.join(pluginsDir, "package.json"), @@ -431,7 +431,7 @@ describe("plugin extension discovery", () => { const pluginsDir = getPluginsDir(); const pluginDir = path.join(pluginsDir, "node_modules", "side-effect-plugin"); const extensionPath = path.join(pluginDir, "src", "index.ts"); - fs.rmSync(path.join(pluginsDir, "node_modules"), { recursive: true, force: true }); + removeSyncWithRetries(path.join(pluginsDir, "node_modules")); fs.mkdirSync(path.join(pluginDir, "src"), { recursive: true }); fs.writeFileSync( path.join(pluginsDir, "package.json"), @@ -511,7 +511,7 @@ describe("plugin extension discovery", () => { const pluginDir = path.join(pluginsDir, "node_modules", "dir-entry-plugin"); const extensionDir = path.join(pluginDir, ".pi", "extensions", "dir-entry"); const extensionPath = path.join(extensionDir, "index.ts"); - fs.rmSync(path.join(pluginsDir, "node_modules"), { recursive: true, force: true }); + removeSyncWithRetries(path.join(pluginsDir, "node_modules")); fs.mkdirSync(extensionDir, { recursive: true }); fs.writeFileSync( path.join(pluginsDir, "package.json"), @@ -557,7 +557,7 @@ describe("plugin extension discovery", () => { const pluginDir = path.join(pluginsDir, "node_modules", "subdir-entry-plugin"); const extensionDir = path.join(pluginDir, "extensions", "feature"); const extensionPath = path.join(extensionDir, "index.ts"); - fs.rmSync(path.join(pluginsDir, "node_modules"), { recursive: true, force: true }); + removeSyncWithRetries(path.join(pluginsDir, "node_modules")); fs.mkdirSync(extensionDir, { recursive: true }); fs.writeFileSync( path.join(pluginsDir, "package.json"), @@ -604,7 +604,7 @@ describe("plugin extension discovery", () => { const pluginDir = path.join(pluginsDir, "node_modules", "nested-manifest-plugin"); const featureDir = path.join(pluginDir, "extensions", "feature"); const realEntry = path.join(featureDir, "dist", "real-ext.ts"); - fs.rmSync(path.join(pluginsDir, "node_modules"), { recursive: true, force: true }); + removeSyncWithRetries(path.join(pluginsDir, "node_modules")); fs.mkdirSync(path.dirname(realEntry), { recursive: true }); fs.writeFileSync( path.join(pluginsDir, "package.json"), @@ -662,7 +662,7 @@ describe("plugin extension discovery", () => { const pluginsDir = getPluginsDir(); const pluginDir = path.join(pluginsDir, "node_modules", "missing-decl-plugin"); const featureDir = path.join(pluginDir, "extensions", "feature"); - fs.rmSync(path.join(pluginsDir, "node_modules"), { recursive: true, force: true }); + removeSyncWithRetries(path.join(pluginsDir, "node_modules")); fs.mkdirSync(featureDir, { recursive: true }); fs.writeFileSync( path.join(pluginsDir, "package.json"), @@ -708,7 +708,7 @@ describe("plugin extension discovery", () => { const pluginDir = path.join(pluginsDir, "node_modules", "dts-plugin"); const extensionsDir = path.join(pluginDir, "extensions"); const moduleEntry = path.join(extensionsDir, "ext.js"); - fs.rmSync(path.join(pluginsDir, "node_modules"), { recursive: true, force: true }); + removeSyncWithRetries(path.join(pluginsDir, "node_modules")); fs.mkdirSync(extensionsDir, { recursive: true }); fs.writeFileSync( path.join(pluginsDir, "package.json"), diff --git a/packages/coding-agent/test/plugin-install-git.test.ts b/packages/coding-agent/test/plugin-install-git.test.ts index 9420202c5..3a5349d5e 100644 --- a/packages/coding-agent/test/plugin-install-git.test.ts +++ b/packages/coding-agent/test/plugin-install-git.test.ts @@ -20,6 +20,7 @@ import * as os from "node:os"; import * as path from "node:path"; import { PluginManager } from "@oh-my-pi/pi-coding-agent/extensibility/plugins/manager"; import * as piUtils from "@oh-my-pi/pi-utils"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; import type { Subprocess } from "bun"; function emptyStream(): ReadableStream { @@ -53,7 +54,7 @@ describe("PluginManager.install with git sources", () => { afterEach(async () => { vi.restoreAllMocks(); - await fs.rm(tmpRoot, { recursive: true, force: true }); + await removeWithRetries(tmpRoot); }); test("installs from github: shorthand and resolves real package name from deps diff", async () => { diff --git a/packages/coding-agent/test/plugin-install-local.test.ts b/packages/coding-agent/test/plugin-install-local.test.ts index bd743880c..a2d365ee5 100644 --- a/packages/coding-agent/test/plugin-install-local.test.ts +++ b/packages/coding-agent/test/plugin-install-local.test.ts @@ -21,6 +21,7 @@ import { PluginManager } from "@oh-my-pi/pi-coding-agent/extensibility/plugins/m import { MarketplaceManager } from "@oh-my-pi/pi-coding-agent/extensibility/plugins/marketplace"; import type { InstalledPlugin } from "@oh-my-pi/pi-coding-agent/extensibility/plugins/types"; import * as piUtils from "@oh-my-pi/pi-utils"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; const FAKE_INSTALLED: InstalledPlugin = { name: "kimi-datasource", @@ -74,7 +75,7 @@ describe("runPluginCommand({ action: 'install', args: [] })", () => { // stubs leak into sibling test files (e.g. marketplace/manager.test.ts // breaks because listMarketplaces() still returns []). mock.restore(); - await fs.rm(tmpRoot, { recursive: true, force: true }); + await removeWithRetries(tmpRoot); }); for (const spec of [".", "./pkg", "../pkg", "/abs/pkg", "~/pkg"]) { diff --git a/packages/coding-agent/test/plugin-install-validation.test.ts b/packages/coding-agent/test/plugin-install-validation.test.ts index 39ce2f601..64157efc4 100644 --- a/packages/coding-agent/test/plugin-install-validation.test.ts +++ b/packages/coding-agent/test/plugin-install-validation.test.ts @@ -4,6 +4,7 @@ import * as os from "node:os"; import * as path from "node:path"; import { PluginManager } from "@oh-my-pi/pi-coding-agent/extensibility/plugins/manager"; import * as piUtils from "@oh-my-pi/pi-utils"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; import type { Subprocess } from "bun"; function emptyStream(): ReadableStream { @@ -64,7 +65,7 @@ describe("PluginManager.install load validation", () => { afterEach(async () => { vi.restoreAllMocks(); - await fs.rm(tmpRoot, { recursive: true, force: true }); + await removeWithRetries(tmpRoot); }); test("rejects an install whose extension entry cannot resolve its dependencies", async () => { diff --git a/packages/coding-agent/test/plugin-manifest-paths.test.ts b/packages/coding-agent/test/plugin-manifest-paths.test.ts index 2b74a5a73..55080d094 100644 --- a/packages/coding-agent/test/plugin-manifest-paths.test.ts +++ b/packages/coding-agent/test/plugin-manifest-paths.test.ts @@ -7,6 +7,7 @@ import { resolvePluginToolPaths, } from "@oh-my-pi/pi-coding-agent/extensibility/plugins/loader"; import type { InstalledPlugin, PluginManifest } from "@oh-my-pi/pi-coding-agent/extensibility/plugins/types"; +import { removeSyncWithRetries } from "@oh-my-pi/pi-utils"; function makePlugin(pluginPath: string, manifest: PluginManifest): InstalledPlugin { return { @@ -43,7 +44,7 @@ describe("plugin manifest path resolution", () => { expect(resolvePluginToolPaths(plugin)).toEqual([path.join(dir, "index.ts")]); expect(resolvePluginExtensionPaths(plugin)).toEqual([path.join(dir, "ext.ts")]); } finally { - fs.rmSync(dir, { recursive: true, force: true }); + removeSyncWithRetries(dir); } }); }); diff --git a/packages/coding-agent/test/profile-cli.test.ts b/packages/coding-agent/test/profile-cli.test.ts index 00232cad1..64c3f7aea 100644 --- a/packages/coding-agent/test/profile-cli.test.ts +++ b/packages/coding-agent/test/profile-cli.test.ts @@ -3,6 +3,7 @@ import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; import * as url from "node:url"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; import { __resetProfileSnapshotForTests, APP_NAME, @@ -89,7 +90,7 @@ describe("global --profile flag", () => { } __resetProfileSnapshotForTests(); process.exitCode = 0; - await fs.rm(path.join(os.homedir(), configDir), { recursive: true, force: true }); + await removeWithRetries(path.join(os.homedir(), configDir)); }); it("activates a profile before dispatching root flags", async () => { @@ -267,7 +268,7 @@ describe("global --profile flag", () => { expect(stdout).toContain("SENTINEL=work"); expect(stdout).not.toContain("SENTINEL=default"); } finally { - await fs.rm(root, { recursive: true, force: true }); + await removeWithRetries(root); } }); @@ -316,7 +317,7 @@ describe("global --profile flag", () => { expect(stderr).toContain("Invalid OMP profile"); expect(exitCode).toBe(1); } finally { - await fs.rm(root, { recursive: true, force: true }); + await removeWithRetries(root); } }); }); diff --git a/packages/coding-agent/test/read-acp-fs.test.ts b/packages/coding-agent/test/read-acp-fs.test.ts index 76a26a17a..8e64923a6 100644 --- a/packages/coding-agent/test/read-acp-fs.test.ts +++ b/packages/coding-agent/test/read-acp-fs.test.ts @@ -8,6 +8,7 @@ import type { ClientBridge } from "@oh-my-pi/pi-coding-agent/session/client-brid import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; import type { ReadToolDetails } from "@oh-my-pi/pi-coding-agent/tools/read"; import { ReadTool } from "@oh-my-pi/pi-coding-agent/tools/read"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; const BRIDGE_CONTENT = "// content from editor buffer\nexport function greet() { return 'bridge'; }\n"; @@ -39,7 +40,7 @@ describe("read tool ACP fs routing", () => { }); afterEach(async () => { - await fs.rm(tmpDir, { recursive: true, force: true }); + await removeWithRetries(tmpDir); }); it("routes plain text reads through the bridge and does not call Bun.file().text()", async () => { diff --git a/packages/coding-agent/test/read-column-truncation-snapshot.test.ts b/packages/coding-agent/test/read-column-truncation-snapshot.test.ts index 9656c2de8..d4c65d71a 100644 --- a/packages/coding-agent/test/read-column-truncation-snapshot.test.ts +++ b/packages/coding-agent/test/read-column-truncation-snapshot.test.ts @@ -22,6 +22,7 @@ import { writethroughNoop } from "@oh-my-pi/pi-coding-agent/lsp"; import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; import type { ReadToolDetails } from "@oh-my-pi/pi-coding-agent/tools/read"; import { ReadTool } from "@oh-my-pi/pi-coding-agent/tools/read"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; const HASHLINE_HEADER_LINE = /^\[([^#\r\n]+)#([0-9A-F]{4})\]$/m; const COLUMN_CAP = 64; @@ -92,7 +93,7 @@ describe("read tool column truncation vs hashline snapshot", () => { }); afterEach(async () => { - await fs.rm(tmpDir, { recursive: true, force: true }); + await removeWithRetries(tmpDir); }); it("snapshot keeps untruncated content for a full-file read with long lines", async () => { diff --git a/packages/coding-agent/test/read-multi-range.test.ts b/packages/coding-agent/test/read-multi-range.test.ts index 99c30f4aa..f85a1da33 100644 --- a/packages/coding-agent/test/read-multi-range.test.ts +++ b/packages/coding-agent/test/read-multi-range.test.ts @@ -8,6 +8,7 @@ import type { ClientBridge } from "@oh-my-pi/pi-coding-agent/session/client-brid import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; import type { ReadToolDetails } from "@oh-my-pi/pi-coding-agent/tools/read"; import { ReadTool } from "@oh-my-pi/pi-coding-agent/tools/read"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; function textOutput(result: AgentToolResult): string { return result.content @@ -45,7 +46,7 @@ describe("read tool multi-range selector", () => { }); afterEach(async () => { - await fs.rm(tmpDir, { recursive: true, force: true }); + await removeWithRetries(tmpDir); }); it("uses only the filename in hashline headers for nested files", async () => { diff --git a/packages/coding-agent/test/read-summary.test.ts b/packages/coding-agent/test/read-summary.test.ts index e21f12897..a573fcd0f 100644 --- a/packages/coding-agent/test/read-summary.test.ts +++ b/packages/coding-agent/test/read-summary.test.ts @@ -8,6 +8,7 @@ import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; import type { ReadToolDetails } from "@oh-my-pi/pi-coding-agent/tools/read"; import { ReadTool } from "@oh-my-pi/pi-coding-agent/tools/read"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; let artifactCounter = 0; @@ -58,7 +59,7 @@ describe("read summary", () => { }); afterEach(async () => { - await fs.rm(tmpDir, { recursive: true, force: true }); + await removeWithRetries(tmpDir); }); it("summarizes parseable TypeScript files without an explicit selector", async () => { diff --git a/packages/coding-agent/test/repro-issue-1022-disabled-default-model.test.ts b/packages/coding-agent/test/repro-issue-1022-disabled-default-model.test.ts index 4c17bc9f4..a483ddbaa 100644 --- a/packages/coding-agent/test/repro-issue-1022-disabled-default-model.test.ts +++ b/packages/coding-agent/test/repro-issue-1022-disabled-default-model.test.ts @@ -7,7 +7,7 @@ import { resetSettingsForTest, Settings } from "@oh-my-pi/pi-coding-agent/config import { createAgentSession } from "@oh-my-pi/pi-coding-agent/sdk"; import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; import { YAML } from "bun"; /** @@ -38,7 +38,7 @@ describe("issue #1022 — path-scoped enabledModels respected by default fallbac afterEach(() => { resetSettingsForTest(); - if (fs.existsSync(testDir)) fs.rmSync(testDir, { recursive: true, force: true }); + if (fs.existsSync(testDir)) removeSyncWithRetries(testDir); }); test("does not pick a disallowed provider when enabledModels excludes it", async () => { diff --git a/packages/coding-agent/test/rpc-host-tools.test.ts b/packages/coding-agent/test/rpc-host-tools.test.ts index ccd5fca7b..fe34cd404 100644 --- a/packages/coding-agent/test/rpc-host-tools.test.ts +++ b/packages/coding-agent/test/rpc-host-tools.test.ts @@ -1,5 +1,4 @@ import { afterEach, describe, expect, it } from "bun:test"; -import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; import type { AgentEvent } from "@oh-my-pi/pi-agent-core"; @@ -10,6 +9,7 @@ import type { RpcHostToolCancelRequest, RpcHostToolUpdate, } from "@oh-my-pi/pi-coding-agent/modes/rpc/rpc-types"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; const tempPaths: string[] = []; @@ -17,7 +17,7 @@ afterEach(async () => { await Promise.all( tempPaths.splice(0).map(async filePath => { try { - await fs.rm(filePath, { force: true }); + await removeWithRetries(filePath); } catch {} }), ); diff --git a/packages/coding-agent/test/rpc-skill-command.test.ts b/packages/coding-agent/test/rpc-skill-command.test.ts index 7a9f56866..2b5348403 100644 --- a/packages/coding-agent/test/rpc-skill-command.test.ts +++ b/packages/coding-agent/test/rpc-skill-command.test.ts @@ -4,7 +4,7 @@ import * as os from "node:os"; import * as path from "node:path"; import { tryRunRpcSkillCommand } from "@oh-my-pi/pi-coding-agent/modes/rpc/rpc-mode"; import { type CustomMessage, SKILL_PROMPT_MESSAGE_TYPE } from "@oh-my-pi/pi-coding-agent/session/messages"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; describe("tryRunRpcSkillCommand", () => { test("dispatches registered /skill commands as skill prompt messages", async () => { @@ -37,7 +37,7 @@ describe("tryRunRpcSkillCommand", () => { expect(message?.display).toBe(true); expect(message?.attribution).toBe("user"); - await fs.rm(dir, { recursive: true, force: true }); + await removeWithRetries(dir); }); test("ignores unknown skill commands so normal prompt handling can continue", async () => { diff --git a/packages/coding-agent/test/rpc-subagents.test.ts b/packages/coding-agent/test/rpc-subagents.test.ts index 133373bdd..b021107f8 100644 --- a/packages/coding-agent/test/rpc-subagents.test.ts +++ b/packages/coding-agent/test/rpc-subagents.test.ts @@ -21,12 +21,13 @@ import { TASK_SUBAGENT_PROGRESS_CHANNEL, } from "@oh-my-pi/pi-coding-agent/task"; import { EventBus } from "@oh-my-pi/pi-coding-agent/utils/event-bus"; +import { removeSyncWithRetries } from "@oh-my-pi/pi-utils"; const tempPaths: string[] = []; afterEach(() => { for (const tempPath of tempPaths.splice(0)) { - fs.rmSync(tempPath, { recursive: true, force: true }); + removeSyncWithRetries(tempPath); } }); diff --git a/packages/coding-agent/test/rpc.test.ts b/packages/coding-agent/test/rpc.test.ts index 8ebf6b65a..d20da322a 100644 --- a/packages/coding-agent/test/rpc.test.ts +++ b/packages/coding-agent/test/rpc.test.ts @@ -12,7 +12,7 @@ import { } from "@oh-my-pi/pi-coding-agent"; import { RpcClient } from "@oh-my-pi/pi-coding-agent/modes/rpc/rpc-client"; import type { BashExecutionMessage } from "@oh-my-pi/pi-coding-agent/session/messages"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; import { e2eApiKey } from "./utilities"; type MessageEndEvent = Extract; @@ -46,7 +46,7 @@ describe.skipIf(!e2eApiKey("ANTHROPIC_API_KEY"))("RPC mode", () => { afterEach(async () => { client.stop(); if (sessionDir && fs.existsSync(sessionDir)) { - fs.rmSync(sessionDir, { recursive: true }); + removeSyncWithRetries(sessionDir); } }); diff --git a/packages/coding-agent/test/sdk-async-job-manager-singleton.test.ts b/packages/coding-agent/test/sdk-async-job-manager-singleton.test.ts index 903c00722..ca66ba796 100644 --- a/packages/coding-agent/test/sdk-async-job-manager-singleton.test.ts +++ b/packages/coding-agent/test/sdk-async-job-manager-singleton.test.ts @@ -7,7 +7,7 @@ import { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry"; import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { createAgentSession } from "@oh-my-pi/pi-coding-agent/sdk"; import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; describe("AsyncJobManager singleton across concurrent top-level sessions", () => { const tempDirs: string[] = []; @@ -28,12 +28,12 @@ describe("AsyncJobManager singleton across concurrent top-level sessions", () => afterAll(() => { sharedAuthStorage.close(); - fs.rmSync(sharedTempDir, { recursive: true, force: true }); + removeSyncWithRetries(sharedTempDir); }); afterEach(async () => { for (const tempDir of tempDirs.splice(0)) { - fs.rmSync(tempDir, { recursive: true, force: true }); + removeSyncWithRetries(tempDir); } AsyncJobManager.resetForTests(); }); diff --git a/packages/coding-agent/test/sdk-autolearn-active-tools.test.ts b/packages/coding-agent/test/sdk-autolearn-active-tools.test.ts index 7e7dfe293..d77bd9ff2 100644 --- a/packages/coding-agent/test/sdk-autolearn-active-tools.test.ts +++ b/packages/coding-agent/test/sdk-autolearn-active-tools.test.ts @@ -9,7 +9,7 @@ import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { createAgentSession } from "@oh-my-pi/pi-coding-agent/sdk"; import type { AgentSession } from "@oh-my-pi/pi-coding-agent/session/agent-session"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; // Guards the auto-learn tool ACTIVATION wiring in createAgentSession: createTools // force-includes manage_skill into the built registry for an enabled top-level @@ -34,7 +34,7 @@ describe("createAgentSession auto-learn tool activation", () => { afterAll(async () => { for (const session of sessions) await session.dispose().catch(() => {}); authStorage.close(); - if (fs.existsSync(registryDir)) fs.rmSync(registryDir, { recursive: true, force: true }); + if (fs.existsSync(registryDir)) removeSyncWithRetries(registryDir); }); async function activeToolNames(settings: Settings): Promise { diff --git a/packages/coding-agent/test/sdk-credential-disabled-bridge.test.ts b/packages/coding-agent/test/sdk-credential-disabled-bridge.test.ts index c28e402ad..6ec022e72 100644 --- a/packages/coding-agent/test/sdk-credential-disabled-bridge.test.ts +++ b/packages/coding-agent/test/sdk-credential-disabled-bridge.test.ts @@ -11,7 +11,7 @@ import { ExtensionRunner } from "@oh-my-pi/pi-coding-agent/extensibility/extensi import { ExtensionRuntime } from "@oh-my-pi/pi-coding-agent/extensibility/extensions/loader"; import { createAgentSession } from "@oh-my-pi/pi-coding-agent/sdk"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; interface SessionDirs { cwd: string; @@ -147,7 +147,7 @@ describe("createAgentSession credential_disabled subscription", () => { afterEach(() => { vi.restoreAllMocks(); for (const dir of tempDirs.splice(0)) { - fs.rmSync(dir, { recursive: true, force: true }); + removeSyncWithRetries(dir); } }); diff --git a/packages/coding-agent/test/sdk-custom-tools-per-session-binding.test.ts b/packages/coding-agent/test/sdk-custom-tools-per-session-binding.test.ts index 1c242c945..a0bd07a9b 100644 --- a/packages/coding-agent/test/sdk-custom-tools-per-session-binding.test.ts +++ b/packages/coding-agent/test/sdk-custom-tools-per-session-binding.test.ts @@ -21,6 +21,7 @@ import { loadCustomTools, type ToolPathWithSource, } from "@oh-my-pi/pi-coding-agent/extensibility/custom-tools"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; describe("loadCustomTools per-session binding (#2190 review fix)", () => { let tmp: string; @@ -47,7 +48,7 @@ describe("loadCustomTools per-session binding (#2190 review fix)", () => { }); afterAll(async () => { - await fs.rm(tmp, { recursive: true, force: true }); + await removeWithRetries(tmp); }); it("binds each load to the cwd passed to loadCustomTools", async () => { diff --git a/packages/coding-agent/test/sdk-extensions-per-session-binding.test.ts b/packages/coding-agent/test/sdk-extensions-per-session-binding.test.ts index 18c03815c..5c020fb70 100644 --- a/packages/coding-agent/test/sdk-extensions-per-session-binding.test.ts +++ b/packages/coding-agent/test/sdk-extensions-per-session-binding.test.ts @@ -17,6 +17,7 @@ import * as os from "node:os"; import * as path from "node:path"; import { loadExtensions } from "@oh-my-pi/pi-coding-agent/extensibility/extensions"; import { EventBus } from "@oh-my-pi/pi-coding-agent/utils/event-bus"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; describe("loadExtensions per-session binding (#2190 review fix)", () => { let tmp: string; @@ -50,7 +51,7 @@ describe("loadExtensions per-session binding (#2190 review fix)", () => { }); afterAll(async () => { - await fs.rm(tmp, { recursive: true, force: true }); + await removeWithRetries(tmp); delete (globalThis as { __bindings?: unknown }).__bindings; delete (globalThis as { __lastExtBinding?: unknown }).__lastExtBinding; }); diff --git a/packages/coding-agent/test/sdk-mcp-auto-discovery.test.ts b/packages/coding-agent/test/sdk-mcp-auto-discovery.test.ts index 84d4cc6ef..322747cc8 100644 --- a/packages/coding-agent/test/sdk-mcp-auto-discovery.test.ts +++ b/packages/coding-agent/test/sdk-mcp-auto-discovery.test.ts @@ -8,7 +8,7 @@ import { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry"; import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { createAgentSession } from "@oh-my-pi/pi-coding-agent/sdk"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; -import { getAgentDir, Snowflake, setAgentDir } from "@oh-my-pi/pi-utils"; +import { getAgentDir, removeSyncWithRetries, Snowflake, setAgentDir } from "@oh-my-pi/pi-utils"; import { MANY_TOOL_COUNT } from "./fixtures/many-tools-mcp"; // Contracts for deferred (hasUI) MCP discovery follow-ups: @@ -50,7 +50,7 @@ describe("createAgentSession deferred MCP auto discovery", () => { authStorage.close(); for (const dir of [registryDir, isolatedHome]) { if (dir && fs.existsSync(dir)) { - fs.rmSync(dir, { recursive: true, force: true }); + removeSyncWithRetries(dir); } } }); @@ -65,7 +65,7 @@ describe("createAgentSession deferred MCP auto discovery", () => { afterEach(() => { setAgentDir(originalAgentDir); if (tempDir && fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true, force: true }); + removeSyncWithRetries(tempDir); } mock.restore(); }); diff --git a/packages/coding-agent/test/sdk-mcp-defer.test.ts b/packages/coding-agent/test/sdk-mcp-defer.test.ts index 36a3ae42e..76167e863 100644 --- a/packages/coding-agent/test/sdk-mcp-defer.test.ts +++ b/packages/coding-agent/test/sdk-mcp-defer.test.ts @@ -8,7 +8,7 @@ import { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry"; import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { createAgentSession } from "@oh-my-pi/pi-coding-agent/sdk"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; // Contract for B1 (interactive MCP deferral): when `hasUI` is true, MCP // discovery is deferred off the first-paint path, so an explicitly requested @@ -55,7 +55,7 @@ describe("createAgentSession MCP deferral (B1)", () => { afterAll(() => { authStorage.close(); if (registryDir && fs.existsSync(registryDir)) { - fs.rmSync(registryDir, { recursive: true, force: true }); + removeSyncWithRetries(registryDir); } }); @@ -66,7 +66,7 @@ describe("createAgentSession MCP deferral (B1)", () => { afterEach(() => { if (tempDir && fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true, force: true }); + removeSyncWithRetries(tempDir); } }); diff --git a/packages/coding-agent/test/sdk-mcp-discovery.test.ts b/packages/coding-agent/test/sdk-mcp-discovery.test.ts index 6b4f30bb6..b243b6797 100644 --- a/packages/coding-agent/test/sdk-mcp-discovery.test.ts +++ b/packages/coding-agent/test/sdk-mcp-discovery.test.ts @@ -12,7 +12,7 @@ import type { CustomTool } from "@oh-my-pi/pi-coding-agent/extensibility/custom- import { createAgentSession } from "@oh-my-pi/pi-coding-agent/sdk"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; import { TOOL_DISCOVERY_AUTO_THRESHOLD } from "@oh-my-pi/pi-coding-agent/tool-discovery/mode"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; import { type } from "arktype"; function createMcpCustomTool(name: string, serverName: string, mcpToolName: string): CustomTool { @@ -67,7 +67,7 @@ describe("createAgentSession MCP discovery prompt gating", () => { afterAll(() => { authStorage.close(); if (registryDir && fs.existsSync(registryDir)) { - fs.rmSync(registryDir, { recursive: true, force: true }); + removeSyncWithRetries(registryDir); } }); @@ -78,7 +78,7 @@ describe("createAgentSession MCP discovery prompt gating", () => { afterEach(() => { if (tempDir && fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true, force: true }); + removeSyncWithRetries(tempDir); } }); diff --git a/packages/coding-agent/test/sdk-mcp-instructions.test.ts b/packages/coding-agent/test/sdk-mcp-instructions.test.ts index 21bf0a364..686351288 100644 --- a/packages/coding-agent/test/sdk-mcp-instructions.test.ts +++ b/packages/coding-agent/test/sdk-mcp-instructions.test.ts @@ -8,7 +8,7 @@ import { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry"; import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { createAgentSession } from "@oh-my-pi/pi-coding-agent/sdk"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; import { SERVER_INSTRUCTIONS } from "./fixtures/instructions-mcp"; // Contract: a deferred interactive (`hasUI`) session runs MCP discovery off the @@ -43,7 +43,7 @@ describe("createAgentSession MCP server instructions (deferred UI)", () => { authStorage.close(); for (const dir of [registryDir, isolatedHome]) { if (dir && fs.existsSync(dir)) { - fs.rmSync(dir, { recursive: true, force: true }); + removeSyncWithRetries(dir); } } }); @@ -64,7 +64,7 @@ describe("createAgentSession MCP server instructions (deferred UI)", () => { afterEach(() => { if (tempDir && fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true, force: true }); + removeSyncWithRetries(tempDir); } mock.restore(); }); diff --git a/packages/coding-agent/test/sdk-model-selection.test.ts b/packages/coding-agent/test/sdk-model-selection.test.ts index f673ff6ef..f03fabe84 100644 --- a/packages/coding-agent/test/sdk-model-selection.test.ts +++ b/packages/coding-agent/test/sdk-model-selection.test.ts @@ -9,7 +9,7 @@ import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { createAgentSession, type ExtensionFactory } from "@oh-my-pi/pi-coding-agent/sdk"; import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; describe("createAgentSession deferred model pattern resolution", () => { let tempDir: string; @@ -26,7 +26,7 @@ describe("createAgentSession deferred model pattern resolution", () => { } authStoragesToClose.length = 0; if (tempDir && fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true, force: true }); + removeSyncWithRetries(tempDir); } }); diff --git a/packages/coding-agent/test/sdk-move-cwd.test.ts b/packages/coding-agent/test/sdk-move-cwd.test.ts index d662b2a30..9122be9cd 100644 --- a/packages/coding-agent/test/sdk-move-cwd.test.ts +++ b/packages/coding-agent/test/sdk-move-cwd.test.ts @@ -6,7 +6,7 @@ import { getBundledModel } from "@oh-my-pi/pi-catalog/models"; import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { createAgentSession } from "@oh-my-pi/pi-coding-agent/sdk"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; function textContent(result: { content?: Array<{ type: string; text?: string }> }): string { return ( @@ -24,7 +24,7 @@ describe("createAgentSession cwd after /move", () => { afterEach(() => { for (const tempDir of tempDirs.splice(0)) { - fs.rmSync(tempDir, { recursive: true, force: true }); + removeSyncWithRetries(tempDir); } }); diff --git a/packages/coding-agent/test/sdk-preloaded-extensions-isolation.test.ts b/packages/coding-agent/test/sdk-preloaded-extensions-isolation.test.ts index e37e757bf..911fb18f4 100644 --- a/packages/coding-agent/test/sdk-preloaded-extensions-isolation.test.ts +++ b/packages/coding-agent/test/sdk-preloaded-extensions-isolation.test.ts @@ -21,6 +21,7 @@ import type { LoadExtensionsResult } from "@oh-my-pi/pi-coding-agent/extensibili import { createAgentSession } from "@oh-my-pi/pi-coding-agent/sdk"; import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; +import { removeSyncWithRetries } from "@oh-my-pi/pi-utils"; describe("createAgentSession preloadedExtensions isolation (issue #2190)", () => { let sharedDir: string; @@ -35,7 +36,7 @@ describe("createAgentSession preloadedExtensions isolation (issue #2190)", () => afterAll(() => { authStorage.close(); - fs.rmSync(sharedDir, { recursive: true, force: true }); + removeSyncWithRetries(sharedDir); }); it("does not mutate the caller's extensions array when preloadedExtensions is provided", async () => { diff --git a/packages/coding-agent/test/sdk-session-isolation.test.ts b/packages/coding-agent/test/sdk-session-isolation.test.ts index df1d3e63e..dba4d4b65 100644 --- a/packages/coding-agent/test/sdk-session-isolation.test.ts +++ b/packages/coding-agent/test/sdk-session-isolation.test.ts @@ -11,7 +11,7 @@ import { createAgentSession } from "@oh-my-pi/pi-coding-agent/sdk"; import { SecretObfuscator } from "@oh-my-pi/pi-coding-agent/secrets"; import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; -import { getSessionsDir, Snowflake } from "@oh-my-pi/pi-utils"; +import { getSessionsDir, removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; function createTtsrRule(name: string): Rule { return { @@ -78,7 +78,7 @@ describe("createAgentSession session storage isolation", () => { afterEach(async () => { for (const tempDir of tempDirs.splice(0)) { - fs.rmSync(tempDir, { recursive: true, force: true }); + removeSyncWithRetries(tempDir); } }); diff --git a/packages/coding-agent/test/sdk-skills.test.ts b/packages/coding-agent/test/sdk-skills.test.ts index 15bccd54a..47be1a42e 100644 --- a/packages/coding-agent/test/sdk-skills.test.ts +++ b/packages/coding-agent/test/sdk-skills.test.ts @@ -8,6 +8,7 @@ import type { Skill } from "@oh-my-pi/pi-coding-agent/sdk"; import { createAgentSession } from "@oh-my-pi/pi-coding-agent/sdk"; import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; +import { removeSyncWithRetries } from "@oh-my-pi/pi-utils"; import { cleanupTempHome } from "./helpers/temp-home-cleanup"; function createIsolatedSkillsSettings(): Settings { @@ -43,7 +44,7 @@ describe("createAgentSession skills option", () => { afterAll(() => { sharedAuthStorage.close(); - fs.rmSync(sharedDir, { recursive: true, force: true }); + removeSyncWithRetries(sharedDir); }); beforeEach(() => { @@ -118,7 +119,7 @@ Loaded via symbolic link. it("should still discover project skills when user skills directory is missing", async () => { const userAgentDir = path.join(tempHomeDir, ".omp", "agent"); - fs.rmSync(path.join(userAgentDir, "skills"), { recursive: true, force: true }); + removeSyncWithRetries(path.join(userAgentDir, "skills")); fs.writeFileSync(path.join(userAgentDir, "placeholder.txt"), "placeholder"); const { session } = await createAgentSession({ diff --git a/packages/coding-agent/test/sdk-tool-activation.test.ts b/packages/coding-agent/test/sdk-tool-activation.test.ts index cb388affb..a935d2161 100644 --- a/packages/coding-agent/test/sdk-tool-activation.test.ts +++ b/packages/coding-agent/test/sdk-tool-activation.test.ts @@ -12,7 +12,7 @@ import { type ExtensionFactory, } from "@oh-my-pi/pi-coding-agent/sdk"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; import { type } from "arktype"; const toolActivationExtension: ExtensionFactory = pi => { @@ -88,14 +88,14 @@ describe("createAgentSession defaultInactive tool activation", () => { afterEach(() => { for (const tempDir of tempDirs.splice(0)) { - fs.rmSync(tempDir, { recursive: true, force: true }); + removeSyncWithRetries(tempDir); } vi.restoreAllMocks(); }); afterAll(() => { - fs.rmSync(registryAuthDir, { recursive: true, force: true }); + removeSyncWithRetries(registryAuthDir); }); it("excludes defaultInactive extension tools from the initial active set unless explicitly requested", async () => { diff --git a/packages/coding-agent/test/session-manager-cwd-adoption.test.ts b/packages/coding-agent/test/session-manager-cwd-adoption.test.ts index 61fc16415..310fb994e 100644 --- a/packages/coding-agent/test/session-manager-cwd-adoption.test.ts +++ b/packages/coding-agent/test/session-manager-cwd-adoption.test.ts @@ -1,8 +1,7 @@ import { afterEach, describe, expect, it } from "bun:test"; -import * as fs from "node:fs/promises"; import * as path from "node:path"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; -import { TempDir } from "@oh-my-pi/pi-utils"; +import { removeWithRetries, TempDir } from "@oh-my-pi/pi-utils"; const tempDirs: TempDir[] = []; @@ -95,7 +94,7 @@ describe("SessionManager cwd adoption on resume", () => { // The session file survives in `store` (like ~/.omp), but its header cwd // points at a project directory that we then delete. const file = await writeSession(goneProject, store); - await fs.rm(goneProject, { recursive: true, force: true }); + await removeWithRetries(goneProject); const launchSessions = path.join(launch, "sessions"); const manager = SessionManager.create(launch, launchSessions); @@ -112,7 +111,7 @@ describe("SessionManager cwd adoption on resume", () => { const store = makeTempDir("@pi-cwd-store-"); const goneProject = makeTempDir("@pi-cwd-gone-"); const file = await writeSession(goneProject, store); - await fs.rm(goneProject, { recursive: true, force: true }); + await removeWithRetries(goneProject); const manager = await SessionManager.open(file, undefined, undefined, { initialCwd: launch }); diff --git a/packages/coding-agent/test/session-manager/file-operations.test.ts b/packages/coding-agent/test/session-manager/file-operations.test.ts index ba0609496..7c3abe61f 100644 --- a/packages/coding-agent/test/session-manager/file-operations.test.ts +++ b/packages/coding-agent/test/session-manager/file-operations.test.ts @@ -6,7 +6,7 @@ import type { FileEntry, SessionHeader } from "@oh-my-pi/pi-coding-agent/session import { findMostRecentSession, resolveResumableSession } from "@oh-my-pi/pi-coding-agent/session/session-listing"; import { loadEntriesFromFile } from "@oh-my-pi/pi-coding-agent/session/session-loader"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; -import { getConfigRootDir, getSessionsDir, Snowflake, setAgentDir } from "@oh-my-pi/pi-utils"; +import { getConfigRootDir, getSessionsDir, removeSyncWithRetries, Snowflake, setAgentDir } from "@oh-my-pi/pi-utils"; describe("loadEntriesFromFile", () => { let tempDir: string; @@ -17,7 +17,7 @@ describe("loadEntriesFromFile", () => { }); afterEach(() => { - fs.rmSync(tempDir, { recursive: true, force: true }); + removeSyncWithRetries(tempDir); }); it("loads valid session file", async () => { @@ -55,7 +55,7 @@ describe("findMostRecentSession", () => { }); afterEach(() => { - fs.rmSync(tempDir, { recursive: true, force: true }); + removeSyncWithRetries(tempDir); }); it("returns single valid session file", async () => { @@ -99,7 +99,7 @@ describe("resolveResumableSession", () => { }); afterEach(() => { - fs.rmSync(tempDir, { recursive: true, force: true }); + removeSyncWithRetries(tempDir); }); function writeSession(fileName: string, headerCwd: string, id: string = Snowflake.next()): string { @@ -185,7 +185,7 @@ describe("SessionManager temp cwd session dirs", () => { setAgentDir(fallbackAgentDir); delete process.env.PI_CODING_AGENT_DIR; } - fs.rmSync(testAgentDir, { recursive: true, force: true }); + removeSyncWithRetries(testAgentDir); }); it("stores temp-root cwd sessions under -tmp-prefixed directories", () => { @@ -251,7 +251,7 @@ describe("SessionManager legacy session migration persistence", () => { }); afterEach(() => { - fs.rmSync(tempDir, { recursive: true, force: true }); + removeSyncWithRetries(tempDir); }); it("keeps legacy migration in memory until later persisted activity rewrites the file", async () => { diff --git a/packages/coding-agent/test/session-manager/title-source-persistence.test.ts b/packages/coding-agent/test/session-manager/title-source-persistence.test.ts index 0931d501a..8fad6402f 100644 --- a/packages/coding-agent/test/session-manager/title-source-persistence.test.ts +++ b/packages/coding-agent/test/session-manager/title-source-persistence.test.ts @@ -5,7 +5,7 @@ import * as path from "node:path"; import type { SessionHeader } from "@oh-my-pi/pi-coding-agent/session/session-entries"; import { loadEntriesFromFile } from "@oh-my-pi/pi-coding-agent/session/session-loader"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; -import { getConfigRootDir, setAgentDir } from "@oh-my-pi/pi-utils"; +import { getConfigRootDir, removeSyncWithRetries, setAgentDir } from "@oh-my-pi/pi-utils"; import { makeAssistantMessage } from "./helpers"; @@ -36,7 +36,7 @@ describe("session title source persistence", () => { setAgentDir(fallbackAgentDir); delete process.env.PI_CODING_AGENT_DIR; } - fs.rmSync(testAgentDir, { recursive: true, force: true }); + removeSyncWithRetries(testAgentDir); }); it("persists auto title source across reopen", async () => { diff --git a/packages/coding-agent/test/session/emit-listener-isolation.test.ts b/packages/coding-agent/test/session/emit-listener-isolation.test.ts index a2efc9875..12b45069f 100644 --- a/packages/coding-agent/test/session/emit-listener-isolation.test.ts +++ b/packages/coding-agent/test/session/emit-listener-isolation.test.ts @@ -13,7 +13,7 @@ import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { AgentSession } from "@oh-my-pi/pi-coding-agent/session/agent-session"; import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; function makeEvent(): AgentEvent { return { type: "tool_execution_start", toolCallId: "probe-1", toolName: "probe", args: {} }; @@ -95,7 +95,7 @@ describe("#emit listener isolation", () => { authStorage = undefined; if (fs.existsSync(tempDir)) { try { - fs.rmSync(tempDir, { recursive: true, force: true }); + removeSyncWithRetries(tempDir); } catch { // Windows may hold sqlite handles briefly after close; best-effort cleanup. } diff --git a/packages/coding-agent/test/session/session-manager-fork.test.ts b/packages/coding-agent/test/session/session-manager-fork.test.ts index 81ebf491f..de0df6890 100644 --- a/packages/coding-agent/test/session/session-manager-fork.test.ts +++ b/packages/coding-agent/test/session/session-manager-fork.test.ts @@ -4,7 +4,7 @@ import * as path from "node:path"; import { CURRENT_SESSION_VERSION, type SessionHeader } from "@oh-my-pi/pi-coding-agent/session/session-entries"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; import { getTerminalId } from "@oh-my-pi/pi-tui"; -import { getAgentDir, getTerminalSessionsDir, setAgentDir, TempDir } from "@oh-my-pi/pi-utils"; +import { getAgentDir, getTerminalSessionsDir, removeWithRetries, setAgentDir, TempDir } from "@oh-my-pi/pi-utils"; interface JsonlMessageEntry { type: "message"; @@ -51,7 +51,7 @@ describe("SessionManager.forkFrom", () => { const terminalId = getTerminalId(); expect(terminalId).toBeString(); const breadcrumbFile = path.join(getTerminalSessionsDir(), terminalId ?? "missing"); - await fs.rm(breadcrumbFile, { force: true }); + await removeWithRetries(breadcrumbFile); const forked = await SessionManager.forkFrom(sourceFile, cwd, sessionDir, undefined, { suppressBreadcrumb: true, diff --git a/packages/coding-agent/test/settings-reload-cwd.test.ts b/packages/coding-agent/test/settings-reload-cwd.test.ts index 7054bf0bf..9c0ab8a94 100644 --- a/packages/coding-agent/test/settings-reload-cwd.test.ts +++ b/packages/coding-agent/test/settings-reload-cwd.test.ts @@ -3,7 +3,7 @@ import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; import { resetSettingsForTest, Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; -import { getProjectAgentDir, Snowflake } from "@oh-my-pi/pi-utils"; +import { getProjectAgentDir, removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; import { beginSettingsTest, restoreSettingsTestState, type SettingsTestState } from "./helpers/settings-test-state"; describe("Settings.reloadForCwd", () => { @@ -76,7 +76,7 @@ describe("Settings.reloadForCwd", () => { expect(settings.get("compaction.enabled")).toBe(true); } finally { resetSettingsForTest(); - if (fs.existsSync(testDir)) fs.rmSync(testDir, { recursive: true, force: true }); + if (fs.existsSync(testDir)) removeSyncWithRetries(testDir); } }); @@ -92,7 +92,7 @@ describe("Settings.reloadForCwd", () => { ); } finally { resetSettingsForTest(); - if (fs.existsSync(testDir)) fs.rmSync(testDir, { recursive: true, force: true }); + if (fs.existsSync(testDir)) removeSyncWithRetries(testDir); } }); @@ -109,7 +109,7 @@ describe("Settings.reloadForCwd", () => { ); } finally { resetSettingsForTest(); - if (fs.existsSync(testDir)) fs.rmSync(testDir, { recursive: true, force: true }); + if (fs.existsSync(testDir)) removeSyncWithRetries(testDir); } }); @@ -141,7 +141,7 @@ describe("Settings.reloadForCwd", () => { afterEach(() => { resetSettingsForTest(); if (fs.existsSync(testDir)) { - fs.rmSync(testDir, { recursive: true, force: true }); + removeSyncWithRetries(testDir); } }); diff --git a/packages/coding-agent/test/silent-abort-overlay-render.test.ts b/packages/coding-agent/test/silent-abort-overlay-render.test.ts index 5e1bb0f9f..9395a95f2 100644 --- a/packages/coding-agent/test/silent-abort-overlay-render.test.ts +++ b/packages/coding-agent/test/silent-abort-overlay-render.test.ts @@ -18,6 +18,7 @@ import { initTheme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme"; import { AgentRegistry } from "@oh-my-pi/pi-coding-agent/registry/agent-registry"; import { SILENT_ABORT_MARKER } from "@oh-my-pi/pi-coding-agent/session/messages"; import type { TUI } from "@oh-my-pi/pi-tui"; +import { removeSyncWithRetries } from "@oh-my-pi/pi-utils"; const SESSION_ID = "test-session-1"; @@ -78,7 +79,7 @@ describe("Agent hub silent-abort regression", () => { afterEach(() => { resetSettingsForTest(); - fs.rmSync(tmpDir, { recursive: true, force: true }); + removeSyncWithRetries(tmpDir); }); it("renders no error line for silent-abort assistant messages with empty content", () => { diff --git a/packages/coding-agent/test/skills.test.ts b/packages/coding-agent/test/skills.test.ts index ba43d0718..33ec167d7 100644 --- a/packages/coding-agent/test/skills.test.ts +++ b/packages/coding-agent/test/skills.test.ts @@ -5,6 +5,7 @@ import * as path from "node:path"; import { type Skill as CapabilitySkill, skillCapability } from "@oh-my-pi/pi-coding-agent/capability/skill"; import { getCapability } from "@oh-my-pi/pi-coding-agent/discovery"; import { loadSkills, loadSkillsFromDir, type Skill } from "@oh-my-pi/pi-coding-agent/extensibility/skills"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; const fixturesDir = path.resolve(import.meta.dirname, "fixtures/skills"); const collisionFixturesDir = path.resolve(import.meta.dirname, "fixtures/skills-collision"); @@ -190,8 +191,8 @@ describe("skills", () => { const result = await claudeProvider!.load({ cwd: tempProjectDir, home: tempHomeDir, repoRoot: null }); expect(result.items.some(skill => skill.name === "user-only-skill" && skill.level === "user")).toBe(true); } finally { - await fs.rm(tempProjectDir, { recursive: true, force: true }); - await fs.rm(tempHomeDir, { recursive: true, force: true }); + await removeWithRetries(tempProjectDir); + await removeWithRetries(tempHomeDir); } }); @@ -223,8 +224,8 @@ describe("skills", () => { expect(skills.some(s => s.name === "user-agents-skill" && s.source === "agents:user")).toBe(true); } finally { homedirSpy.mockRestore(); - await fs.rm(tempHome, { recursive: true, force: true }); - await fs.rm(tempCwd, { recursive: true, force: true }); + await removeWithRetries(tempHome); + await removeWithRetries(tempCwd); } }); @@ -247,8 +248,8 @@ describe("skills", () => { expect(skills.some(s => s.name === "opted-out")).toBe(false); } finally { homedirSpy.mockRestore(); - await fs.rm(tempHome, { recursive: true, force: true }); - await fs.rm(tempCwd, { recursive: true, force: true }); + await removeWithRetries(tempHome); + await removeWithRetries(tempCwd); } }); @@ -281,8 +282,8 @@ describe("skills", () => { expect(skills.some(s => s.name === "leaked-opencode")).toBe(false); } finally { homedirSpy.mockRestore(); - await fs.rm(tempHome, { recursive: true, force: true }); - await fs.rm(tempCwd, { recursive: true, force: true }); + await removeWithRetries(tempHome); + await removeWithRetries(tempCwd); } }); @@ -324,7 +325,7 @@ enabled: false const { skills } = await loadSkills({ ...DISABLE_ALL_BUILTIN_SKILLS, customDirectories: [tempDir] }); expect(skills.some(s => s.name === "disabled-skill")).toBe(false); } finally { - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); } }); @@ -343,7 +344,7 @@ enabled: false expect(skill).toBeDefined(); expect(skill!.hide).toBe(true); } finally { - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); } }); @@ -388,7 +389,7 @@ description: Skill loaded from a tilde-expanded custom directory. expect(withTilde.length).toBe(withoutTilde.length); expect(withTilde.some(skill => skill.name === "tilde-skill")).toBe(true); } finally { - await fs.rm(tempHomeSkillsDir, { recursive: true, force: true }); + await removeWithRetries(tempHomeSkillsDir); } }); diff --git a/packages/coding-agent/test/ssh/connection-manager.test.ts b/packages/coding-agent/test/ssh/connection-manager.test.ts index bd2213fa5..38f530e8f 100644 --- a/packages/coding-agent/test/ssh/connection-manager.test.ts +++ b/packages/coding-agent/test/ssh/connection-manager.test.ts @@ -3,6 +3,7 @@ import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; import * as connectionManager from "@oh-my-pi/pi-coding-agent/ssh/connection-manager"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; async function withLooseKey(run: (keyPath: string) => Promise): Promise { const dir = await fs.mkdtemp(path.join(os.tmpdir(), "omp-ssh-key-")); @@ -12,7 +13,7 @@ async function withLooseKey(run: (keyPath: string) => Promise): Promise try { return await run(keyPath); } finally { - await fs.rm(dir, { recursive: true, force: true }); + await removeWithRetries(dir); } } @@ -87,7 +88,7 @@ describe("buildRemoteCommand", () => { ), ).rejects.toThrow("SSH key not found"); } finally { - await fs.rm(dir, { recursive: true, force: true }); + await removeWithRetries(dir); } }); @@ -106,7 +107,7 @@ describe("buildRemoteCommand", () => { ), ).rejects.toThrow("SSH key is not a file"); } finally { - await fs.rm(keyPath, { recursive: true, force: true }); + await removeWithRetries(keyPath); } }); diff --git a/packages/coding-agent/test/status-line-path.test.ts b/packages/coding-agent/test/status-line-path.test.ts index 6790f927d..b02f01edd 100644 --- a/packages/coding-agent/test/status-line-path.test.ts +++ b/packages/coding-agent/test/status-line-path.test.ts @@ -5,7 +5,7 @@ import * as path from "node:path"; import type { SegmentContext } from "@oh-my-pi/pi-coding-agent/modes/components/status-line/segments"; import { renderSegment } from "@oh-my-pi/pi-coding-agent/modes/components/status-line/segments"; import { initTheme, theme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme"; -import { getProjectDir, setProjectDir } from "@oh-my-pi/pi-utils"; +import { getProjectDir, removeSyncWithRetries, setProjectDir } from "@oh-my-pi/pi-utils"; const originalProjectDir = getProjectDir(); beforeAll(async () => { @@ -97,8 +97,8 @@ describe("status line path segment", () => { expect(rendered.content).not.toContain(`${path.sep}Projects${path.sep}`); } finally { setProjectDir(originalProjectDir); - fs.rmSync(aliasRoot, { recursive: true, force: true }); - fs.rmSync(realProjectDir, { recursive: true, force: true }); + removeSyncWithRetries(aliasRoot); + removeSyncWithRetries(realProjectDir); } }); @@ -116,7 +116,7 @@ describe("status line path segment", () => { expect(rendered.content).not.toContain(os.tmpdir()); } finally { setProjectDir(originalProjectDir); - fs.rmSync(scratchDir, { recursive: true, force: true }); + removeSyncWithRetries(scratchDir); } }); @@ -134,7 +134,7 @@ describe("status line path segment", () => { expect(rendered.content).not.toContain(os.tmpdir()); } finally { setProjectDir(originalProjectDir); - fs.rmSync(scratchDir, { recursive: true, force: true }); + removeSyncWithRetries(scratchDir); } }); @@ -151,7 +151,7 @@ describe("status line path segment", () => { expect(rendered.content).not.toContain(theme.icon.scratchFolder); } finally { setProjectDir(originalProjectDir); - fs.rmSync(scratchDir, { recursive: true, force: true }); + removeSyncWithRetries(scratchDir); } }); @@ -168,7 +168,7 @@ describe("status line path segment", () => { expect(rendered.content).not.toContain(theme.icon.scratchFolder); } finally { setProjectDir(originalProjectDir); - fs.rmSync(realProjectDir, { recursive: true, force: true }); + removeSyncWithRetries(realProjectDir); } }); diff --git a/packages/coding-agent/test/status-line-settings-cache.test.ts b/packages/coding-agent/test/status-line-settings-cache.test.ts index c86302a3d..944c60829 100644 --- a/packages/coding-agent/test/status-line-settings-cache.test.ts +++ b/packages/coding-agent/test/status-line-settings-cache.test.ts @@ -8,7 +8,7 @@ import { StatusLineComponent, type StatusLineSettings } from "@oh-my-pi/pi-codin import { STATUS_LINE_PRESETS } from "@oh-my-pi/pi-coding-agent/modes/components/status-line/presets"; import { initTheme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme"; import * as git from "@oh-my-pi/pi-coding-agent/utils/git"; -import { setProjectDir } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, setProjectDir } from "@oh-my-pi/pi-utils"; import { beginSettingsTest, restoreSettingsTestState, type SettingsTestState } from "./helpers/settings-test-state"; let settingsState: SettingsTestState | undefined; @@ -26,7 +26,7 @@ afterEach(() => { restoreSettingsTestState(settingsState); settingsState = undefined; if (projectDir) { - fs.rmSync(projectDir, { recursive: true, force: true }); + removeSyncWithRetries(projectDir); } projectDir = ""; }); diff --git a/packages/coding-agent/test/streaming-edit-abort.test.ts b/packages/coding-agent/test/streaming-edit-abort.test.ts index 137bd4fb5..c9f7e114a 100644 --- a/packages/coding-agent/test/streaming-edit-abort.test.ts +++ b/packages/coding-agent/test/streaming-edit-abort.test.ts @@ -17,7 +17,7 @@ import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; import * as autoGeneratedGuard from "@oh-my-pi/pi-coding-agent/tools/auto-generated-guard"; import { ToolError } from "@oh-my-pi/pi-coding-agent/tools/tool-errors"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; import { type } from "arktype"; function createAssistantMessage(content: AssistantMessage["content"], stopReason: StopReason): AssistantMessage { @@ -224,7 +224,7 @@ beforeEach(() => { afterEach(async () => { if (tempDir) { - fs.rmSync(tempDir, { recursive: true, force: true }); + removeSyncWithRetries(tempDir); } }); diff --git a/packages/coding-agent/test/streaming-output.test.ts b/packages/coding-agent/test/streaming-output.test.ts index eddda058c..f18d05fa9 100644 --- a/packages/coding-agent/test/streaming-output.test.ts +++ b/packages/coding-agent/test/streaming-output.test.ts @@ -15,6 +15,7 @@ import { truncateTail, truncateTailBytes, } from "@oh-my-pi/pi-coding-agent/session/streaming-output"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; const createdTempDirs: string[] = []; const originalForceProtocol = Bun.env.PI_FORCE_IMAGE_PROTOCOL; @@ -32,7 +33,7 @@ function byteLength(text: string): number { afterEach(async () => { for (const dir of createdTempDirs.splice(0)) { - await fs.rm(dir, { recursive: true, force: true }); + await removeWithRetries(dir); } if (originalForceProtocol === undefined) delete Bun.env.PI_FORCE_IMAGE_PROTOCOL; else Bun.env.PI_FORCE_IMAGE_PROTOCOL = originalForceProtocol; diff --git a/packages/coding-agent/test/streaming-preview-height.test.ts b/packages/coding-agent/test/streaming-preview-height.test.ts index 4552f6dfb..13eb892dc 100644 --- a/packages/coding-agent/test/streaming-preview-height.test.ts +++ b/packages/coding-agent/test/streaming-preview-height.test.ts @@ -9,6 +9,7 @@ import { ToolExecutionComponent } from "@oh-my-pi/pi-coding-agent/modes/componen import { theme as activeTheme, initTheme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme"; import { previewWindowRows } from "@oh-my-pi/pi-coding-agent/tools/render-utils"; import { TUI, visibleWidth } from "@oh-my-pi/pi-tui"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; import { VirtualTerminal } from "../../tui/test/virtual-terminal"; // The streaming edit preview is a fixed-height tail window ("cursor"): the last @@ -69,7 +70,7 @@ describe("streaming edit preview height (stable, full tail window)", () => { afterEach(async () => { resetSettingsForTest(); - await fs.rm(tmpDir, { recursive: true, force: true }); + await removeWithRetries(tmpDir); }); // Char-by-char partials of the new function body. diff --git a/packages/coding-agent/test/stt-preflight.test.ts b/packages/coding-agent/test/stt-preflight.test.ts index 836eda040..a13c00375 100644 --- a/packages/coding-agent/test/stt-preflight.test.ts +++ b/packages/coding-agent/test/stt-preflight.test.ts @@ -6,7 +6,7 @@ import { Settings, settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import * as downloader from "@oh-my-pi/pi-coding-agent/stt/downloader"; import * as recorder from "@oh-my-pi/pi-coding-agent/stt/recorder"; import { STTController } from "@oh-my-pi/pi-coding-agent/stt/stt-controller"; -import { getTinyModelsCacheDir, setAgentDir } from "@oh-my-pi/pi-utils"; +import { getTinyModelsCacheDir, removeWithRetries, setAgentDir } from "@oh-my-pi/pi-utils"; import { beginSettingsTest, restoreSettingsTestState, type SettingsTestState } from "./helpers/settings-test-state"; const WHISPER_BASE_REPO = "onnx-community/whisper-base"; @@ -31,7 +31,7 @@ describe("isSttModelCached completeness", () => { afterEach(async () => { restoreSettingsTestState(state); - await fs.rm(tmp, { recursive: true, force: true }); + await removeWithRetries(tmp); }); it("treats a transformers model as cached only when both encoder and decoder onnx are present", async () => { diff --git a/packages/coding-agent/test/system-prompt-model.test.ts b/packages/coding-agent/test/system-prompt-model.test.ts index 92bc6c907..8a0eba4f3 100644 --- a/packages/coding-agent/test/system-prompt-model.test.ts +++ b/packages/coding-agent/test/system-prompt-model.test.ts @@ -10,6 +10,7 @@ import { AgentSession } from "@oh-my-pi/pi-coding-agent/session/agent-session"; import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; import { buildSystemPrompt } from "@oh-my-pi/pi-coding-agent/system-prompt"; +import { removeSyncWithRetries } from "@oh-my-pi/pi-utils"; import { cleanupTempHome } from "./helpers/temp-home-cleanup"; const EMPTY_TREE = { @@ -80,7 +81,7 @@ describe("AgentSession model-change prompt refresh", () => { session = undefined; } authStorage.close(); - fs.rmSync(tempDir, { recursive: true, force: true }); + removeSyncWithRetries(tempDir); }); function pickTwoModels(): [Model, Model] { diff --git a/packages/coding-agent/test/task/discovery.test.ts b/packages/coding-agent/test/task/discovery.test.ts index c498e63d4..63d3878c2 100644 --- a/packages/coding-agent/test/task/discovery.test.ts +++ b/packages/coding-agent/test/task/discovery.test.ts @@ -3,6 +3,7 @@ import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; import { discoverAgents } from "@oh-my-pi/pi-coding-agent/task/discovery"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; const OMP_AGENT_MD = [ "---", @@ -34,7 +35,7 @@ describe("discoverAgents", () => { }); afterEach(async () => { - await fs.rm(tempHome, { recursive: true, force: true }); + await removeWithRetries(tempHome); }); test("loads OMP agents but skips Claude Code custom agents", async () => { diff --git a/packages/coding-agent/test/task/subagent-lsp.test.ts b/packages/coding-agent/test/task/subagent-lsp.test.ts index fc7e252d4..84afdfca5 100644 --- a/packages/coding-agent/test/task/subagent-lsp.test.ts +++ b/packages/coding-agent/test/task/subagent-lsp.test.ts @@ -16,6 +16,7 @@ import type { AgentDefinition, TaskParams } from "@oh-my-pi/pi-coding-agent/task import type { IsolationHandle, WorktreeBaseline } from "@oh-my-pi/pi-coding-agent/task/worktree"; import * as worktreeModule from "@oh-my-pi/pi-coding-agent/task/worktree"; import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; import "@oh-my-pi/pi-coding-agent/tools/yield"; import { EventBus } from "@oh-my-pi/pi-coding-agent/utils/event-bus"; @@ -262,7 +263,7 @@ describe("subagent LSP availability", () => { expect(getOptions()?.cwd).toBe("/tmp/isolated-subagent"); expect(sessionManager?.getCwd?.()).toBe("/tmp/isolated-subagent"); } finally { - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); } }); diff --git a/packages/coding-agent/test/task/worktree.test.ts b/packages/coding-agent/test/task/worktree.test.ts index c72e42fd8..5a6ff3dd3 100644 --- a/packages/coding-agent/test/task/worktree.test.ts +++ b/packages/coding-agent/test/task/worktree.test.ts @@ -14,6 +14,7 @@ import { } from "@oh-my-pi/pi-coding-agent/task/worktree"; import * as jj from "@oh-my-pi/pi-coding-agent/utils/jj"; import * as natives from "@oh-my-pi/pi-natives"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; const tempDirs: string[] = []; @@ -54,7 +55,7 @@ async function createGitRepo(): Promise<{ baseBranch: string; repo: string }> { afterEach(async () => { vi.restoreAllMocks(); jj.repo.clearRootCache(); - await Promise.all(tempDirs.splice(0).map(dir => fs.rm(dir, { recursive: true, force: true }))); + await Promise.all(tempDirs.splice(0).map(dir => removeWithRetries(dir))); }); describe("worktree isolation helpers", () => { it("returns platform-specific null path for git --no-index diffs", () => { @@ -113,7 +114,7 @@ describe("worktree isolation helpers", () => { }); afterAll(async () => { - await fs.rm(repo, { recursive: true, force: true }); + await removeWithRetries(repo); }); afterEach(() => { @@ -307,7 +308,7 @@ describe("applyNestedPatches", () => { }); afterEach(async () => { - await fs.rm(parentRepo, { recursive: true, force: true }); + await removeWithRetries(parentRepo); }); it("does not fold pre-existing dirty nested-repo state into the agent commit", async () => { diff --git a/packages/coding-agent/test/theme-islight.test.ts b/packages/coding-agent/test/theme-islight.test.ts index 964cc3d41..3d3642002 100644 --- a/packages/coding-agent/test/theme-islight.test.ts +++ b/packages/coding-agent/test/theme-islight.test.ts @@ -5,7 +5,7 @@ import * as path from "node:path"; import { generateThemeVars } from "@oh-my-pi/pi-coding-agent/export/html"; import { defaultThemes } from "@oh-my-pi/pi-coding-agent/modes/theme/defaults"; import { getResolvedThemeColors, getThemeByName, isLightTheme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme"; -import { getAgentDir, getCustomThemesDir, setAgentDir } from "@oh-my-pi/pi-utils"; +import { getAgentDir, getCustomThemesDir, removeWithRetries, setAgentDir } from "@oh-my-pi/pi-utils"; describe("Theme.isLight", () => { it("classifies built-in themes by their status-line surface", async () => { @@ -73,7 +73,7 @@ describe("getResolvedThemeColors HTML export defaults", () => { } else { process.env.PI_CODING_AGENT_DIR = originalAgentDirEnv; } - await fs.rm(tempAgentDir, { recursive: true, force: true }); + await removeWithRetries(tempAgentDir); tempAgentDir = undefined; }); diff --git a/packages/coding-agent/test/theme-spinner-frames.test.ts b/packages/coding-agent/test/theme-spinner-frames.test.ts index dd421ec27..14ccbc2ff 100644 --- a/packages/coding-agent/test/theme-spinner-frames.test.ts +++ b/packages/coding-agent/test/theme-spinner-frames.test.ts @@ -7,7 +7,7 @@ import { sharedSpinnerFrame, } from "@oh-my-pi/pi-coding-agent/modes/components/tool-execution"; import { getThemeByName } from "@oh-my-pi/pi-coding-agent/modes/theme/theme"; -import { getConfigRootDir, getCustomThemesDir, setAgentDir } from "@oh-my-pi/pi-utils"; +import { getConfigRootDir, getCustomThemesDir, removeWithRetries, setAgentDir } from "@oh-my-pi/pi-utils"; // Path of the built-in dark theme JSON, used as a known-valid base we can // extend with custom `symbols.spinnerFrames` shapes. @@ -44,7 +44,7 @@ describe("theme symbols.spinnerFrames", () => { setAgentDir(fallbackAgentDir); delete process.env.PI_CODING_AGENT_DIR; } - await fs.rm(tmpAgentDir, { recursive: true, force: true }); + await removeWithRetries(tmpAgentDir); }); it("flat-array override applies to both status and activity spinners", async () => { diff --git a/packages/coding-agent/test/tools/apply-patch-renderer.test.ts b/packages/coding-agent/test/tools/apply-patch-renderer.test.ts index 327dced6a..547d7139a 100644 --- a/packages/coding-agent/test/tools/apply-patch-renderer.test.ts +++ b/packages/coding-agent/test/tools/apply-patch-renderer.test.ts @@ -7,6 +7,7 @@ import { ToolExecutionComponent } from "@oh-my-pi/pi-coding-agent/modes/componen import * as themeModule from "@oh-my-pi/pi-coding-agent/modes/theme/theme"; import { toolRenderers } from "@oh-my-pi/pi-coding-agent/tools/renderers"; import type { TUI } from "@oh-my-pi/pi-tui"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; async function getUiTheme() { await themeModule.initTheme(false, undefined, undefined, "dark", "light"); @@ -148,7 +149,7 @@ describe("apply_patch rendering", () => { expect(after).toContain("(preview)"); expect(after).toContain("const value = 2;"); } finally { - await fs.rm(tmpDir, { recursive: true, force: true }); + await removeWithRetries(tmpDir); } }); @@ -176,7 +177,7 @@ describe("apply_patch rendering", () => { expect(setTimeoutSpy).not.toHaveBeenCalled(); } finally { setTimeoutSpy.mockRestore(); - await fs.rm(tmpDir, { recursive: true, force: true }); + await removeWithRetries(tmpDir); } }); diff --git a/packages/coding-agent/test/tools/approval-mode.test.ts b/packages/coding-agent/test/tools/approval-mode.test.ts index a5fa4ce2d..2b0af2767 100644 --- a/packages/coding-agent/test/tools/approval-mode.test.ts +++ b/packages/coding-agent/test/tools/approval-mode.test.ts @@ -8,7 +8,7 @@ import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { createAgentSession } from "@oh-my-pi/pi-coding-agent/sdk"; import type { AgentSession } from "@oh-my-pi/pi-coding-agent/session/agent-session"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; const BASE_SETTINGS = { "async.enabled": false, @@ -66,7 +66,7 @@ describe("tools.approvalMode setting", () => { // Windows can briefly hold tempdir handles after session.dispose(); retry a few times. for (let attempt = 0; attempt < 5; attempt++) { try { - fs.rmSync(tempDir, { recursive: true, force: true }); + removeSyncWithRetries(tempDir); break; } catch (err) { const code = (err as NodeJS.ErrnoException).code; diff --git a/packages/coding-agent/test/tools/ast-edit.test.ts b/packages/coding-agent/test/tools/ast-edit.test.ts index 5bd45a194..1a5b8f9d5 100644 --- a/packages/coding-agent/test/tools/ast-edit.test.ts +++ b/packages/coding-agent/test/tools/ast-edit.test.ts @@ -6,6 +6,7 @@ import { adaptSchemaForStrict, toolWireSchema } from "@oh-my-pi/pi-ai/utils/sche import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { ToolChoiceQueue } from "@oh-my-pi/pi-coding-agent/session/tool-choice-queue"; import { createTools, type ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; type InvokedToolResult = { content: Array<{ type: string; text?: string }>; @@ -85,7 +86,7 @@ describe("ast_edit tool schema", () => { expect(addedLine).toMatch(/^\+\d+:/); expect(removedLine?.split(":", 1)[0].length).toBe(addedLine?.split(":", 1)[0].length); } finally { - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); } }); @@ -129,7 +130,7 @@ describe("ast_edit tool schema", () => { const updated = await Bun.file(filePath).text(); expect(updated).toContain("modernWrap(x, value)"); } finally { - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); } }); @@ -172,7 +173,7 @@ describe("ast_edit tool schema", () => { ).toBe(0); expect(await Bun.file(filePath).text()).toBe(mutatedContent); } finally { - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); } }); @@ -235,7 +236,7 @@ describe("ast_edit tool schema", () => { "legacyWrap(outsideValue, outsideArg)", ); } finally { - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); } }); @@ -271,7 +272,7 @@ describe("ast_edit tool schema", () => { await invoker({ action: "apply", reason: "apply tlaplus AST edit" }); expect(await Bun.file(filePath).text()).toContain("Start == x = 0"); } finally { - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); } }); }); diff --git a/packages/coding-agent/test/tools/ast-grep.test.ts b/packages/coding-agent/test/tools/ast-grep.test.ts index f584859dd..406e2d55b 100644 --- a/packages/coding-agent/test/tools/ast-grep.test.ts +++ b/packages/coding-agent/test/tools/ast-grep.test.ts @@ -4,6 +4,7 @@ import * as os from "node:os"; import * as path from "node:path"; import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { createTools, type ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; function createTestSession(cwd = "/tmp/test", overrides: Partial = {}): ToolSession { return { @@ -43,7 +44,7 @@ describe("ast_grep parse errors", () => { expect(details?.parseErrors?.[0]).not.toContain("someUnlikelyCall($A):"); expect(text.match(/parse error \(syntax tree contains error nodes\)/g)?.length ?? 0).toBe(1); } finally { - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); } }); it("caps parseErrors at PARSE_ERRORS_LIMIT and records the original total", async () => { @@ -73,7 +74,7 @@ describe("ast_grep parse errors", () => { expect(details?.parseErrorsTotal).toBe(fileCount); expect(text).toContain(`Parse issues (20 / ${fileCount}):`); } finally { - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); } }); it("combines globbing from path and glob parameters", async () => { @@ -109,7 +110,7 @@ describe("ast_grep parse errors", () => { expect(details?.matchCount).toBe(2); expect(details?.fileCount).toBe(2); } finally { - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); } }); @@ -138,7 +139,7 @@ describe("ast_grep parse errors", () => { expect(details?.matchCount).toBe(1); expect(details?.parseErrors).toBeUndefined(); } finally { - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); } }); }); diff --git a/packages/coding-agent/test/tools/conflict-integration.test.ts b/packages/coding-agent/test/tools/conflict-integration.test.ts index 287690385..a47a90ff3 100644 --- a/packages/coding-agent/test/tools/conflict-integration.test.ts +++ b/packages/coding-agent/test/tools/conflict-integration.test.ts @@ -5,6 +5,7 @@ import * as path from "node:path"; import { resetSettingsForTest, Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { createTools, type ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; import { ConflictHistory } from "@oh-my-pi/pi-coding-agent/tools/conflict-detect"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; function createTestSession(cwd: string, overrides: Partial = {}): ToolSession { return { @@ -82,7 +83,7 @@ describe("read surfaces conflicts as a warning footer", () => { }); afterEach(async () => { - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); }); it("returns file content and appends a conflict warning with id 1", async () => { @@ -308,7 +309,7 @@ describe("write resolves conflicts via conflict://N", () => { }); afterEach(async () => { - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); }); it("splices the registered region with the supplied content", async () => { diff --git a/packages/coding-agent/test/tools/edit-renderer.test.ts b/packages/coding-agent/test/tools/edit-renderer.test.ts index 8379fa71d..2558a7fc4 100644 --- a/packages/coding-agent/test/tools/edit-renderer.test.ts +++ b/packages/coding-agent/test/tools/edit-renderer.test.ts @@ -9,6 +9,7 @@ import { editToolRenderer } from "@oh-my-pi/pi-coding-agent/edit/renderer"; import { ToolExecutionComponent } from "@oh-my-pi/pi-coding-agent/modes/components/tool-execution"; import * as themeModule from "@oh-my-pi/pi-coding-agent/modes/theme/theme"; import { Text, type TUI, visibleWidth } from "@oh-my-pi/pi-tui"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; beforeAll(async () => { resetSettingsForTest(); @@ -288,7 +289,7 @@ describe("editToolRenderer", () => { expect(rendered).toContain("export const b = 22;"); expect(rendered).not.toContain("No changes would be made"); } finally { - await fs.rm(tmpDir, { recursive: true, force: true }); + await removeWithRetries(tmpDir); } }); @@ -319,7 +320,7 @@ describe("editToolRenderer", () => { expect(rendered).toContain("export const b = 22;"); expect(rendered).not.toContain(" …"); } finally { - await fs.rm(tmpDir, { recursive: true, force: true }); + await removeWithRetries(tmpDir); } }); diff --git a/packages/coding-agent/test/tools/fetch-binary-dispatch.test.ts b/packages/coding-agent/test/tools/fetch-binary-dispatch.test.ts index 5d5bdae76..fd61a0868 100644 --- a/packages/coding-agent/test/tools/fetch-binary-dispatch.test.ts +++ b/packages/coding-agent/test/tools/fetch-binary-dispatch.test.ts @@ -10,7 +10,7 @@ import { ReadTool } from "@oh-my-pi/pi-coding-agent/tools/read"; import { zip } from "@oh-my-pi/pi-coding-agent/utils/zip"; import * as scrapers from "@oh-my-pi/pi-coding-agent/web/scrapers/types"; import * as scraperUtils from "@oh-my-pi/pi-coding-agent/web/scrapers/utils"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; function makeSession(testDir: string): ToolSession { const sessionFile = path.join(testDir, "session.jsonl"); @@ -108,7 +108,7 @@ describe("read URL binary dispatch", () => { afterEach(() => { vi.restoreAllMocks(); - fs.rmSync(testDir, { recursive: true, force: true }); + removeSyncWithRetries(testDir); }); it("lists a remote zip instead of dumping decoded bytes", async () => { diff --git a/packages/coding-agent/test/tools/fetch-kagi-toggle.test.ts b/packages/coding-agent/test/tools/fetch-kagi-toggle.test.ts index de67c08b7..63f4707d2 100644 --- a/packages/coding-agent/test/tools/fetch-kagi-toggle.test.ts +++ b/packages/coding-agent/test/tools/fetch-kagi-toggle.test.ts @@ -10,7 +10,7 @@ import * as toolsManager from "@oh-my-pi/pi-coding-agent/utils/tools-manager"; import * as scrapers from "@oh-my-pi/pi-coding-agent/web/scrapers/types"; import * as scraperUtils from "@oh-my-pi/pi-coding-agent/web/scrapers/utils"; import * as natives from "@oh-my-pi/pi-natives"; -import { ptree, Snowflake } from "@oh-my-pi/pi-utils"; +import { ptree, removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; import { asGlobalFetch } from "../helpers/fetch-mock"; const withMissingSystemPython = () => { @@ -32,7 +32,7 @@ describe("read tool URL selector shorthands", () => { afterEach(() => { vi.restoreAllMocks(); - fs.rmSync(testDir, { recursive: true, force: true }); + removeSyncWithRetries(testDir); }); const createSession = (settingsOverrides: Partial> = {}): ToolSession => { @@ -125,7 +125,7 @@ describe("read tool URL handling", () => { afterEach(() => { vi.restoreAllMocks(); delete process.env.PARALLEL_API_KEY; - fs.rmSync(testDir, { recursive: true, force: true }); + removeSyncWithRetries(testDir); }); const createSession = (overrides: Partial> = {}): ToolSession => { diff --git a/packages/coding-agent/test/tools/fetch-raw-mode.test.ts b/packages/coding-agent/test/tools/fetch-raw-mode.test.ts index 64724615d..1f7eb5fa5 100644 --- a/packages/coding-agent/test/tools/fetch-raw-mode.test.ts +++ b/packages/coding-agent/test/tools/fetch-raw-mode.test.ts @@ -6,7 +6,7 @@ import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; import { ReadTool } from "@oh-my-pi/pi-coding-agent/tools/read"; import * as scrapers from "@oh-my-pi/pi-coding-agent/web/scrapers/types"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; const ATOM = `\nSampleOne12024-01-01T00:00:00Zbody`; const JSON_BODY = `{"alpha":1,"beta":[2,3]}`; @@ -47,7 +47,7 @@ describe("read URL with :raw selector (regression: JSON/feed parsers ignored raw }); afterEach(() => { vi.restoreAllMocks(); - fs.rmSync(testDir, { recursive: true, force: true }); + removeSyncWithRetries(testDir); }); it("returns the raw atom feed body when :raw is set", async () => { @@ -130,7 +130,7 @@ describe("read URL with multi-range selector (regression: was stuck on URL → 4 }); afterEach(() => { vi.restoreAllMocks(); - fs.rmSync(testDir, { recursive: true, force: true }); + removeSyncWithRetries(testDir); }); it("routes :A-B,C-D to the multi-range builder against the cached body", async () => { diff --git a/packages/coding-agent/test/tools/gh-cache-invalidation.test.ts b/packages/coding-agent/test/tools/gh-cache-invalidation.test.ts index 76b3b0878..b6fad560d 100644 --- a/packages/coding-agent/test/tools/gh-cache-invalidation.test.ts +++ b/packages/coding-agent/test/tools/gh-cache-invalidation.test.ts @@ -13,6 +13,7 @@ import { putCached, resetForTests as resetCacheForTests, } from "@oh-my-pi/pi-coding-agent/tools/github-cache"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; const REPO = "owner/example"; @@ -92,7 +93,7 @@ afterEach(async () => { } else { process.env.OMP_GITHUB_CACHE_DB = originalEnv; } - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); }); describe("invalidateGithubCacheForBashCommand", () => { diff --git a/packages/coding-agent/test/tools/gh.test.ts b/packages/coding-agent/test/tools/gh.test.ts index a762aeb9e..a5ae32e8a 100644 --- a/packages/coding-agent/test/tools/gh.test.ts +++ b/packages/coding-agent/test/tools/gh.test.ts @@ -13,7 +13,7 @@ import { resolveDefaultRepoMemoized, } from "@oh-my-pi/pi-coding-agent/tools/gh"; import * as git from "@oh-my-pi/pi-coding-agent/utils/git"; -import { getAgentDir, hashPath, setAgentDir } from "@oh-my-pi/pi-utils"; +import { getAgentDir, hashPath, removeWithRetries, setAgentDir } from "@oh-my-pi/pi-utils"; // Isolate every `git` invocation in this file from the developer's host // configuration. The fixture spawns dozens of git subprocesses against tiny @@ -197,7 +197,7 @@ async function setupTempHome(): Promise<{ home: string; cleanup: () => Promise { setAgentDir(originalAgentDir); - await fs.rm(home, { recursive: true, force: true }); + await removeWithRetries(home); }, }; } @@ -267,7 +267,7 @@ describe("github tool", () => { afterAll(async () => { if (prFixtureTemplate) { - await fs.rm(prFixtureTemplate.baseDir, { recursive: true, force: true }); + await removeWithRetries(prFixtureTemplate.baseDir); prFixtureTemplate = null; } }); @@ -777,7 +777,7 @@ describe("github tool", () => { }); afterAll(async () => { await tempHome.cleanup(); - await fs.rm(fixture.baseDir, { recursive: true, force: true }); + await removeWithRetries(fixture.baseDir); }); it("checks out a pull request into a worktree and configures contributor push metadata", async () => { @@ -826,7 +826,7 @@ describe("github tool", () => { remoteFixture = await createPrFixture(); }); afterAll(async () => { - await fs.rm(remoteFixture.baseDir, { recursive: true, force: true }); + await removeWithRetries(remoteFixture.baseDir); }); it("treats git.remote.add as a no-op when the remote already exists with the same URL", async () => { @@ -865,7 +865,7 @@ describe("github tool", () => { expect(dump).toContain(`branch.race-test.key${idx} value-${idx}`); } } finally { - await fs.rm(repoRoot, { recursive: true, force: true }); + await removeWithRetries(repoRoot); } }); @@ -879,7 +879,7 @@ describe("github tool", () => { }); afterAll(async () => { await tempHome.cleanup(); - await fs.rm(fixture.baseDir, { recursive: true, force: true }); + await removeWithRetries(fixture.baseDir); }); it("checks out multiple pull requests in a single call when pr is an array", async () => { @@ -946,7 +946,7 @@ describe("github tool", () => { runGit(fixture.repoRoot, ["commit", "-m", "manual branch commit"]); }); afterAll(async () => { - await fs.rm(fixture.baseDir, { recursive: true, force: true }); + await removeWithRetries(fixture.baseDir); }); it("rejects PR pushes from branches without checkout metadata", async () => { @@ -1049,7 +1049,7 @@ describe("github tool", () => { expect(artifactText).toContain("epsilon"); expect(artifactText).toContain("zeta"); } finally { - await fs.rm(artifactsDir, { recursive: true, force: true }); + await removeWithRetries(artifactsDir); } }); diff --git a/packages/coding-agent/test/tools/github-cache.test.ts b/packages/coding-agent/test/tools/github-cache.test.ts index c800e25c6..865664924 100644 --- a/packages/coding-agent/test/tools/github-cache.test.ts +++ b/packages/coding-agent/test/tools/github-cache.test.ts @@ -21,6 +21,7 @@ import { } from "@oh-my-pi/pi-coding-agent/tools/github-cache"; import { ToolAbortError, throwIfAborted } from "@oh-my-pi/pi-coding-agent/tools/tool-errors"; import * as git from "@oh-my-pi/pi-coding-agent/utils/git"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; const TEST_REPO = "owner/example"; const TEST_AUTH_KEY = "test-auth"; @@ -43,7 +44,7 @@ afterEach(async () => { process.env.OMP_GITHUB_CACHE_DB = originalEnv; } vi.restoreAllMocks(); - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); }); function issuePayload(number: number, body: string) { diff --git a/packages/coding-agent/test/tools/glob-validate-paths.test.ts b/packages/coding-agent/test/tools/glob-validate-paths.test.ts index 0fe58c82d..91187f689 100644 --- a/packages/coding-agent/test/tools/glob-validate-paths.test.ts +++ b/packages/coding-agent/test/tools/glob-validate-paths.test.ts @@ -11,6 +11,7 @@ import { splitDelimitedPathEntry, } from "@oh-my-pi/pi-coding-agent/tools/path-utils"; import type { Component } from "@oh-my-pi/pi-tui"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; import { globToolRenderer } from "../../src/tools/glob"; let uiTheme: Theme; @@ -45,7 +46,7 @@ describe("delimited path expansion", () => { }); afterEach(async () => { - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); }); it("splits comma, semicolon, and space delimited entries when parts resolve", async () => { diff --git a/packages/coding-agent/test/tools/grep-internal-urls.test.ts b/packages/coding-agent/test/tools/grep-internal-urls.test.ts index 22df28691..4cee14278 100644 --- a/packages/coding-agent/test/tools/grep-internal-urls.test.ts +++ b/packages/coding-agent/test/tools/grep-internal-urls.test.ts @@ -17,6 +17,7 @@ import { AgentRegistry } from "@oh-my-pi/pi-coding-agent/registry/agent-registry import * as sshFileTransfer from "@oh-my-pi/pi-coding-agent/ssh/file-transfer"; import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; import { ReadTool } from "@oh-my-pi/pi-coding-agent/tools/read"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; import { GlobTool } from "../../src/tools/glob"; import { GrepTool } from "../../src/tools/grep"; @@ -90,7 +91,7 @@ describe("GrepTool internal URL resolution", () => { }); afterEach(async () => { - await fs.rm(tmpDir, { recursive: true, force: true }); + await removeWithRetries(tmpDir); AgentRegistry.resetGlobalForTests(); LocalProtocolHandler.resetOverrideForTests(); InternalUrlRouter.resetForTests(); diff --git a/packages/coding-agent/test/tools/grep-invalid-regex.test.ts b/packages/coding-agent/test/tools/grep-invalid-regex.test.ts index a139d82bd..e458c4d00 100644 --- a/packages/coding-agent/test/tools/grep-invalid-regex.test.ts +++ b/packages/coding-agent/test/tools/grep-invalid-regex.test.ts @@ -5,6 +5,7 @@ import * as path from "node:path"; import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { GrepTool, type ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; import { ToolError } from "@oh-my-pi/pi-coding-agent/tools/tool-errors"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; function createTestSession(cwd: string, overrides: Partial = {}): ToolSession { return { @@ -26,7 +27,7 @@ describe("search tool invalid regex handling", () => { }); afterEach(async () => { - await fs.rm(cwd, { recursive: true, force: true }); + await removeWithRetries(cwd); }); it("wraps invalid regex pattern errors in a ToolError", async () => { diff --git a/packages/coding-agent/test/tools/grep-path-lists.test.ts b/packages/coding-agent/test/tools/grep-path-lists.test.ts index fa0348134..cdd445c18 100644 --- a/packages/coding-agent/test/tools/grep-path-lists.test.ts +++ b/packages/coding-agent/test/tools/grep-path-lists.test.ts @@ -20,6 +20,7 @@ import type { SessionEntry, SessionTreeNode } from "@oh-my-pi/pi-coding-agent/se import { ToolChoiceQueue } from "@oh-my-pi/pi-coding-agent/session/tool-choice-queue"; import { createTools, type ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; import { Text } from "@oh-my-pi/pi-tui"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; import { grepToolRenderer } from "../../src/tools/grep"; function createTestSession(cwd: string, overrides: Partial = {}): ToolSession { @@ -137,7 +138,7 @@ describe("tool path arrays", () => { }); afterAll(async () => { - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); resetSettingsForTest(); }); @@ -279,7 +280,7 @@ describe("tool path arrays", () => { paths: ["apps/[id]"], }); expect(getText(dir)).toContain("bracket-needle"); - await fs.rm(tmp, { recursive: true, force: true }); + await removeWithRetries(tmp); }); it("grep pending renderer accepts a single string path", () => { @@ -373,7 +374,7 @@ describe("tool path arrays", () => { // single-string `paths` arg shows up as the "in " scope meta on the // pending call line (a completed result merges the call line away). expect(rendered).toContain("in folder with spaces/"); - await fs.rm(tmp, { recursive: true, force: true }); + await removeWithRetries(tmp); }); it("tree selector renders a single-string grep path summary", () => { @@ -500,7 +501,7 @@ describe("tool path arrays", () => { expect(text).toContain("Successfully wrote 8 bytes to written.txt"); expect(text).not.toContain(tmp); expect(await Bun.file(absoluteTarget).text()).toBe("written\n"); - await fs.rm(tmp, { recursive: true, force: true }); + await removeWithRetries(tmp); }); it("read expands comma-delimited paths", async () => { @@ -648,7 +649,7 @@ describe("tool path arrays", () => { ); expect(await Bun.file(path.join(tmp, "phases", "ast.ts")).text()).toContain("modernWrap(phasesValue, phasesArg)"); expect(await Bun.file(path.join(tmp, "other", "ast.ts")).text()).toContain("legacyWrap(otherValue, otherArg)"); - await fs.rm(tmp, { recursive: true, force: true }); + await removeWithRetries(tmp); }); it("find accepts explicit path arrays", async () => { @@ -787,7 +788,7 @@ describe("tool path arrays", () => { expect(details?.files).toEqual([expectedPath]); expect(details?.scopePath).toBe(outsideDir.replace(/\\/g, "/")); } finally { - await fs.rm(outsideDir, { recursive: true, force: true }); + await removeWithRetries(outsideDir); } }); @@ -839,7 +840,7 @@ describe("tool path arrays", () => { expect(text).not.toContain("nested"); expect(details?.fileCount).toBe(2); expect(details?.scopePath).toBe("alpha.txt, beta.txt"); - await fs.rm(tmp, { recursive: true, force: true }); + await removeWithRetries(tmp); }); it("grep renders only file headings that have child lines", async () => { @@ -888,6 +889,6 @@ describe("tool path arrays", () => { expect(text).toMatch(/ 1:#if FLAG/); expect(text).toMatch(/\*2:needle/); expect(text).toMatch(/ 3:#endif/); - await fs.rm(tmp, { recursive: true, force: true }); + await removeWithRetries(tmp); }); }); diff --git a/packages/coding-agent/test/tools/image-gen.test.ts b/packages/coding-agent/test/tools/image-gen.test.ts index 86ef5d48e..306f34bd3 100644 --- a/packages/coding-agent/test/tools/image-gen.test.ts +++ b/packages/coding-agent/test/tools/image-gen.test.ts @@ -1,5 +1,4 @@ import { afterEach, describe, expect, it } from "bun:test"; -import * as fs from "node:fs/promises"; import type { Model } from "@oh-my-pi/pi-ai"; import type { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry"; import type { CustomToolContext } from "@oh-my-pi/pi-coding-agent/extensibility/custom-tools"; @@ -10,12 +9,13 @@ import { imageGenTool, setPreferredImageProvider, } from "@oh-my-pi/pi-coding-agent/tools/image-gen"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; const originalOpenRouterKey = Bun.env.OPENROUTER_API_KEY; const generatedImagePaths: string[] = []; afterEach(async () => { - await Promise.all(generatedImagePaths.splice(0).map(imagePath => fs.rm(imagePath, { force: true }))); + await Promise.all(generatedImagePaths.splice(0).map(imagePath => removeWithRetries(imagePath))); if (originalOpenRouterKey === undefined) { delete Bun.env.OPENROUTER_API_KEY; } else { diff --git a/packages/coding-agent/test/tools/inspect-image.test.ts b/packages/coding-agent/test/tools/inspect-image.test.ts index 3393cd57f..91dfcc661 100644 --- a/packages/coding-agent/test/tools/inspect-image.test.ts +++ b/packages/coding-agent/test/tools/inspect-image.test.ts @@ -13,7 +13,7 @@ import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; import { InspectImageTool } from "@oh-my-pi/pi-coding-agent/tools/inspect-image"; import { inspectImageToolRenderer } from "@oh-my-pi/pi-coding-agent/tools/inspect-image-renderer"; import { toolRenderers } from "@oh-my-pi/pi-coding-agent/tools/renderers"; -import { sanitizeText } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, sanitizeText } from "@oh-my-pi/pi-utils"; import { type } from "arktype"; const TINY_PNG_BASE64 = @@ -129,7 +129,7 @@ describe("InspectImageTool", () => { }); afterEach(() => { - fs.rmSync(testDir, { recursive: true, force: true }); + removeSyncWithRetries(testDir); }); it("sends image and question to completeSimple and returns text-only result", async () => { diff --git a/packages/coding-agent/test/tools/multi-grep-path.test.ts b/packages/coding-agent/test/tools/multi-grep-path.test.ts index ca3e6a2a6..00cc03ad4 100644 --- a/packages/coding-agent/test/tools/multi-grep-path.test.ts +++ b/packages/coding-agent/test/tools/multi-grep-path.test.ts @@ -5,6 +5,7 @@ import * as path from "node:path"; import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { createTools, type ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; import { resolveExplicitSearchPaths } from "@oh-my-pi/pi-coding-agent/tools/path-utils"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; const isWindows = process.platform === "win32"; @@ -53,7 +54,7 @@ describe.skipIf(isWindows)("search with omitted paths", () => { }); afterEach(async () => { - await fs.rm(cwd, { recursive: true, force: true }); + await removeWithRetries(cwd); }); it("defaults to the workspace root when paths is omitted", async () => { @@ -103,11 +104,7 @@ describe.skipIf(isWindows)("search across unrelated filesystem trees", () => { }); afterEach(async () => { - await Promise.all([ - fs.rm(dirA, { recursive: true, force: true }), - fs.rm(dirB, { recursive: true, force: true }), - fs.rm(cwd, { recursive: true, force: true }), - ]); + await Promise.all([removeWithRetries(dirA), removeWithRetries(dirB), removeWithRetries(cwd)]); }); it("returns matches from both trees without rooting the scan at /", async () => { @@ -151,7 +148,7 @@ describe.skipIf(isWindows)("resolveExplicitSearchPaths shared non-root ancestor" }); afterEach(async () => { - await fs.rm(parent, { recursive: true, force: true }); + await removeWithRetries(parent); }); it("fans out per-path targets instead of walking the unrequested ancestor", async () => { @@ -198,7 +195,7 @@ describe.skipIf(isWindows)("search with explicit walker-pruned file targets", () }); afterEach(async () => { - await fs.rm(repo, { recursive: true, force: true }); + await removeWithRetries(repo); }); it("matches inside an explicit .git/config target alongside a directory scope", async () => { diff --git a/packages/coding-agent/test/tools/multi-path-missing.test.ts b/packages/coding-agent/test/tools/multi-path-missing.test.ts index 67450de2e..da8e1a4e9 100644 --- a/packages/coding-agent/test/tools/multi-path-missing.test.ts +++ b/packages/coding-agent/test/tools/multi-path-missing.test.ts @@ -4,6 +4,7 @@ import * as os from "node:os"; import * as path from "node:path"; import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { createTools, type ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; // Regression for grievances #208 (find) and #209 (search): a multi-path call // that includes an entry which does not exist on disk must not abort the whole @@ -39,7 +40,7 @@ describe("multi-path tools tolerate missing entries", () => { }); afterEach(async () => { - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); }); it("search returns matches from existing paths and reports the missing one", async () => { diff --git a/packages/coding-agent/test/tools/plan-mode-guard-local.test.ts b/packages/coding-agent/test/tools/plan-mode-guard-local.test.ts index 2622613e8..ab5166bc1 100644 --- a/packages/coding-agent/test/tools/plan-mode-guard-local.test.ts +++ b/packages/coding-agent/test/tools/plan-mode-guard-local.test.ts @@ -5,6 +5,7 @@ import * as path from "node:path"; import type { PlanModeState } from "@oh-my-pi/pi-coding-agent/plan-mode/state"; import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; import { enforcePlanModeWrite, resolvePlanPath } from "@oh-my-pi/pi-coding-agent/tools/plan-mode-guard"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; const ARTIFACTS_DIR = path.join(os.tmpdir(), "agent-artifacts"); const REPO_ROOT = path.join(os.tmpdir(), "repo"); @@ -126,7 +127,7 @@ describe("enforcePlanModeWrite accepts absolute local-sandbox paths", () => { const absolute = resolvePlanPath(session, "local://my-plan.md"); expect(() => enforcePlanModeWrite(session, absolute, { op: "update" })).not.toThrow(); } finally { - await fs.rm(artifactsDir, { recursive: true, force: true }); + await removeWithRetries(artifactsDir); } }); @@ -142,7 +143,7 @@ describe("enforcePlanModeWrite accepts absolute local-sandbox paths", () => { expect(() => enforcePlanModeWrite(session, `[${absolute}]`, { op: "update" })).not.toThrow(); expect(() => enforcePlanModeWrite(session, `[local://my-plan.md#ABCD]`, { op: "update" })).not.toThrow(); } finally { - await fs.rm(artifactsDir, { recursive: true, force: true }); + await removeWithRetries(artifactsDir); } }); diff --git a/packages/coding-agent/test/tools/read-directory-range.test.ts b/packages/coding-agent/test/tools/read-directory-range.test.ts index 17aacfa5e..5701d788b 100644 --- a/packages/coding-agent/test/tools/read-directory-range.test.ts +++ b/packages/coding-agent/test/tools/read-directory-range.test.ts @@ -5,7 +5,7 @@ import * as path from "node:path"; import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; import { ReadTool } from "@oh-my-pi/pi-coding-agent/tools/read"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; function getTextOutput(result: { content: Array<{ type: string; text?: string }> }): string { return result.content @@ -43,7 +43,7 @@ describe("read tool directory listings honor line selectors (regression: was sil }); afterEach(() => { - fs.rmSync(testDir, { recursive: true, force: true }); + removeSyncWithRetries(testDir); }); it("returns the full listing when no selector is given", async () => { diff --git a/packages/coding-agent/test/tools/read-fs-not-abortable.test.ts b/packages/coding-agent/test/tools/read-fs-not-abortable.test.ts index f33fdad73..50f51ca91 100644 --- a/packages/coding-agent/test/tools/read-fs-not-abortable.test.ts +++ b/packages/coding-agent/test/tools/read-fs-not-abortable.test.ts @@ -6,7 +6,7 @@ import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; import { ReadTool } from "@oh-my-pi/pi-coding-agent/tools/read"; import { ToolAbortError } from "@oh-my-pi/pi-coding-agent/tools/tool-errors"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; function getTextOutput(result: { content: Array<{ type: string; text?: string }> }): string { return result.content @@ -52,7 +52,7 @@ describe("plain-file and directory reads ignore an already-aborted signal", () = }); afterEach(() => { - fs.rmSync(testDir, { recursive: true, force: true }); + removeSyncWithRetries(testDir); }); it("returns a plain-file line range with an aborted signal", async () => { diff --git a/packages/coding-agent/test/tools/read-local-image.test.ts b/packages/coding-agent/test/tools/read-local-image.test.ts index f84613d63..8b7f49c99 100644 --- a/packages/coding-agent/test/tools/read-local-image.test.ts +++ b/packages/coding-agent/test/tools/read-local-image.test.ts @@ -15,6 +15,7 @@ import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { InternalUrlRouter, LocalProtocolHandler, parseInternalUrl } from "@oh-my-pi/pi-coding-agent/internal-urls"; import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; import { ReadTool } from "@oh-my-pi/pi-coding-agent/tools/read"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; // 1x1 transparent PNG — small enough to pass through image loading untouched. const TINY_PNG = Buffer.from( @@ -62,7 +63,7 @@ describe("read local:// images", () => { afterEach(async () => { LocalProtocolHandler.resetOverrideForTests(); InternalUrlRouter.resetForTests(); - await fs.rm(testDir, { recursive: true, force: true }); + await removeWithRetries(testDir); }); it("decodes a local:// PNG into an inline image block", async () => { diff --git a/packages/coding-agent/test/tools/read-pdf-images.test.ts b/packages/coding-agent/test/tools/read-pdf-images.test.ts index d6bd8b142..4c4a0ebe1 100644 --- a/packages/coding-agent/test/tools/read-pdf-images.test.ts +++ b/packages/coding-agent/test/tools/read-pdf-images.test.ts @@ -14,7 +14,7 @@ import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; import { ReadTool } from "@oh-my-pi/pi-coding-agent/tools/read"; import * as markit from "@oh-my-pi/pi-coding-agent/utils/markit"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; // 1x1 transparent PNG — small enough to pass through image loading untouched. const TINY_PNG = Buffer.from( @@ -59,7 +59,7 @@ describe("read PDF image extraction", () => { }); afterEach(() => { vi.restoreAllMocks(); - fs.rmSync(testDir, { recursive: true, force: true }); + removeSyncWithRetries(testDir); }); it("rewrites image placeholders into browse handles on a full read", async () => { diff --git a/packages/coding-agent/test/tools/read-pdf-line-range.test.ts b/packages/coding-agent/test/tools/read-pdf-line-range.test.ts index 9cb6de996..cb97f146b 100644 --- a/packages/coding-agent/test/tools/read-pdf-line-range.test.ts +++ b/packages/coding-agent/test/tools/read-pdf-line-range.test.ts @@ -13,7 +13,7 @@ import { Markit } from "@oh-my-pi/pi-coding-agent/markit"; import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; import { ReadTool } from "@oh-my-pi/pi-coding-agent/tools/read"; import * as markit from "@oh-my-pi/pi-coding-agent/utils/markit"; -import { __resetDirsFromEnvForTests, Snowflake, setAgentDir } from "@oh-my-pi/pi-utils"; +import { __resetDirsFromEnvForTests, removeSyncWithRetries, setAgentDir, Snowflake } from "@oh-my-pi/pi-utils"; function restoreEnv(key: string, value: string | undefined): void { if (value === undefined) { @@ -52,7 +52,7 @@ describe("read PDF with a line-range selector", () => { }); afterEach(() => { vi.restoreAllMocks(); - fs.rmSync(testDir, { recursive: true, force: true }); + removeSyncWithRetries(testDir); }); it("honours `:N-M` against the converted markdown body", async () => { diff --git a/packages/coding-agent/test/tools/root-path-alias.test.ts b/packages/coding-agent/test/tools/root-path-alias.test.ts index e40875a9b..1932b0aac 100644 --- a/packages/coding-agent/test/tools/root-path-alias.test.ts +++ b/packages/coding-agent/test/tools/root-path-alias.test.ts @@ -6,6 +6,7 @@ import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { ToolChoiceQueue } from "@oh-my-pi/pi-coding-agent/session/tool-choice-queue"; import { createTools, type ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; import { resolveToCwd } from "@oh-my-pi/pi-coding-agent/tools/path-utils"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; function createTestSession(cwd: string, overrides: Partial = {}): ToolSession { return { @@ -38,7 +39,7 @@ describe("tool path root alias", () => { }); afterEach(async () => { - await fs.rm(tempDir, { recursive: true, force: true }); + await removeWithRetries(tempDir); }); it("resolves a bare slash to the session cwd", () => { diff --git a/packages/coding-agent/test/tools/sqlite.test.ts b/packages/coding-agent/test/tools/sqlite.test.ts index eda7a0dd2..12f869f8a 100644 --- a/packages/coding-agent/test/tools/sqlite.test.ts +++ b/packages/coding-agent/test/tools/sqlite.test.ts @@ -3,6 +3,7 @@ import { afterAll, beforeAll, describe, expect, it } from "bun:test"; import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; import "@oh-my-pi/pi-coding-agent/tools/renderers"; import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { ReadTool } from "@oh-my-pi/pi-coding-agent/tools/read"; @@ -178,7 +179,7 @@ describe("SQLite tool support", () => { } else { Bun.env.PI_EDIT_VARIANT = originalEditVariant; } - await fs.rm(tmpDir, { recursive: true, force: true }); + await removeWithRetries(tmpDir); }); it("parses SQLite path candidates at the extension boundary", () => { diff --git a/packages/coding-agent/test/tools/tool-output-hyperlinks.test.ts b/packages/coding-agent/test/tools/tool-output-hyperlinks.test.ts index 47c35e50f..c1c409dfe 100644 --- a/packages/coding-agent/test/tools/tool-output-hyperlinks.test.ts +++ b/packages/coding-agent/test/tools/tool-output-hyperlinks.test.ts @@ -10,6 +10,7 @@ import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; import { astGrepToolRenderer } from "@oh-my-pi/pi-coding-agent/tools/ast-grep"; import { ReadTool, readToolRenderer } from "@oh-my-pi/pi-coding-agent/tools/read"; import { WriteTool, writeToolRenderer } from "@oh-my-pi/pi-coding-agent/tools/write"; +import { removeSyncWithRetries } from "@oh-my-pi/pi-utils"; import { grepToolRenderer } from "../../src/tools/grep"; // 1x1 PNG so the read tool takes its image branch. @@ -81,7 +82,7 @@ describe("tool output OSC 8 file:// hyperlinks", () => { expect(extractLinkUris(textRender)).toContain(url.pathToFileURL(path.resolve(textPath)).href); expect(extractLinkUris(imgRender)).toContain(url.pathToFileURL(path.resolve(imgPath)).href); } finally { - fs.rmSync(dir, { recursive: true, force: true }); + removeSyncWithRetries(dir); } }); @@ -104,7 +105,7 @@ describe("tool output OSC 8 file:// hyperlinks", () => { .join("\n"); expect(extractLinkUris(rendered)).toContain(url.pathToFileURL(path.resolve(filePath)).href); } finally { - fs.rmSync(dir, { recursive: true, force: true }); + removeSyncWithRetries(dir); } }); diff --git a/packages/coding-agent/test/tools/web-search-exa.test.ts b/packages/coding-agent/test/tools/web-search-exa.test.ts index 34065411c..6d491028d 100644 --- a/packages/coding-agent/test/tools/web-search-exa.test.ts +++ b/packages/coding-agent/test/tools/web-search-exa.test.ts @@ -13,6 +13,7 @@ import { searchExa, synthesizeAnswer, } from "@oh-my-pi/pi-coding-agent/web/search/providers/exa"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; async function withLocalAuthStorage(run: (authStorage: AuthStorage) => Promise): Promise { const dir = await fs.mkdtemp(path.join(os.tmpdir(), "web-search-exa-auth-")); @@ -21,7 +22,7 @@ async function withLocalAuthStorage(run: (authStorage: AuthStorage) => Promis return await run(authStorage); } finally { authStorage.close(); - await fs.rm(dir, { recursive: true, force: true }); + await removeWithRetries(dir); } } diff --git a/packages/coding-agent/test/tools/web-search-searxng.test.ts b/packages/coding-agent/test/tools/web-search-searxng.test.ts index abbaf84ce..8431a6f10 100644 --- a/packages/coding-agent/test/tools/web-search-searxng.test.ts +++ b/packages/coding-agent/test/tools/web-search-searxng.test.ts @@ -6,6 +6,7 @@ import type { FetchImpl } from "@oh-my-pi/pi-ai/types"; import { resetSettingsForTest, Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { searchSearXNG } from "@oh-my-pi/pi-coding-agent/web/search/providers/searxng"; import { SearchProviderError } from "@oh-my-pi/pi-coding-agent/web/search/types"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; describe("SearXNG web search provider", () => { afterEach(() => { @@ -90,7 +91,7 @@ describe("SearXNG web search provider", () => { `Basic ${Buffer.from("alice:s3cret", "utf-8").toString("base64")}`, ); } finally { - await fs.rm(agentDir, { recursive: true, force: true }); + await removeWithRetries(agentDir); } }); diff --git a/packages/coding-agent/test/update-cli.test.ts b/packages/coding-agent/test/update-cli.test.ts index ad58002de..6446e4cf6 100644 --- a/packages/coding-agent/test/update-cli.test.ts +++ b/packages/coding-agent/test/update-cli.test.ts @@ -12,6 +12,7 @@ import { resolveUpdateMethodForTest, sweepStaleBackups, } from "@oh-my-pi/pi-coding-agent/cli/update-cli"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; const tempDirs: string[] = []; @@ -22,7 +23,7 @@ async function makeTempDir(): Promise { } afterEach(async () => { - await Promise.all(tempDirs.splice(0).map(dir => fs.rm(dir, { recursive: true, force: true }))); + await Promise.all(tempDirs.splice(0).map(dir => removeWithRetries(dir))); }); describe("update-cli install target detection", () => { it("uses bun update when prioritized omp is inside bun global bin", () => { diff --git a/packages/coding-agent/test/utils/git-clone.test.ts b/packages/coding-agent/test/utils/git-clone.test.ts index 0e2b71326..7af615041 100644 --- a/packages/coding-agent/test/utils/git-clone.test.ts +++ b/packages/coding-agent/test/utils/git-clone.test.ts @@ -3,8 +3,8 @@ import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; import * as url from "node:url"; - import * as git from "@oh-my-pi/pi-coding-agent/utils/git"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; // Regression coverage for #1589: `git.clone({ sha })` used to hardcode // `--depth 1`, producing a shallow clone whose object store never contained @@ -56,7 +56,7 @@ describe("git.clone with options.sha", () => { }); afterAll(async () => { - await fs.rm(tmpRoot, { recursive: true, force: true }); + await removeWithRetries(tmpRoot); }); test("checks out a non-tip SHA (regression for #1589)", async () => { diff --git a/packages/coding-agent/test/utils/git-eisdir-fallback.test.ts b/packages/coding-agent/test/utils/git-eisdir-fallback.test.ts index 16d7f0de2..8eebbd1f4 100644 --- a/packages/coding-agent/test/utils/git-eisdir-fallback.test.ts +++ b/packages/coding-agent/test/utils/git-eisdir-fallback.test.ts @@ -2,6 +2,7 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; import { $ } from "bun"; import * as git from "../../src/utils/git"; @@ -41,7 +42,7 @@ describe("git reference directory fallback", () => { expect(packedRefs).toContain("refs/heads/pi-flash"); // Delete the loose ref file for refs/heads/pi-flash if git pack-refs didn't already delete it (it usually does). - await fs.rm(path.join(repoDir, ".git", "refs", "heads", "pi-flash"), { force: true }); + await removeWithRetries(path.join(repoDir, ".git", "refs", "heads", "pi-flash")); // Now, create refs/heads/pi-flash as a directory to simulate another branch like "pi-flash/feature" existing. // We can just create the directory and a file inside it, or just the directory. @@ -50,7 +51,7 @@ describe("git reference directory fallback", () => { }); afterAll(async () => { - await fs.rm(repoDir, { recursive: true, force: true }).catch(() => {}); + await removeWithRetries(repoDir).catch(() => {}); }); test("resolves branch that has directory conflict via resolveSync on head", () => { diff --git a/packages/coding-agent/test/utils/image-vision-fallback.test.ts b/packages/coding-agent/test/utils/image-vision-fallback.test.ts index fe8f67470..970b476c5 100644 --- a/packages/coding-agent/test/utils/image-vision-fallback.test.ts +++ b/packages/coding-agent/test/utils/image-vision-fallback.test.ts @@ -9,6 +9,7 @@ import { type DescribeAttachedImagesDeps, describeAttachedImagesForTextModel, } from "@oh-my-pi/pi-coding-agent/utils/image-vision-fallback"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; // 1x1 transparent PNG. const TINY_PNG_BASE64 = @@ -82,7 +83,7 @@ describe("describeAttachedImagesForTextModel", () => { }); afterEach(async () => { - await fs.rm(testDir, { recursive: true, force: true }); + await removeWithRetries(testDir); }); it("saves the image under local:// and injects a vision description block", async () => { diff --git a/packages/coding-agent/test/utils/jj.test.ts b/packages/coding-agent/test/utils/jj.test.ts index 1b0ff868a..230e82b8f 100644 --- a/packages/coding-agent/test/utils/jj.test.ts +++ b/packages/coding-agent/test/utils/jj.test.ts @@ -3,6 +3,7 @@ import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; import * as jj from "@oh-my-pi/pi-coding-agent/utils/jj"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; describe("jj workspace detection", () => { let tmpDir: string | undefined; @@ -10,7 +11,7 @@ describe("jj workspace detection", () => { afterEach(async () => { jj.repo.clearRootCache(); if (tmpDir) { - await fs.rm(tmpDir, { recursive: true, force: true }); + await removeWithRetries(tmpDir); tmpDir = undefined; } }); @@ -37,7 +38,7 @@ describe("jj workspace detection", () => { await fs.mkdir(nested, { recursive: true }); expect(await jj.repo.root(nested)).toBe(dir); - await fs.rm(path.join(dir, ".jj"), { recursive: true, force: true }); + await removeWithRetries(path.join(dir, ".jj")); expect(await jj.repo.root(nested)).toBe(dir); expect(await jj.repo.root(path.join(dir, "src"))).toBeNull(); @@ -83,7 +84,7 @@ describe("isPureJjRepo", () => { afterEach(async () => { jj.repo.clearRootCache(); - await Promise.all(tempDirs.splice(0).map(dir => fs.rm(dir, { recursive: true, force: true }))); + await Promise.all(tempDirs.splice(0).map(dir => removeWithRetries(dir))); }); async function createTempDir(prefix: string): Promise { diff --git a/packages/coding-agent/test/workspace-tree.test.ts b/packages/coding-agent/test/workspace-tree.test.ts index 72cd0767d..20c496b7f 100644 --- a/packages/coding-agent/test/workspace-tree.test.ts +++ b/packages/coding-agent/test/workspace-tree.test.ts @@ -3,6 +3,7 @@ import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; import { buildDirectoryTree, buildWorkspaceTree } from "@oh-my-pi/pi-coding-agent/workspace-tree"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; const tempDirs: string[] = []; @@ -30,7 +31,7 @@ function lineIndex(rendered: string, needle: string): number { describe("buildWorkspaceTree", () => { afterEach(async () => { - await Promise.all(tempDirs.splice(0).map(dir => fs.rm(dir, { recursive: true, force: true }))); + await Promise.all(tempDirs.splice(0).map(dir => removeWithRetries(dir))); }); it("sorts files and directories together by modification time", async () => { diff --git a/packages/coding-agent/test/write-acp-fs.test.ts b/packages/coding-agent/test/write-acp-fs.test.ts index 3b076baf3..54c90fe74 100644 --- a/packages/coding-agent/test/write-acp-fs.test.ts +++ b/packages/coding-agent/test/write-acp-fs.test.ts @@ -8,6 +8,7 @@ import type { PlanModeState } from "@oh-my-pi/pi-coding-agent/plan-mode/state"; import type { ClientBridge } from "@oh-my-pi/pi-coding-agent/session/client-bridge"; import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; import { WriteTool } from "@oh-my-pi/pi-coding-agent/tools/write"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; const FILE_CONTENT = "bridge write content\n"; @@ -43,7 +44,7 @@ describe("write tool ACP fs routing", () => { }); afterEach(async () => { - await fs.rm(tmpDir, { recursive: true, force: true }); + await removeWithRetries(tmpDir); }); it("routes plain text writes through the bridge and does not call Bun.write", async () => { diff --git a/packages/coding-agent/test/write-hashline-header.test.ts b/packages/coding-agent/test/write-hashline-header.test.ts index a6a11f7b4..aae4b98ca 100644 --- a/packages/coding-agent/test/write-hashline-header.test.ts +++ b/packages/coding-agent/test/write-hashline-header.test.ts @@ -9,6 +9,7 @@ import { HashlineFilesystem } from "@oh-my-pi/pi-coding-agent/edit/hashline/file import { writethroughNoop } from "@oh-my-pi/pi-coding-agent/lsp"; import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; import { WriteTool } from "@oh-my-pi/pi-coding-agent/tools/write"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; function createSession(cwd: string): ToolSession { return { @@ -44,7 +45,7 @@ describe("write tool hashline header", () => { }); afterEach(async () => { - await fs.rm(tmpDir, { recursive: true, force: true }); + await removeWithRetries(tmpDir); }); it("inserts a fresh [path#TAG] header that maps to the written content", async () => { diff --git a/packages/coding-agent/test/write-shebang-chmod.test.ts b/packages/coding-agent/test/write-shebang-chmod.test.ts index a7e6cd531..9a339f3e5 100644 --- a/packages/coding-agent/test/write-shebang-chmod.test.ts +++ b/packages/coding-agent/test/write-shebang-chmod.test.ts @@ -5,6 +5,7 @@ import * as path from "node:path"; import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; import { WriteTool } from "@oh-my-pi/pi-coding-agent/tools/write"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; function createSession(cwd: string): ToolSession { return { @@ -42,7 +43,7 @@ describe("write tool shebang chmod", () => { }); afterEach(async () => { - await fs.rm(tmpDir, { recursive: true, force: true }); + await removeWithRetries(tmpDir); }); it("marks files starting with #! as executable and flags the result", async () => {