diff --git a/packages/mcp-server/src/coerce-string-arrays.test.ts b/packages/mcp-server/src/coerce-string-arrays.test.ts index 8a64d2b11..ab4b8b74d 100644 --- a/packages/mcp-server/src/coerce-string-arrays.test.ts +++ b/packages/mcp-server/src/coerce-string-arrays.test.ts @@ -1,4 +1,4 @@ -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import assert from "node:assert/strict"; import { z } from "zod"; diff --git a/packages/mcp-server/src/import-candidates.test.ts b/packages/mcp-server/src/import-candidates.test.ts index ef581e8bd..e4978aa7c 100644 --- a/packages/mcp-server/src/import-candidates.test.ts +++ b/packages/mcp-server/src/import-candidates.test.ts @@ -1,5 +1,5 @@ // SF — Regression tests for importLocalModule candidate resolution (#3954) -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import assert from "node:assert/strict"; import { _buildImportCandidates } from "./workflow-tools.js"; diff --git a/packages/mcp-server/src/tool-credentials.test.ts b/packages/mcp-server/src/tool-credentials.test.ts index 3c8777dc4..b23aeb59d 100644 --- a/packages/mcp-server/src/tool-credentials.test.ts +++ b/packages/mcp-server/src/tool-credentials.test.ts @@ -1,4 +1,4 @@ -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import assert from "node:assert/strict"; import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; diff --git a/packages/mcp-server/src/workflow-tools.test.ts b/packages/mcp-server/src/workflow-tools.test.ts index 04b839949..2697492a3 100644 --- a/packages/mcp-server/src/workflow-tools.test.ts +++ b/packages/mcp-server/src/workflow-tools.test.ts @@ -1,4 +1,4 @@ -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import assert from "node:assert/strict"; import { mkdirSync, rmSync, writeFileSync, existsSync } from "node:fs"; import { join } from "node:path"; diff --git a/packages/native/src/__tests__/clipboard.test.mjs b/packages/native/src/__tests__/clipboard.test.mjs index 1816dd93e..5bb9a76d7 100644 --- a/packages/native/src/__tests__/clipboard.test.mjs +++ b/packages/native/src/__tests__/clipboard.test.mjs @@ -1,4 +1,4 @@ -import { test, describe } from "node:test"; +import { describe } from 'vitest'; import assert from "node:assert/strict"; import { createRequire } from "node:module"; import * as path from "node:path"; diff --git a/packages/native/src/__tests__/diff.test.mjs b/packages/native/src/__tests__/diff.test.mjs index 2a720e19c..396acd787 100644 --- a/packages/native/src/__tests__/diff.test.mjs +++ b/packages/native/src/__tests__/diff.test.mjs @@ -1,4 +1,4 @@ -import { test, describe } from "node:test"; +import { describe } from 'vitest'; import assert from "node:assert/strict"; import { createRequire } from "node:module"; import * as path from "node:path"; diff --git a/packages/native/src/__tests__/fd.test.mjs b/packages/native/src/__tests__/fd.test.mjs index e0bce2ce3..80f309567 100644 --- a/packages/native/src/__tests__/fd.test.mjs +++ b/packages/native/src/__tests__/fd.test.mjs @@ -1,4 +1,4 @@ -import { test, describe } from "node:test"; +import { describe } from 'vitest'; import assert from "node:assert/strict"; import { createRequire } from "node:module"; import * as path from "node:path"; diff --git a/packages/native/src/__tests__/glob.test.mjs b/packages/native/src/__tests__/glob.test.mjs index 0a205e500..94e847f9e 100644 --- a/packages/native/src/__tests__/glob.test.mjs +++ b/packages/native/src/__tests__/glob.test.mjs @@ -1,4 +1,4 @@ -import { test, describe } from "node:test"; +import { describe } from 'vitest'; import assert from "node:assert/strict"; import { createRequire } from "node:module"; import * as path from "node:path"; diff --git a/packages/native/src/__tests__/grep.test.mjs b/packages/native/src/__tests__/grep.test.mjs index 8d1abd68f..c6ebb787f 100644 --- a/packages/native/src/__tests__/grep.test.mjs +++ b/packages/native/src/__tests__/grep.test.mjs @@ -1,4 +1,4 @@ -import { test, describe } from "node:test"; +import { describe } from 'vitest'; import assert from "node:assert/strict"; import { createRequire } from "node:module"; import * as path from "node:path"; diff --git a/packages/native/src/__tests__/highlight.test.mjs b/packages/native/src/__tests__/highlight.test.mjs index 41c3c3dc2..bec51b51c 100644 --- a/packages/native/src/__tests__/highlight.test.mjs +++ b/packages/native/src/__tests__/highlight.test.mjs @@ -1,4 +1,4 @@ -import { test, describe } from "node:test"; +import { describe } from 'vitest'; import assert from "node:assert/strict"; import { createRequire } from "node:module"; import * as path from "node:path"; diff --git a/packages/native/src/__tests__/html.test.mjs b/packages/native/src/__tests__/html.test.mjs index 2be998cb4..8f4e42179 100644 --- a/packages/native/src/__tests__/html.test.mjs +++ b/packages/native/src/__tests__/html.test.mjs @@ -1,4 +1,4 @@ -import { test, describe } from "node:test"; +import { describe } from 'vitest'; import assert from "node:assert/strict"; import { createRequire } from "node:module"; import * as path from "node:path"; diff --git a/packages/native/src/__tests__/image.test.mjs b/packages/native/src/__tests__/image.test.mjs index 6d67c80b3..1a915972f 100644 --- a/packages/native/src/__tests__/image.test.mjs +++ b/packages/native/src/__tests__/image.test.mjs @@ -1,4 +1,4 @@ -import { test, describe } from "node:test"; +import { describe } from 'vitest'; import assert from "node:assert/strict"; import { createRequire } from "node:module"; import * as path from "node:path"; diff --git a/packages/native/src/__tests__/json-parse.test.mjs b/packages/native/src/__tests__/json-parse.test.mjs index 0dfaa3cb6..79cf3e096 100644 --- a/packages/native/src/__tests__/json-parse.test.mjs +++ b/packages/native/src/__tests__/json-parse.test.mjs @@ -1,4 +1,4 @@ -import { test, describe } from "node:test"; +import { describe } from 'vitest'; import assert from "node:assert/strict"; import { createRequire } from "node:module"; import * as path from "node:path"; diff --git a/packages/native/src/__tests__/module-compat.test.mjs b/packages/native/src/__tests__/module-compat.test.mjs index b55491024..cc03b39dc 100644 --- a/packages/native/src/__tests__/module-compat.test.mjs +++ b/packages/native/src/__tests__/module-compat.test.mjs @@ -7,7 +7,7 @@ * declared "type": "module" and strict ESM resolution was enforced. */ -import { test, describe } from "node:test"; +import { describe } from 'vitest'; import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import * as path from "node:path"; diff --git a/packages/native/src/__tests__/ps.test.mjs b/packages/native/src/__tests__/ps.test.mjs index 7ebf5e004..d9599210d 100644 --- a/packages/native/src/__tests__/ps.test.mjs +++ b/packages/native/src/__tests__/ps.test.mjs @@ -1,4 +1,4 @@ -import { test, describe } from "node:test"; +import { describe } from 'vitest'; import assert from "node:assert/strict"; import { createRequire } from "node:module"; import * as path from "node:path"; diff --git a/packages/native/src/__tests__/stream-process.test.mjs b/packages/native/src/__tests__/stream-process.test.mjs index 224f0bffa..bc77628e8 100644 --- a/packages/native/src/__tests__/stream-process.test.mjs +++ b/packages/native/src/__tests__/stream-process.test.mjs @@ -1,4 +1,4 @@ -import { test, describe } from "node:test"; +import { describe } from 'vitest'; import assert from "node:assert/strict"; import { processStreamChunk } from "../stream-process/index.ts"; diff --git a/packages/native/src/__tests__/text.test.mjs b/packages/native/src/__tests__/text.test.mjs index 78276594d..092158e94 100644 --- a/packages/native/src/__tests__/text.test.mjs +++ b/packages/native/src/__tests__/text.test.mjs @@ -1,4 +1,4 @@ -import { test, describe } from "node:test"; +import { describe } from 'vitest'; import assert from "node:assert/strict"; import { createRequire } from "node:module"; import * as path from "node:path"; diff --git a/packages/native/src/__tests__/truncate.test.mjs b/packages/native/src/__tests__/truncate.test.mjs index e33c16d71..c1bac4028 100644 --- a/packages/native/src/__tests__/truncate.test.mjs +++ b/packages/native/src/__tests__/truncate.test.mjs @@ -1,4 +1,4 @@ -import { test, describe } from "node:test"; +import { describe } from 'vitest'; import assert from "node:assert/strict"; import { createRequire } from "node:module"; import * as path from "node:path"; diff --git a/packages/native/src/__tests__/ttsr.test.mjs b/packages/native/src/__tests__/ttsr.test.mjs index c2e122744..f5981f756 100644 --- a/packages/native/src/__tests__/ttsr.test.mjs +++ b/packages/native/src/__tests__/ttsr.test.mjs @@ -1,4 +1,4 @@ -import { test, describe } from "node:test"; +import { describe } from 'vitest'; import assert from "node:assert/strict"; import { createRequire } from "node:module"; import * as path from "node:path"; diff --git a/packages/native/src/__tests__/xxhash.test.mjs b/packages/native/src/__tests__/xxhash.test.mjs index 59df383c1..669a595b5 100644 --- a/packages/native/src/__tests__/xxhash.test.mjs +++ b/packages/native/src/__tests__/xxhash.test.mjs @@ -1,4 +1,4 @@ -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import assert from "node:assert/strict"; import { xxHash32, xxHash32Fallback } from "@singularity-forge/native/xxhash"; diff --git a/packages/pi-agent-core/src/agent-loop.test.ts b/packages/pi-agent-core/src/agent-loop.test.ts index 5ddb1d637..9ead3d2a0 100644 --- a/packages/pi-agent-core/src/agent-loop.test.ts +++ b/packages/pi-agent-core/src/agent-loop.test.ts @@ -1,7 +1,7 @@ // agent-loop tests // Covers: pauseTurn handling (#2869), schema overload retry cap (#2783) -import { describe, it, mock } from "node:test"; +import { describe, it } from 'vitest'; import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { join, dirname } from "node:path"; diff --git a/packages/pi-agent-core/src/agent.test.ts b/packages/pi-agent-core/src/agent.test.ts index 9eba4db5b..cd6bfe50a 100644 --- a/packages/pi-agent-core/src/agent.test.ts +++ b/packages/pi-agent-core/src/agent.test.ts @@ -3,7 +3,7 @@ // and that the footer reads activeInferenceModel instead of state.model. // Regression test for https://github.com/singularity-forge/sf-run/issues/1844 Bug 2 -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { join, dirname } from "node:path"; diff --git a/packages/pi-ai/src/env-api-keys.test.ts b/packages/pi-ai/src/env-api-keys.test.ts index 946d92e3e..321933c93 100644 --- a/packages/pi-ai/src/env-api-keys.test.ts +++ b/packages/pi-ai/src/env-api-keys.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import { getEnvApiKey } from "./env-api-keys.js"; describe("getEnvApiKey", () => { diff --git a/packages/pi-ai/src/models.generated.test.ts b/packages/pi-ai/src/models.generated.test.ts index c70ed99bd..bcb7eaa68 100644 --- a/packages/pi-ai/src/models.generated.test.ts +++ b/packages/pi-ai/src/models.generated.test.ts @@ -1,4 +1,4 @@ -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import assert from "node:assert/strict"; import { MODELS } from "./models.generated.js"; import { getModel, getModels, getProviders } from "./models.js"; diff --git a/packages/pi-ai/src/models.test.ts b/packages/pi-ai/src/models.test.ts index bd38c3d66..86d59a97a 100644 --- a/packages/pi-ai/src/models.test.ts +++ b/packages/pi-ai/src/models.test.ts @@ -1,4 +1,4 @@ -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import assert from "node:assert/strict"; import { getProviders, getModels, getModel, supportsXhigh, applyCapabilityPatches } from "./models.js"; import type { Api, Model } from "./types.js"; diff --git a/packages/pi-ai/src/providers/amazon-bedrock.test.ts b/packages/pi-ai/src/providers/amazon-bedrock.test.ts index d00a49cdd..92368e23d 100644 --- a/packages/pi-ai/src/providers/amazon-bedrock.test.ts +++ b/packages/pi-ai/src/providers/amazon-bedrock.test.ts @@ -7,7 +7,7 @@ * Related: #4392 (opus-4-7 adaptive thinking not recognised on Bedrock) * #4352 (pre-existing: only opus-4-6 / sonnet-4-6 whitelisted) */ -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import assert from "node:assert/strict"; import { diff --git a/packages/pi-ai/src/providers/anthropic-auth.test.ts b/packages/pi-ai/src/providers/anthropic-auth.test.ts index 917039321..81627df14 100644 --- a/packages/pi-ai/src/providers/anthropic-auth.test.ts +++ b/packages/pi-ai/src/providers/anthropic-auth.test.ts @@ -1,4 +1,4 @@ -import test from "node:test"; +import { test } from 'vitest'; import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { dirname, join } from "node:path"; diff --git a/packages/pi-ai/src/providers/anthropic-shared.test.ts b/packages/pi-ai/src/providers/anthropic-shared.test.ts index 6e08bc52e..50fef5595 100644 --- a/packages/pi-ai/src/providers/anthropic-shared.test.ts +++ b/packages/pi-ai/src/providers/anthropic-shared.test.ts @@ -1,4 +1,4 @@ -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import assert from "node:assert/strict"; import { convertTools, mapStopReason } from "./anthropic-shared.js"; diff --git a/packages/pi-ai/src/providers/google-shared.test.ts b/packages/pi-ai/src/providers/google-shared.test.ts index 4468ac231..9da0ab662 100644 --- a/packages/pi-ai/src/providers/google-shared.test.ts +++ b/packages/pi-ai/src/providers/google-shared.test.ts @@ -1,4 +1,4 @@ -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import assert from "node:assert/strict"; import { sanitizeSchemaForGoogle } from "./google-shared.js"; diff --git a/packages/pi-ai/src/providers/provider-capabilities.test.ts b/packages/pi-ai/src/providers/provider-capabilities.test.ts index 73db8e42a..5d3e8d112 100644 --- a/packages/pi-ai/src/providers/provider-capabilities.test.ts +++ b/packages/pi-ai/src/providers/provider-capabilities.test.ts @@ -1,5 +1,5 @@ // SF — Provider Capabilities Registry Tests (ADR-005 Phase 1) -import { describe, test } from "node:test"; +import { describe } from 'vitest'; import assert from "node:assert/strict"; import { diff --git a/packages/pi-ai/src/providers/simple-options.test.ts b/packages/pi-ai/src/providers/simple-options.test.ts index 93a15749a..dc885b94a 100644 --- a/packages/pi-ai/src/providers/simple-options.test.ts +++ b/packages/pi-ai/src/providers/simple-options.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import type { Model } from "../types.js"; import { isAutoReasoning, resolveReasoningLevel } from "./simple-options.js"; diff --git a/packages/pi-ai/src/providers/transform-messages-report.test.ts b/packages/pi-ai/src/providers/transform-messages-report.test.ts index 4c31777c0..dbef910e7 100644 --- a/packages/pi-ai/src/providers/transform-messages-report.test.ts +++ b/packages/pi-ai/src/providers/transform-messages-report.test.ts @@ -1,5 +1,5 @@ // SF — ProviderSwitchReport Tests (ADR-005 Phase 3) -import { describe, test } from "node:test"; +import { describe } from 'vitest'; import assert from "node:assert/strict"; import { transformMessages, createEmptyReport, hasTransformations } from "./transform-messages.js"; diff --git a/packages/pi-ai/src/utils/event-stream.test.ts b/packages/pi-ai/src/utils/event-stream.test.ts index d5ae103d7..6d202ecb2 100644 --- a/packages/pi-ai/src/utils/event-stream.test.ts +++ b/packages/pi-ai/src/utils/event-stream.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import { parseAnthropicSSE } from "./event-stream.js"; function createMockResponse(chunks: string[]): Response { diff --git a/packages/pi-ai/src/utils/oauth/github-copilot.test.ts b/packages/pi-ai/src/utils/oauth/github-copilot.test.ts index fabe2c09f..ad6b32645 100644 --- a/packages/pi-ai/src/utils/oauth/github-copilot.test.ts +++ b/packages/pi-ai/src/utils/oauth/github-copilot.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import test from "node:test"; +import { test } from 'vitest'; import type { Api, Model } from "../../types.js"; import type { OAuthCredentials } from "./index.js"; diff --git a/packages/pi-ai/src/utils/tests/json-parse.test.ts b/packages/pi-ai/src/utils/tests/json-parse.test.ts index d8b8e8740..298d7eb70 100644 --- a/packages/pi-ai/src/utils/tests/json-parse.test.ts +++ b/packages/pi-ai/src/utils/tests/json-parse.test.ts @@ -1,4 +1,4 @@ -import { describe, test } from "node:test"; +import { describe } from 'vitest'; import assert from "node:assert/strict"; import { parseStreamingJson } from "../json-parse.js"; diff --git a/packages/pi-ai/src/utils/tests/overflow.test.ts b/packages/pi-ai/src/utils/tests/overflow.test.ts index 068196bcb..3dee7fe87 100644 --- a/packages/pi-ai/src/utils/tests/overflow.test.ts +++ b/packages/pi-ai/src/utils/tests/overflow.test.ts @@ -1,4 +1,4 @@ -import { describe, test } from "node:test"; +import { describe } from 'vitest'; import assert from "node:assert/strict"; import { isContextOverflow } from "../overflow.js"; diff --git a/packages/pi-ai/src/utils/tests/repair-tool-json.test.ts b/packages/pi-ai/src/utils/tests/repair-tool-json.test.ts index 4249ca415..6f1917017 100644 --- a/packages/pi-ai/src/utils/tests/repair-tool-json.test.ts +++ b/packages/pi-ai/src/utils/tests/repair-tool-json.test.ts @@ -1,4 +1,4 @@ -import { describe, test } from "node:test"; +import { describe } from 'vitest'; import assert from "node:assert/strict"; import { repairToolJson, diff --git a/packages/pi-coding-agent/src/cli/args.test.ts b/packages/pi-coding-agent/src/cli/args.test.ts index d7a849627..9e7dec089 100644 --- a/packages/pi-coding-agent/src/cli/args.test.ts +++ b/packages/pi-coding-agent/src/cli/args.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import { parseArgs } from "./args.js"; describe("parseArgs", () => { diff --git a/packages/pi-coding-agent/src/cli/list-models.test.ts b/packages/pi-coding-agent/src/cli/list-models.test.ts index 64e16fcf5..9202a6fe1 100644 --- a/packages/pi-coding-agent/src/cli/list-models.test.ts +++ b/packages/pi-coding-agent/src/cli/list-models.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { afterEach, beforeEach, describe, it } from "node:test"; +import { afterEach, beforeEach, describe, it } from 'vitest'; import type { Model } from "@singularity-forge/pi-ai"; import type { ModelRegistry } from "../core/model-registry.js"; import { listModels } from "./list-models.js"; diff --git a/packages/pi-coding-agent/src/core/agent-session-custom-message-queue.test.ts b/packages/pi-coding-agent/src/core/agent-session-custom-message-queue.test.ts index 9195f89b7..2e1b3f7bd 100644 --- a/packages/pi-coding-agent/src/core/agent-session-custom-message-queue.test.ts +++ b/packages/pi-coding-agent/src/core/agent-session-custom-message-queue.test.ts @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import { mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { afterEach, beforeEach, describe, it } from "node:test"; +import { afterEach, beforeEach, describe, it } from 'vitest'; import { Agent, type AgentMessage } from "@singularity-forge/pi-agent-core"; import { AgentSession } from "./agent-session.js"; diff --git a/packages/pi-coding-agent/src/core/agent-session-model-switch.test.ts b/packages/pi-coding-agent/src/core/agent-session-model-switch.test.ts index f86dac6ca..6b26d81c3 100644 --- a/packages/pi-coding-agent/src/core/agent-session-model-switch.test.ts +++ b/packages/pi-coding-agent/src/core/agent-session-model-switch.test.ts @@ -1,4 +1,4 @@ -import test from "node:test"; +import { test } from 'vitest'; import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { join } from "node:path"; diff --git a/packages/pi-coding-agent/src/core/agent-session-print-mode-persist.test.ts b/packages/pi-coding-agent/src/core/agent-session-print-mode-persist.test.ts index 978cf5c60..a14384c17 100644 --- a/packages/pi-coding-agent/src/core/agent-session-print-mode-persist.test.ts +++ b/packages/pi-coding-agent/src/core/agent-session-print-mode-persist.test.ts @@ -1,4 +1,4 @@ -import test from "node:test"; +import { test } from 'vitest'; import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { join } from "node:path"; diff --git a/packages/pi-coding-agent/src/core/agent-session-renderable-tools.test.ts b/packages/pi-coding-agent/src/core/agent-session-renderable-tools.test.ts index 1106f7b98..66bd4f395 100644 --- a/packages/pi-coding-agent/src/core/agent-session-renderable-tools.test.ts +++ b/packages/pi-coding-agent/src/core/agent-session-renderable-tools.test.ts @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import { mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { afterEach, beforeEach, describe, it } from "node:test"; +import { afterEach, beforeEach, describe, it } from 'vitest'; import { Agent } from "@singularity-forge/pi-agent-core"; import { Type } from "@sinclair/typebox"; diff --git a/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts b/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts index 1e5a8ae49..10048d814 100644 --- a/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts +++ b/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts @@ -1,7 +1,7 @@ // SF — Regression tests for #3616: tool list persistence across newSession() calls // Copyright (c) 2026 Jeremy McSpadden -import test, { describe } from "node:test"; +import { test, describe } from 'vitest'; import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { join } from "node:path"; diff --git a/packages/pi-coding-agent/src/core/auth-storage.test.ts b/packages/pi-coding-agent/src/core/auth-storage.test.ts index fac250e5d..b44bac70c 100644 --- a/packages/pi-coding-agent/src/core/auth-storage.test.ts +++ b/packages/pi-coding-agent/src/core/auth-storage.test.ts @@ -1,4 +1,4 @@ -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import assert from "node:assert/strict"; import { AuthStorage } from "./auth-storage.js"; diff --git a/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts b/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts index f61f27c9b..4473a51bc 100644 --- a/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +++ b/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { test } from "node:test"; +import { } from 'vitest'; import { handleAgentEvent } from "../modes/interactive/controllers/chat-controller.js"; diff --git a/packages/pi-coding-agent/src/core/compaction-utils.test.ts b/packages/pi-coding-agent/src/core/compaction-utils.test.ts index 0cbe18015..d88aa04dc 100644 --- a/packages/pi-coding-agent/src/core/compaction-utils.test.ts +++ b/packages/pi-coding-agent/src/core/compaction-utils.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import test from "node:test"; +import { test } from 'vitest'; import type { Message } from "@singularity-forge/pi-ai"; diff --git a/packages/pi-coding-agent/src/core/compaction/compaction.test.ts b/packages/pi-coding-agent/src/core/compaction/compaction.test.ts index 99198aff3..5e41bf631 100644 --- a/packages/pi-coding-agent/src/core/compaction/compaction.test.ts +++ b/packages/pi-coding-agent/src/core/compaction/compaction.test.ts @@ -4,7 +4,7 @@ */ import assert from "node:assert/strict"; -import { describe, it, mock } from "node:test"; +import { vi, describe, it } from 'vitest'; import type { AgentMessage } from "@singularity-forge/pi-agent-core"; import type { Model, AssistantMessage } from "@singularity-forge/pi-ai"; @@ -126,7 +126,7 @@ describe("generateSummary — chunked fallback (#2932)", () => { // Track calls const calls: string[] = []; - const mockComplete = mock.fn(async (_model: any, context: any, _options: any) => { + const mockComplete = vi.fn(async (_model: any, context: any, _options: any) => { const userMsg = context.messages?.[0]; const text = typeof userMsg?.content === "string" @@ -184,7 +184,7 @@ describe("generateSummary — chunked fallback (#2932)", () => { `Test setup: ${totalTokens} tokens should fit in ${model.contextWindow} context window`, ); - const mockComplete = mock.fn(async () => makeFakeResponse("Single pass summary")); + const mockComplete = vi.fn(async () => makeFakeResponse("Single pass summary")); await generateSummary(messages, model, reserveTokens, undefined, undefined, undefined, undefined, mockComplete); @@ -206,7 +206,7 @@ describe("generateSummary — chunked fallback (#2932)", () => { const previousSummary = "Previous session summary content"; const prompts: string[] = []; - const mockComplete = mock.fn(async (_model: any, context: any) => { + const mockComplete = vi.fn(async (_model: any, context: any) => { const userMsg = context.messages?.[0]; const text = typeof userMsg?.content === "string" diff --git a/packages/pi-coding-agent/src/core/contextual-tips.test.ts b/packages/pi-coding-agent/src/core/contextual-tips.test.ts index 29341e659..fff43165c 100644 --- a/packages/pi-coding-agent/src/core/contextual-tips.test.ts +++ b/packages/pi-coding-agent/src/core/contextual-tips.test.ts @@ -1,4 +1,4 @@ -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import assert from "node:assert/strict"; import { ContextualTips } from "./contextual-tips.js"; diff --git a/packages/pi-coding-agent/src/core/discovery-cache.test.ts b/packages/pi-coding-agent/src/core/discovery-cache.test.ts index e060fa5a8..ea96fc8eb 100644 --- a/packages/pi-coding-agent/src/core/discovery-cache.test.ts +++ b/packages/pi-coding-agent/src/core/discovery-cache.test.ts @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import { mkdirSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { afterEach, beforeEach, describe, it } from "node:test"; +import { afterEach, beforeEach, describe, it } from 'vitest'; import { ModelDiscoveryCache } from "./discovery-cache.js"; let testDir: string; diff --git a/packages/pi-coding-agent/src/core/extensions/extension-manifest.test.ts b/packages/pi-coding-agent/src/core/extensions/extension-manifest.test.ts index efe3ba388..3c0ab7595 100644 --- a/packages/pi-coding-agent/src/core/extensions/extension-manifest.test.ts +++ b/packages/pi-coding-agent/src/core/extensions/extension-manifest.test.ts @@ -1,7 +1,7 @@ // SF — Extension Manifest Tests // Copyright (c) 2026 Jeremy McSpadden -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import assert from "node:assert/strict"; import { mkdtempSync, mkdirSync, writeFileSync } from "node:fs"; import { join } from "node:path"; diff --git a/packages/pi-coding-agent/src/core/extensions/extension-sort.test.ts b/packages/pi-coding-agent/src/core/extensions/extension-sort.test.ts index f58413300..49c361604 100644 --- a/packages/pi-coding-agent/src/core/extensions/extension-sort.test.ts +++ b/packages/pi-coding-agent/src/core/extensions/extension-sort.test.ts @@ -1,7 +1,7 @@ // SF — Extension Sort Tests // Copyright (c) 2026 Jeremy McSpadden -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import assert from "node:assert/strict"; import { mkdtempSync, mkdirSync, writeFileSync } from "node:fs"; import { join } from "node:path"; diff --git a/packages/pi-coding-agent/src/core/extensions/loader.test.ts b/packages/pi-coding-agent/src/core/extensions/loader.test.ts index da547e525..67189d0b6 100644 --- a/packages/pi-coding-agent/src/core/extensions/loader.test.ts +++ b/packages/pi-coding-agent/src/core/extensions/loader.test.ts @@ -1,4 +1,4 @@ -import { describe, it, beforeEach, afterEach } from "node:test"; +import { describe, it, beforeEach, afterEach } from 'vitest'; import assert from "node:assert/strict"; import * as fs from "node:fs"; import * as os from "node:os"; diff --git a/packages/pi-coding-agent/src/core/extensions/provider-registration.test.ts b/packages/pi-coding-agent/src/core/extensions/provider-registration.test.ts index d2376b0f5..4feef0ea0 100644 --- a/packages/pi-coding-agent/src/core/extensions/provider-registration.test.ts +++ b/packages/pi-coding-agent/src/core/extensions/provider-registration.test.ts @@ -1,7 +1,7 @@ // sf — Regression test: pendingProviderRegistrations must be flushed exactly once (#3576) // Copyright (c) 2026 Jeremy McSpadden -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import assert from "node:assert/strict"; /** diff --git a/packages/pi-coding-agent/src/core/extensions/runner.test.ts b/packages/pi-coding-agent/src/core/extensions/runner.test.ts index 8a5dcca24..9f2d225aa 100644 --- a/packages/pi-coding-agent/src/core/extensions/runner.test.ts +++ b/packages/pi-coding-agent/src/core/extensions/runner.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { describe, it, mock } from "node:test"; +import { describe, it } from 'vitest'; import { ExtensionRunner } from "./runner.js"; import type { Extension, ExtensionRuntime, ToolCallEvent } from "./index.js"; import { SessionManager } from "../session-manager.js"; diff --git a/packages/pi-coding-agent/src/core/fallback-resolver.test.ts b/packages/pi-coding-agent/src/core/fallback-resolver.test.ts index 3a9f9b131..11f705272 100644 --- a/packages/pi-coding-agent/src/core/fallback-resolver.test.ts +++ b/packages/pi-coding-agent/src/core/fallback-resolver.test.ts @@ -1,7 +1,7 @@ // SF Provider Fallback Resolver Tests // Copyright (c) 2026 Jeremy McSpadden -import { describe, it, beforeEach, mock } from "node:test"; +import { vi, describe, it, beforeEach } from 'vitest'; import assert from "node:assert/strict"; import { FallbackResolver } from "./fallback-resolver.js"; import type { Api, Model } from "@singularity-forge/pi-ai"; @@ -49,7 +49,7 @@ function createResolver(overrides?: { } as unknown as SettingsManager; const authStorage = { - markProviderExhausted: mock.fn(), + markProviderExhausted: vi.fn(), isProviderAvailable: overrides?.isProviderAvailable ?? (() => true), hasAuth: overrides?.hasAuth ?? (() => true), } as unknown as AuthStorage; diff --git a/packages/pi-coding-agent/src/core/fs-utils.test.ts b/packages/pi-coding-agent/src/core/fs-utils.test.ts index 6c20beba1..ac34ef743 100644 --- a/packages/pi-coding-agent/src/core/fs-utils.test.ts +++ b/packages/pi-coding-agent/src/core/fs-utils.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { describe, it, afterEach } from "node:test"; +import { describe, it, afterEach } from 'vitest'; import { mkdtempSync, readFileSync, rmSync, existsSync } from "node:fs"; import { join } from "node:path"; import { tmpdir } from "node:os"; diff --git a/packages/pi-coding-agent/src/core/image-overflow-recovery.test.ts b/packages/pi-coding-agent/src/core/image-overflow-recovery.test.ts index db66ae411..706ff6037 100644 --- a/packages/pi-coding-agent/src/core/image-overflow-recovery.test.ts +++ b/packages/pi-coding-agent/src/core/image-overflow-recovery.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import { isImageDimensionError, MANY_IMAGE_MAX_DIMENSION, diff --git a/packages/pi-coding-agent/src/core/keybindings-followup.test.ts b/packages/pi-coding-agent/src/core/keybindings-followup.test.ts index fd97f52f6..ed54261bf 100644 --- a/packages/pi-coding-agent/src/core/keybindings-followup.test.ts +++ b/packages/pi-coding-agent/src/core/keybindings-followup.test.ts @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { join } from "node:path"; -import test from "node:test"; +import { test } from 'vitest'; const source = readFileSync(join(process.cwd(), "packages/pi-coding-agent/src/core/keybindings.ts"), "utf-8"); diff --git a/packages/pi-coding-agent/src/core/lifecycle-hooks.test.ts b/packages/pi-coding-agent/src/core/lifecycle-hooks.test.ts index b005729ee..b7ed478bd 100644 --- a/packages/pi-coding-agent/src/core/lifecycle-hooks.test.ts +++ b/packages/pi-coding-agent/src/core/lifecycle-hooks.test.ts @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import { mkdtempSync, mkdirSync, rmSync, writeFileSync, existsSync } from "node:fs"; import { homedir, tmpdir } from "node:os"; import { join, resolve } from "node:path"; -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import { readManifestRuntimeDeps, collectRuntimeDependencies, diff --git a/packages/pi-coding-agent/src/core/lsp/lsp-integration.test.ts b/packages/pi-coding-agent/src/core/lsp/lsp-integration.test.ts index 1db637356..62025a8e2 100644 --- a/packages/pi-coding-agent/src/core/lsp/lsp-integration.test.ts +++ b/packages/pi-coding-agent/src/core/lsp/lsp-integration.test.ts @@ -8,7 +8,7 @@ * Run: node --experimental-strip-types --test src/core/lsp/lsp-integration.test.ts * (from packages/pi-coding-agent/) */ -import test from "node:test"; +import { test } from 'vitest'; import assert from "node:assert/strict"; import { spawn } from "node:child_process"; import * as fs from "node:fs"; diff --git a/packages/pi-coding-agent/src/core/lsp/lsp-legacy-alias.test.ts b/packages/pi-coding-agent/src/core/lsp/lsp-legacy-alias.test.ts index b28b7c16e..9108c643b 100644 --- a/packages/pi-coding-agent/src/core/lsp/lsp-legacy-alias.test.ts +++ b/packages/pi-coding-agent/src/core/lsp/lsp-legacy-alias.test.ts @@ -9,7 +9,7 @@ * containing an lsp.json that uses the legacy key. */ -import { describe, it, beforeEach, afterEach } from "node:test"; +import { describe, it, beforeEach, afterEach } from 'vitest'; import assert from "node:assert/strict"; import * as fs from "node:fs"; import * as path from "node:path"; diff --git a/packages/pi-coding-agent/src/core/messages.test.ts b/packages/pi-coding-agent/src/core/messages.test.ts index 6741da93c..3d4d78a12 100644 --- a/packages/pi-coding-agent/src/core/messages.test.ts +++ b/packages/pi-coding-agent/src/core/messages.test.ts @@ -6,7 +6,7 @@ * user-typed input when converted to LLM messages. */ -import test from "node:test"; +import { test } from 'vitest'; import assert from "node:assert/strict"; import { convertToLlm, type CustomMessage } from "./messages.js"; diff --git a/packages/pi-coding-agent/src/core/model-discovery.test.ts b/packages/pi-coding-agent/src/core/model-discovery.test.ts index 4dca8f93d..7c734c76a 100644 --- a/packages/pi-coding-agent/src/core/model-discovery.test.ts +++ b/packages/pi-coding-agent/src/core/model-discovery.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import { DISCOVERY_TTLS, getDefaultTTL, diff --git a/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts b/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts index 7a4b6e3a6..317f516a8 100644 --- a/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +++ b/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import type { Api, Model, SimpleStreamOptions, Context, AssistantMessageEventStream } from "@singularity-forge/pi-ai"; import { getApiProvider } from "@singularity-forge/pi-ai"; import { AuthStorage, type AuthStorageData } from "./auth-storage.js"; diff --git a/packages/pi-coding-agent/src/core/model-registry-discovery.test.ts b/packages/pi-coding-agent/src/core/model-registry-discovery.test.ts index afcdc3ed8..270279894 100644 --- a/packages/pi-coding-agent/src/core/model-registry-discovery.test.ts +++ b/packages/pi-coding-agent/src/core/model-registry-discovery.test.ts @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import { mkdirSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { afterEach, beforeEach, describe, it } from "node:test"; +import { afterEach, beforeEach, describe, it } from 'vitest'; import { AuthStorage } from "./auth-storage.js"; import { ModelDiscoveryCache } from "./discovery-cache.js"; import { diff --git a/packages/pi-coding-agent/src/core/model-registry-env-fallback.test.ts b/packages/pi-coding-agent/src/core/model-registry-env-fallback.test.ts index fb48db7af..c03cf6035 100644 --- a/packages/pi-coding-agent/src/core/model-registry-env-fallback.test.ts +++ b/packages/pi-coding-agent/src/core/model-registry-env-fallback.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import type { AuthStorage } from "./auth-storage.js"; import { ModelRegistry } from "./model-registry.js"; diff --git a/packages/pi-coding-agent/src/core/model-registry-proxy-routing.test.ts b/packages/pi-coding-agent/src/core/model-registry-proxy-routing.test.ts index 0d3788abb..db09f7836 100644 --- a/packages/pi-coding-agent/src/core/model-registry-proxy-routing.test.ts +++ b/packages/pi-coding-agent/src/core/model-registry-proxy-routing.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import type { Api, AssistantMessageEventStream, Context, Model, SimpleStreamOptions } from "@singularity-forge/pi-ai"; import type { AuthStorage } from "./auth-storage.js"; import { ModelRegistry } from "./model-registry.js"; diff --git a/packages/pi-coding-agent/src/core/model-resolver-initial-model-auth.test.ts b/packages/pi-coding-agent/src/core/model-resolver-initial-model-auth.test.ts index c0615d660..f6639355b 100644 --- a/packages/pi-coding-agent/src/core/model-resolver-initial-model-auth.test.ts +++ b/packages/pi-coding-agent/src/core/model-resolver-initial-model-auth.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import type { Api, Model } from "@singularity-forge/pi-ai"; import type { ModelRegistry } from "./model-registry.js"; import { findInitialModel } from "./model-resolver.js"; diff --git a/packages/pi-coding-agent/src/core/model-resolver.test.ts b/packages/pi-coding-agent/src/core/model-resolver.test.ts index d15e93793..bde741dfc 100644 --- a/packages/pi-coding-agent/src/core/model-resolver.test.ts +++ b/packages/pi-coding-agent/src/core/model-resolver.test.ts @@ -5,7 +5,7 @@ * "current". */ -import test from "node:test"; +import { test } from 'vitest'; import assert from "node:assert/strict"; import { findInitialModel } from "./model-resolver.js"; diff --git a/packages/pi-coding-agent/src/core/models-json-writer.test.ts b/packages/pi-coding-agent/src/core/models-json-writer.test.ts index 3dcb0be98..619dc0b33 100644 --- a/packages/pi-coding-agent/src/core/models-json-writer.test.ts +++ b/packages/pi-coding-agent/src/core/models-json-writer.test.ts @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import { existsSync, mkdirSync, readFileSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { afterEach, beforeEach, describe, it } from "node:test"; +import { afterEach, beforeEach, describe, it } from 'vitest'; import { ModelsJsonWriter } from "./models-json-writer.js"; let testDir: string; diff --git a/packages/pi-coding-agent/src/core/package-commands.test.ts b/packages/pi-coding-agent/src/core/package-commands.test.ts index 4b691a812..4355c149a 100644 --- a/packages/pi-coding-agent/src/core/package-commands.test.ts +++ b/packages/pi-coding-agent/src/core/package-commands.test.ts @@ -3,7 +3,7 @@ import { existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync import { tmpdir } from "node:os"; import { join } from "node:path"; import { Writable } from "node:stream"; -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import { runPackageCommand } from "./package-commands.js"; function createCaptureStream() { diff --git a/packages/pi-coding-agent/src/core/resolve-config-value.test.ts b/packages/pi-coding-agent/src/core/resolve-config-value.test.ts index 48a0f8f0e..81472a726 100644 --- a/packages/pi-coding-agent/src/core/resolve-config-value.test.ts +++ b/packages/pi-coding-agent/src/core/resolve-config-value.test.ts @@ -1,4 +1,4 @@ -import { describe, it, beforeEach, afterEach } from "node:test"; +import { describe, it, beforeEach, afterEach } from 'vitest'; import assert from "node:assert/strict"; import { resolveConfigValue, diff --git a/packages/pi-coding-agent/src/core/resource-loader-cache-reset.test.ts b/packages/pi-coding-agent/src/core/resource-loader-cache-reset.test.ts index dfb3cecdd..53afe87e0 100644 --- a/packages/pi-coding-agent/src/core/resource-loader-cache-reset.test.ts +++ b/packages/pi-coding-agent/src/core/resource-loader-cache-reset.test.ts @@ -1,7 +1,7 @@ // SF — Regression test for #3616: reload() must reset jiti extension loader cache // Copyright (c) 2026 Jeremy McSpadden -import test, { describe } from "node:test"; +import { test, describe } from 'vitest'; import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { join } from "node:path"; diff --git a/packages/pi-coding-agent/src/core/retry-handler.test.ts b/packages/pi-coding-agent/src/core/retry-handler.test.ts index 6a9d0c17a..c1a83b37b 100644 --- a/packages/pi-coding-agent/src/core/retry-handler.test.ts +++ b/packages/pi-coding-agent/src/core/retry-handler.test.ts @@ -6,7 +6,7 @@ * downgrade from [1m] to base when no cross-provider fallback exists. */ -import { describe, it, beforeEach, mock, type Mock } from "node:test"; +import { vi, describe, it, beforeEach, type Mock } from 'vitest'; import assert from "node:assert/strict"; import { RetryHandler, type RetryHandlerDeps } from "./retry-handler.js"; import type { Api, AssistantMessage, Model } from "@singularity-forge/pi-ai"; @@ -69,13 +69,13 @@ function createMockDeps(overrides?: { }): MockDeps { const model = overrides?.model ?? createMockModel("anthropic", "claude-opus-4-6[1m]"); const emittedEvents: Array> = []; - const continueFn = mock.fn(async () => {}); - const onModelChangeFn = mock.fn((_model: Model) => {}); - const markUsageLimitReached = mock.fn( + const continueFn = vi.fn(async () => {}); + const onModelChangeFn = vi.fn((_model: Model) => {}); + const markUsageLimitReached = vi.fn( () => overrides?.markUsageLimitReachedResult ?? false, ); - const findFallback = mock.fn(async () => overrides?.fallbackResult ?? null); - const findModel = mock.fn( + const findFallback = vi.fn(async () => overrides?.fallbackResult ?? null); + const findModel = vi.fn( overrides?.findModelResult ?? ((_provider: string, _modelId: string) => undefined), ); @@ -85,8 +85,8 @@ function createMockDeps(overrides?: { agent: { continue: continueFn, state: { messages }, - setModel: mock.fn(), - replaceMessages: mock.fn((newMessages: any[]) => { + setModel: vi.fn(), + replaceMessages: vi.fn((newMessages: any[]) => { messages.length = 0; messages.push(...newMessages); }), diff --git a/packages/pi-coding-agent/src/core/sdk.test.ts b/packages/pi-coding-agent/src/core/sdk.test.ts index cebb41490..38d4336a7 100644 --- a/packages/pi-coding-agent/src/core/sdk.test.ts +++ b/packages/pi-coding-agent/src/core/sdk.test.ts @@ -1,7 +1,7 @@ // pi-coding-agent / CredentialCooldownError unit tests // Copyright (c) 2026 Jeremy McSpadden -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import assert from "node:assert/strict"; import { CredentialCooldownError } from "./sdk.js"; diff --git a/packages/pi-coding-agent/src/core/session-manager.test.ts b/packages/pi-coding-agent/src/core/session-manager.test.ts index 4891a9d38..3f5f5df2b 100644 --- a/packages/pi-coding-agent/src/core/session-manager.test.ts +++ b/packages/pi-coding-agent/src/core/session-manager.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { describe, it, afterEach } from "node:test"; +import { describe, it, afterEach } from 'vitest'; import { mkdtempSync, rmSync } from "node:fs"; import { join } from "node:path"; import { tmpdir } from "node:os"; diff --git a/packages/pi-coding-agent/src/core/settings-manager-security.test.ts b/packages/pi-coding-agent/src/core/settings-manager-security.test.ts index b052a2bd6..f25a2d74d 100644 --- a/packages/pi-coding-agent/src/core/settings-manager-security.test.ts +++ b/packages/pi-coding-agent/src/core/settings-manager-security.test.ts @@ -1,4 +1,4 @@ -import { describe, it, afterEach } from "node:test"; +import { describe, it, afterEach } from 'vitest'; import assert from "node:assert/strict"; import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs"; import { join } from "node:path"; diff --git a/packages/pi-coding-agent/src/core/skill-tool.test.ts b/packages/pi-coding-agent/src/core/skill-tool.test.ts index 239fb5063..196998401 100644 --- a/packages/pi-coding-agent/src/core/skill-tool.test.ts +++ b/packages/pi-coding-agent/src/core/skill-tool.test.ts @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { afterEach, beforeEach, describe, it } from "node:test"; +import { afterEach, beforeEach, describe, it } from 'vitest'; import { Agent } from "@singularity-forge/pi-agent-core"; import { AuthStorage } from "./auth-storage.js"; diff --git a/packages/pi-coding-agent/src/core/tools/bash-background.test.ts b/packages/pi-coding-agent/src/core/tools/bash-background.test.ts index 316f752de..340cff14a 100644 --- a/packages/pi-coding-agent/src/core/tools/bash-background.test.ts +++ b/packages/pi-coding-agent/src/core/tools/bash-background.test.ts @@ -7,7 +7,7 @@ * the command does not already redirect stdout. */ -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import assert from "node:assert/strict"; import { rewriteBackgroundCommand } from "./bash.js"; diff --git a/packages/pi-coding-agent/src/core/tools/bash-interceptor.test.ts b/packages/pi-coding-agent/src/core/tools/bash-interceptor.test.ts index 315fc4dc0..bd71c6717 100644 --- a/packages/pi-coding-agent/src/core/tools/bash-interceptor.test.ts +++ b/packages/pi-coding-agent/src/core/tools/bash-interceptor.test.ts @@ -1,4 +1,4 @@ -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import assert from "node:assert/strict"; import { checkBashInterception, diff --git a/packages/pi-coding-agent/src/core/tools/bash-spawn-windows.test.ts b/packages/pi-coding-agent/src/core/tools/bash-spawn-windows.test.ts index 5579322dd..3c9014b04 100644 --- a/packages/pi-coding-agent/src/core/tools/bash-spawn-windows.test.ts +++ b/packages/pi-coding-agent/src/core/tools/bash-spawn-windows.test.ts @@ -16,7 +16,7 @@ * See: singularity-forge/sf-run#XXXX */ -import test from "node:test"; +import { test } from 'vitest'; import assert from "node:assert/strict"; import { spawn } from "node:child_process"; diff --git a/packages/pi-coding-agent/src/core/tools/edit-diff.test.ts b/packages/pi-coding-agent/src/core/tools/edit-diff.test.ts index b7272559e..7bb42f925 100644 --- a/packages/pi-coding-agent/src/core/tools/edit-diff.test.ts +++ b/packages/pi-coding-agent/src/core/tools/edit-diff.test.ts @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import { computeEditDiff, diff --git a/packages/pi-coding-agent/src/core/tools/hashline.test.ts b/packages/pi-coding-agent/src/core/tools/hashline.test.ts index a4df96d3a..be87105b2 100644 --- a/packages/pi-coding-agent/src/core/tools/hashline.test.ts +++ b/packages/pi-coding-agent/src/core/tools/hashline.test.ts @@ -1,4 +1,4 @@ -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import assert from "node:assert/strict"; import { computeLineHash, diff --git a/packages/pi-coding-agent/src/core/tools/path-utils.test.ts b/packages/pi-coding-agent/src/core/tools/path-utils.test.ts index 78c3a30ab..981b9181e 100644 --- a/packages/pi-coding-agent/src/core/tools/path-utils.test.ts +++ b/packages/pi-coding-agent/src/core/tools/path-utils.test.ts @@ -1,4 +1,4 @@ -import { describe, it, mock, afterEach } from "node:test"; +import { describe, it, afterEach } from 'vitest'; import assert from "node:assert/strict"; import { mkdtempSync, writeFileSync, symlinkSync, unlinkSync, rmdirSync } from "node:fs"; import { tmpdir } from "node:os"; diff --git a/packages/pi-coding-agent/src/core/tools/spawn-shell-windows.test.ts b/packages/pi-coding-agent/src/core/tools/spawn-shell-windows.test.ts index a61f35a0c..5b9247bb2 100644 --- a/packages/pi-coding-agent/src/core/tools/spawn-shell-windows.test.ts +++ b/packages/pi-coding-agent/src/core/tools/spawn-shell-windows.test.ts @@ -13,7 +13,7 @@ * Fixes: singularity-forge/sf-run#2854 */ -import test from "node:test"; +import { test } from 'vitest'; import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { join, dirname, relative } from "node:path"; diff --git a/packages/pi-coding-agent/src/modes/interactive/components/__tests__/login-dialog.test.ts b/packages/pi-coding-agent/src/modes/interactive/components/__tests__/login-dialog.test.ts index 1ce0469ff..49c18fac9 100644 --- a/packages/pi-coding-agent/src/modes/interactive/components/__tests__/login-dialog.test.ts +++ b/packages/pi-coding-agent/src/modes/interactive/components/__tests__/login-dialog.test.ts @@ -1,4 +1,4 @@ -import { describe, test } from "node:test"; +import { describe } from 'vitest'; import assert from "node:assert/strict"; import { buildAuthUrlPresentation } from "../login-dialog.js"; diff --git a/packages/pi-coding-agent/src/modes/interactive/components/__tests__/provider-display-name.test.ts b/packages/pi-coding-agent/src/modes/interactive/components/__tests__/provider-display-name.test.ts index 7d33b369a..b066615f9 100644 --- a/packages/pi-coding-agent/src/modes/interactive/components/__tests__/provider-display-name.test.ts +++ b/packages/pi-coding-agent/src/modes/interactive/components/__tests__/provider-display-name.test.ts @@ -1,5 +1,5 @@ // SF — Provider display name mapping tests -import { test, describe } from "node:test"; +import { describe } from 'vitest'; import assert from "node:assert/strict"; import { providerDisplayName } from "../model-selector.js"; diff --git a/packages/pi-coding-agent/src/modes/interactive/components/__tests__/timestamp.test.ts b/packages/pi-coding-agent/src/modes/interactive/components/__tests__/timestamp.test.ts index c5eb4ce74..f92a1c762 100644 --- a/packages/pi-coding-agent/src/modes/interactive/components/__tests__/timestamp.test.ts +++ b/packages/pi-coding-agent/src/modes/interactive/components/__tests__/timestamp.test.ts @@ -1,4 +1,4 @@ -import { test, describe } from "node:test"; +import { describe } from 'vitest'; import assert from "node:assert/strict"; import { formatTimestamp } from "../timestamp.js"; diff --git a/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts b/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts index d61d92466..c2de93ce0 100644 --- a/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +++ b/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts @@ -1,4 +1,4 @@ -import { describe, test } from "node:test"; +import { describe } from 'vitest'; import assert from "node:assert/strict"; import stripAnsi from "strip-ansi"; import { ToolExecutionComponent } from "../tool-execution.js"; diff --git a/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.test.ts b/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.test.ts index 9a881d6fa..30a0a391a 100644 --- a/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.test.ts +++ b/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { describe, it, mock } from "node:test"; +import { describe, it } from 'vitest'; import { DynamicBorder } from "./dynamic-border.js"; diff --git a/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts b/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts index d667af20d..c908679f6 100644 --- a/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +++ b/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import test from "node:test"; +import { test } from 'vitest'; import { findLatestPinnableText } from "./chat-controller.js"; diff --git a/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts b/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts index 4119d028c..78f6846ac 100644 --- a/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +++ b/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import test from "node:test"; +import { test } from 'vitest'; import { setupEditorSubmitHandler } from "./input-controller.js"; diff --git a/packages/pi-coding-agent/src/modes/rpc/rpc-protocol-v2.test.ts b/packages/pi-coding-agent/src/modes/rpc/rpc-protocol-v2.test.ts index 9d538d3e5..818e5a548 100644 --- a/packages/pi-coding-agent/src/modes/rpc/rpc-protocol-v2.test.ts +++ b/packages/pi-coding-agent/src/modes/rpc/rpc-protocol-v2.test.ts @@ -6,7 +6,7 @@ * mock child processes using PassThrough streams. */ -import { describe, it, beforeEach, afterEach, mock } from "node:test"; +import { describe, it, beforeEach, afterEach } from 'vitest'; import assert from "node:assert/strict"; import { PassThrough } from "node:stream"; import { attachJsonlLineReader, serializeJsonLine } from "./jsonl.js"; diff --git a/packages/pi-coding-agent/src/resources/extensions/memory/storage.test.ts b/packages/pi-coding-agent/src/resources/extensions/memory/storage.test.ts index fc8fedf3f..2151a8050 100644 --- a/packages/pi-coding-agent/src/resources/extensions/memory/storage.test.ts +++ b/packages/pi-coding-agent/src/resources/extensions/memory/storage.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { describe, it, afterEach } from "node:test"; +import { describe, it, afterEach } from 'vitest'; import { mkdtempSync, rmSync, readFileSync, existsSync } from "node:fs"; import { join } from "node:path"; import { tmpdir } from "node:os"; diff --git a/packages/pi-coding-agent/src/tests/path-display.test.ts b/packages/pi-coding-agent/src/tests/path-display.test.ts index 92eb2113e..d1a6bd3f7 100644 --- a/packages/pi-coding-agent/src/tests/path-display.test.ts +++ b/packages/pi-coding-agent/src/tests/path-display.test.ts @@ -5,7 +5,7 @@ * the system prompt builder produces forward-slash paths for LLM consumption. */ -import test from "node:test"; +import { test } from 'vitest'; import assert from "node:assert/strict"; import { toPosixPath } from "../utils/path-display.js"; import { buildSystemPrompt } from "../core/system-prompt.js"; diff --git a/packages/pi-coding-agent/src/tests/system-prompt-skill-filter.test.ts b/packages/pi-coding-agent/src/tests/system-prompt-skill-filter.test.ts index a81cc2eb2..cdce77d5e 100644 --- a/packages/pi-coding-agent/src/tests/system-prompt-skill-filter.test.ts +++ b/packages/pi-coding-agent/src/tests/system-prompt-skill-filter.test.ts @@ -3,7 +3,7 @@ // filter lets consumers narrow the catalog rendered into // the cached system prompt without touching skill loading or invocation. -import test from "node:test"; +import { test } from 'vitest'; import assert from "node:assert/strict"; import { buildSystemPrompt } from "../core/system-prompt.js"; diff --git a/packages/pi-tui/src/__tests__/autocomplete.test.ts b/packages/pi-tui/src/__tests__/autocomplete.test.ts index e065f8f6b..9be56f426 100644 --- a/packages/pi-tui/src/__tests__/autocomplete.test.ts +++ b/packages/pi-tui/src/__tests__/autocomplete.test.ts @@ -1,4 +1,4 @@ -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import assert from "node:assert/strict"; import { CombinedAutocompleteProvider } from "../autocomplete.js"; import type { SlashCommand } from "../autocomplete.js"; diff --git a/packages/pi-tui/src/__tests__/fuzzy.test.ts b/packages/pi-tui/src/__tests__/fuzzy.test.ts index b576ebfdb..cec9e0f44 100644 --- a/packages/pi-tui/src/__tests__/fuzzy.test.ts +++ b/packages/pi-tui/src/__tests__/fuzzy.test.ts @@ -1,4 +1,4 @@ -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import assert from "node:assert/strict"; import { fuzzyMatch, fuzzyFilter } from "../fuzzy.js"; diff --git a/packages/pi-tui/src/__tests__/overlay-layout.test.ts b/packages/pi-tui/src/__tests__/overlay-layout.test.ts index 49d0539da..549acbeaf 100644 --- a/packages/pi-tui/src/__tests__/overlay-layout.test.ts +++ b/packages/pi-tui/src/__tests__/overlay-layout.test.ts @@ -1,6 +1,6 @@ // pi-tui — Overlay Layout Tests (backdrop dimming) -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import assert from "node:assert/strict"; import { compositeOverlays, type OverlayEntry } from "../overlay-layout.js"; diff --git a/packages/pi-tui/src/__tests__/stdin-buffer.test.ts b/packages/pi-tui/src/__tests__/stdin-buffer.test.ts index ba053567b..fa4555881 100644 --- a/packages/pi-tui/src/__tests__/stdin-buffer.test.ts +++ b/packages/pi-tui/src/__tests__/stdin-buffer.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import { setTimeout as delay } from "node:timers/promises"; import { StdinBuffer } from "../stdin-buffer.js"; diff --git a/packages/pi-tui/src/__tests__/tui.test.ts b/packages/pi-tui/src/__tests__/tui.test.ts index dd63211a3..ad249c700 100644 --- a/packages/pi-tui/src/__tests__/tui.test.ts +++ b/packages/pi-tui/src/__tests__/tui.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import { Container, TUI } from "../tui.js"; import type { Component } from "../tui.js"; diff --git a/packages/pi-tui/src/components/__tests__/cancellable-loader.test.ts b/packages/pi-tui/src/components/__tests__/cancellable-loader.test.ts index 4f7889402..89a233002 100644 --- a/packages/pi-tui/src/components/__tests__/cancellable-loader.test.ts +++ b/packages/pi-tui/src/components/__tests__/cancellable-loader.test.ts @@ -1,12 +1,12 @@ // pi-tui CancellableLoader component regression tests // Copyright (c) 2026 Jeremy McSpadden -import { describe, it, mock, beforeEach, afterEach } from "node:test"; +import { vi, describe, it, beforeEach, afterEach } from 'vitest'; import assert from "node:assert/strict"; import { CancellableLoader } from "../cancellable-loader.js"; function makeMockTUI() { - return { requestRender: mock.fn() } as any; + return { requestRender: vi.fn() } as any; } describe("CancellableLoader", () => { diff --git a/packages/pi-tui/src/components/__tests__/editor.test.ts b/packages/pi-tui/src/components/__tests__/editor.test.ts index 91eb6257b..c03307732 100644 --- a/packages/pi-tui/src/components/__tests__/editor.test.ts +++ b/packages/pi-tui/src/components/__tests__/editor.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import { Editor, type EditorTheme } from "../editor.js"; import { CURSOR_MARKER, TUI } from "../../tui.js"; diff --git a/packages/pi-tui/src/components/__tests__/input.test.ts b/packages/pi-tui/src/components/__tests__/input.test.ts index 7ea0fec46..1efaf1ee0 100644 --- a/packages/pi-tui/src/components/__tests__/input.test.ts +++ b/packages/pi-tui/src/components/__tests__/input.test.ts @@ -1,7 +1,7 @@ // pi-tui Input component regression tests // Copyright (c) 2026 Jeremy McSpadden -import { describe, it } from "node:test"; +import { describe, it } from 'vitest'; import assert from "node:assert/strict"; import { Input } from "../input.js"; diff --git a/packages/pi-tui/src/components/__tests__/loader.test.ts b/packages/pi-tui/src/components/__tests__/loader.test.ts index 9c22056fa..52929e9b8 100644 --- a/packages/pi-tui/src/components/__tests__/loader.test.ts +++ b/packages/pi-tui/src/components/__tests__/loader.test.ts @@ -1,12 +1,12 @@ // pi-tui Loader component regression tests // Copyright (c) 2026 Jeremy McSpadden -import { describe, it, mock, beforeEach, afterEach } from "node:test"; +import { vi, describe, it, beforeEach, afterEach } from 'vitest'; import assert from "node:assert/strict"; import { Loader } from "../loader.js"; function makeMockTUI() { - return { requestRender: mock.fn() } as any; + return { requestRender: vi.fn() } as any; } describe("Loader", () => { diff --git a/packages/pi-tui/src/components/__tests__/markdown-maxlines.test.ts b/packages/pi-tui/src/components/__tests__/markdown-maxlines.test.ts index fb9fbf0bc..e473be465 100644 --- a/packages/pi-tui/src/components/__tests__/markdown-maxlines.test.ts +++ b/packages/pi-tui/src/components/__tests__/markdown-maxlines.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { test } from "node:test"; +import { } from 'vitest'; import { Markdown, type MarkdownTheme } from "../markdown.js"; diff --git a/packages/pi-tui/src/components/image.test.ts b/packages/pi-tui/src/components/image.test.ts index 3bef04a85..2f11674c5 100644 --- a/packages/pi-tui/src/components/image.test.ts +++ b/packages/pi-tui/src/components/image.test.ts @@ -3,7 +3,7 @@ * re-render loop when dimensions resolve in cmux sessions. */ -import { describe, test } from "node:test"; +import { describe } from 'vitest'; import assert from "node:assert/strict"; import { Image } from "./image.js"; diff --git a/packages/rpc-client/src/rpc-client.test.ts b/packages/rpc-client/src/rpc-client.test.ts index d99aeb4de..5cdd5f00c 100644 --- a/packages/rpc-client/src/rpc-client.test.ts +++ b/packages/rpc-client/src/rpc-client.test.ts @@ -1,4 +1,4 @@ -import { describe, it, beforeEach, afterEach } from "node:test"; +import { describe, it, beforeEach, afterEach } from 'vitest'; import assert from "node:assert/strict"; import { PassThrough } from "node:stream"; import { serializeJsonLine, attachJsonlLineReader } from "./jsonl.js"; diff --git a/scripts/check-versioned-json.test.mjs b/scripts/check-versioned-json.test.mjs index 28747c935..bcb9b4807 100644 --- a/scripts/check-versioned-json.test.mjs +++ b/scripts/check-versioned-json.test.mjs @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import test from "node:test"; +import { test } from 'vitest'; import { checkJsonPolicy, diff --git a/scripts/fix-vitest-api.mjs b/scripts/fix-vitest-api.mjs new file mode 100644 index 000000000..97904a5dd --- /dev/null +++ b/scripts/fix-vitest-api.mjs @@ -0,0 +1,89 @@ +#!/usr/bin/env node +/** + * Fix remaining node:test API calls that the initial migration missed. + * + * 1. t.after(() => ...) → afterEach(() => ...) (add afterEach to imports if needed) + * 2. t.before(() => ...) → beforeEach(() => ...) (add beforeEach to imports if needed) + * 3. await t.test("name", fn) → test("name", fn) (flatten subtests) + * 4. t.skip("msg") → return ctx.skip("msg") — only in describe blocks; most are top-level test() calls + * where t.skip() should become a return or conditional. + */ + +import { readFileSync, writeFileSync } from "node:fs"; +import { execSync } from "node:child_process"; + +const files = execSync( + 'grep -rl "t\\.after\\b\\|t\\.before\\b\\|t\\.test(\\|t\\.skip(" src/tests/ src/resources/extensions/ --include="*.test.ts" --include="*.test.mjs"', + { encoding: "utf-8", maxBuffer: 50 * 1024 * 1024 }, +) + .trim() + .split("\n") + .filter(Boolean); + +console.log(`Fixing ${files.length} files`); + +let updated = 0; + +for (const file of files) { + let content = readFileSync(file, "utf-8"); + let changed = false; + + // 1. t.after(() => ...) → afterEach(() => ...) + if (content.includes("t.after(")) { + content = content.replace(/\bt\.after\(/g, "afterEach("); + changed = true; + } + + // 2. t.before(() => ...) → beforeEach(() => ...) + if (content.includes("t.before(")) { + content = content.replace(/\bt\.before\(/g, "beforeEach("); + changed = true; + } + + // 3. await t.test("name", fn) → test("name", fn) + // and t.test("name", fn) without await + if (content.includes("t.test(")) { + content = content.replace(/await\s+t\.test\(/g, "test("); + content = content.replace(/\bt\.test\(/g, "test("); + changed = true; + } + + // 4. t.skip("msg") → remove or comment — these are rare (4 files) + // In most cases t.skip is inside a test callback and means "skip this test". + // Vitest uses test.skip() at declaration time, not runtime. + // Best effort: replace with return + comment + if (content.includes("t.skip(")) { + content = content.replace(/\bt\.skip\(([^)]*)\)/g, 'return; // skip: $1'); + changed = true; + } + + // 5. Ensure afterEach/beforeEach are imported from vitest if used + if (changed) { + const needsAfterEach = content.includes("afterEach("); + const needsBeforeEach = content.includes("beforeEach("); + + if (needsAfterEach || needsBeforeEach) { + // Find the vitest import line and extend it + const vitestImportMatch = content.match(/import\s+\{([^}]+)\}\s+from\s+['"]vitest['"];?/); + if (vitestImportMatch) { + const existing = vitestImportMatch[1].split(",").map((s) => s.trim()); + const additions = []; + if (needsAfterEach && !existing.includes("afterEach")) additions.push("afterEach"); + if (needsBeforeEach && !existing.includes("beforeEach")) additions.push("beforeEach"); + + if (additions.length > 0) { + const all = [...existing, ...additions]; + const newImport = `import { ${all.join(", ")} } from 'vitest';`; + content = content.replace(vitestImportMatch[0], newImport); + } + } + } + } + + if (changed) { + writeFileSync(file, content, "utf-8"); + updated++; + } +} + +console.log(`Updated ${updated} files`); diff --git a/scripts/migrate-to-vitest-all.mjs b/scripts/migrate-to-vitest-all.mjs new file mode 100644 index 000000000..29f9d49cf --- /dev/null +++ b/scripts/migrate-to-vitest-all.mjs @@ -0,0 +1,134 @@ +#!/usr/bin/env node +/** + * Migrate ALL test files from node:test to vitest. + * + * Scans src/, packages/, web/, studio/, and scripts/. + * Changes: + * 1. Replace `from "node:test"` → `from 'vitest'` in all imports + * 2. Files using mock.fn(): replace `mock.fn` → `vi.fn` and add `vi` to imports + * 3. Files using mock.timers: migrate to vi fake timers API + */ + +import { readFileSync, readdirSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; + +const ROOTS = [ + join(process.cwd(), "src"), + join(process.cwd(), "packages"), + join(process.cwd(), "web"), + join(process.cwd(), "studio"), + join(process.cwd(), "scripts"), +]; + +function collectTestFiles(dirs) { + const results = []; + for (const dir of dirs) { + try { + const entries = readdirSync(dir, { withFileTypes: true }); + for (const entry of entries) { + const full = join(dir, entry.name); + if (entry.isDirectory()) { + results.push(...collectTestFiles([full])); + } else if ( + (entry.name.endsWith(".test.ts") || entry.name.endsWith(".test.mjs")) && + !entry.name.endsWith(".d.ts") + ) { + results.push(full); + } + } + } catch { + // Directory may not exist + } + } + return results; +} + +function migrateImport(content, { hasMockFn, hasMockTimers }) { + // Case 1: import test from "node:test"; + content = content.replace( + /^import test from "node:test";$/gm, + "import { test } from 'vitest';", + ); + + // Case 2: import { ... } from "node:test" (and variants with default import) + content = content.replace( + /import\s+(test,)?\s*\{\s*([^}]+)\}\s+from\s+"node:test";?/g, + (match, hasDefault, named) => { + const namedList = named + .split(",") + .map((s) => s.trim()) + .filter((s) => s !== "mock" && s !== ""); + + const extra = []; + if (hasMockFn || hasMockTimers) { + extra.push("vi"); + } + + const allNamed = [...extra, ...namedList.filter((s) => s !== "test")]; + const defaultImport = hasDefault ? "test, " : ""; + const namedStr = allNamed.join(", "); + return `import { ${defaultImport}${namedStr} } from 'vitest';`; + }, + ); + + return content; +} + +const files = collectTestFiles(ROOTS); +console.log(`Found ${files.length} test files`); + +let updated = 0; +let errors = 0; + +for (const file of files) { + try { + const content = readFileSync(file, "utf-8"); + if (!content.includes('from "node:test"')) continue; + + const hasMockFn = content.includes("mock.fn"); + const hasMockTimers = content.includes("mock.timers"); + + let newContent = migrateImport(content, { hasMockFn, hasMockTimers }); + + // Migrate mock.fn → vi.fn + if (hasMockFn) { + newContent = newContent.replace(/\bmock\.fn\b/g, "vi.fn"); + newContent = newContent.replace( + /ReturnType/g, + "ReturnType", + ); + } + + // Migrate mock.timers → vi fake timers + if (hasMockTimers) { + newContent = newContent.replace( + /\bmock\.timers\.enable\(\)/g, + "vi.useFakeTimers()", + ); + newContent = newContent.replace( + /\bmock\.timers\.tick\(([^)]+)\)/g, + "vi.advanceTimersByTime($1)", + ); + newContent = newContent.replace( + /\bmock\.timers\.reset\(\)/g, + "vi.useRealTimers()", + ); + } + + if (newContent !== content) { + writeFileSync(file, newContent, "utf-8"); + updated++; + const rel = file.replace(process.cwd() + "/", ""); + const tags = []; + if (hasMockFn) tags.push("mock"); + if (hasMockTimers) tags.push("timers"); + const tagStr = tags.length ? ` (${tags.join(", ")})` : ""; + console.log(` Migrated${tagStr}: ${rel}`); + } + } catch (err) { + console.error(`Error: ${file}: ${err.message}`); + errors++; + } +} + +console.log(`Updated ${updated} files, ${errors} errors`); diff --git a/src/resources/extensions/sf/auto-model-selection.ts b/src/resources/extensions/sf/auto-model-selection.ts index 9b21b8933..a66833fe7 100644 --- a/src/resources/extensions/sf/auto-model-selection.ts +++ b/src/resources/extensions/sf/auto-model-selection.ts @@ -830,6 +830,25 @@ export async function selectAndApplyModel( policyDenyReasons, ); } + + // ── Advisor-check fallback to session model ───────────────────────────────── + // When all configured models were filtered by the advisor check and no + // autoModeStartModel was provided, fall back to ctx.model (the active session + // model) so the subagent can still run on an allowed provider. + // Only fires when the advisor check was active (advisor_allowed_providers + // is set) and no model was successfully applied. + if ( + appliedModel === null && + (unitType === "subagent" || unitType.startsWith("subagent/")) && + prefs && + isProviderAllowedForAdvisor(ctx.model?.provider ?? "", prefs) + ) { + const ok = await pi.setModel(ctx.model, { persist: persistModelChanges }); + if (ok) { + appliedModel = ctx.model as Model; + reapplyThinkingLevel(pi, autoModeStartThinkingLevel); + } + } } else if (autoModeStartModel) { // No model preference for this unit type — re-apply the model captured // at auto-mode start to prevent bleed from shared global settings.json (#650). diff --git a/src/resources/extensions/sf/model-router.ts b/src/resources/extensions/sf/model-router.ts index 0c1650f07..533f9890a 100644 --- a/src/resources/extensions/sf/model-router.ts +++ b/src/resources/extensions/sf/model-router.ts @@ -552,6 +552,280 @@ export const MODEL_CAPABILITY_PROFILES: Record = { longContext: 55, instruction: 65, }, + + // ── Mistral AI ───────────────────────────────────────────────────────────── + "mistral-large-latest": { + coding: 85, + debugging: 80, + research: 75, + reasoning: 80, + speed: 50, + longContext: 75, + instruction: 85, + }, + "mistral-large-2411": { + coding: 85, + debugging: 80, + research: 75, + reasoning: 80, + speed: 50, + longContext: 75, + instruction: 85, + }, + "mistral-large-2512": { + coding: 88, + debugging: 82, + research: 78, + reasoning: 82, + speed: 52, + longContext: 78, + instruction: 88, + }, + "pixtral-large-latest": { + coding: 85, + debugging: 80, + research: 85, + reasoning: 80, + speed: 45, + longContext: 80, + instruction: 85, + }, + "mistral-medium-latest": { + coding: 75, + debugging: 70, + research: 65, + reasoning: 70, + speed: 60, + longContext: 65, + instruction: 75, + }, + "mistral-medium-2505": { + coding: 75, + debugging: 70, + research: 65, + reasoning: 70, + speed: 60, + longContext: 65, + instruction: 75, + }, + "mistral-medium-2508": { + coding: 78, + debugging: 72, + research: 68, + reasoning: 72, + speed: 62, + longContext: 68, + instruction: 78, + }, + "mistral-small-latest": { + coding: 65, + debugging: 60, + research: 55, + reasoning: 60, + speed: 80, + longContext: 55, + instruction: 70, + }, + "mistral-small-2506": { + coding: 65, + debugging: 60, + research: 55, + reasoning: 60, + speed: 80, + longContext: 55, + instruction: 70, + }, + "mistral-small-2603": { + coding: 68, + debugging: 62, + research: 58, + reasoning: 62, + speed: 82, + longContext: 58, + instruction: 72, + }, + "codestral-latest": { + coding: 85, + debugging: 75, + research: 50, + reasoning: 70, + speed: 70, + longContext: 60, + instruction: 80, + }, + "ministral-8b-latest": { + coding: 55, + debugging: 45, + research: 40, + reasoning: 45, + speed: 90, + longContext: 45, + instruction: 70, + }, + "ministral-3b-latest": { + coding: 45, + debugging: 35, + research: 30, + reasoning: 35, + speed: 95, + longContext: 35, + instruction: 60, + }, + "open-mixtral-8x22b": { + coding: 75, + debugging: 70, + research: 70, + reasoning: 70, + speed: 40, + longContext: 70, + instruction: 75, + }, + "pixtral-12b": { + coding: 60, + debugging: 55, + research: 65, + reasoning: 55, + speed: 75, + longContext: 60, + instruction: 65, + }, + "mistral-nemo": { + coding: 60, + debugging: 55, + research: 55, + reasoning: 55, + speed: 85, + longContext: 60, + instruction: 65, + }, + "magistral-medium-latest": { + coding: 80, + debugging: 75, + research: 75, + reasoning: 75, + speed: 55, + longContext: 75, + instruction: 80, + }, + "magistral-small": { + coding: 70, + debugging: 65, + research: 65, + reasoning: 65, + speed: 75, + longContext: 65, + instruction: 70, + }, + "devstral-2512": { + coding: 82, + debugging: 75, + research: 60, + reasoning: 70, + speed: 65, + longContext: 65, + instruction: 80, + }, + "devstral-medium-latest": { + coding: 78, + debugging: 70, + research: 55, + reasoning: 65, + speed: 75, + longContext: 60, + instruction: 75, + }, + "devstral-medium-2507": { + coding: 78, + debugging: 70, + research: 55, + reasoning: 65, + speed: 75, + longContext: 60, + instruction: 75, + }, + "devstral-small-2505": { + coding: 60, + debugging: 55, + research: 45, + reasoning: 50, + speed: 90, + longContext: 45, + instruction: 65, + }, + "devstral-small-2507": { + coding: 60, + debugging: 55, + research: 45, + reasoning: 50, + speed: 90, + longContext: 45, + instruction: 65, + }, + + // ── Zhipu AI (GLM) ───────────────────────────────────────────────────────── + "glm-5": { + coding: 90, + debugging: 85, + research: 80, + reasoning: 90, + speed: 35, + longContext: 80, + instruction: 88, + }, + "glm-5-turbo": { + coding: 85, + debugging: 80, + research: 75, + reasoning: 80, + speed: 65, + longContext: 75, + instruction: 85, + }, + "glm-5.1": { + coding: 92, + debugging: 87, + research: 82, + reasoning: 91, + speed: 38, + longContext: 82, + instruction: 89, + }, + "glm-5v-turbo": { + coding: 82, + debugging: 78, + research: 85, + reasoning: 78, + speed: 60, + longContext: 75, + instruction: 82, + }, + "glm-4.7": { + coding: 80, + debugging: 75, + research: 70, + reasoning: 75, + speed: 60, + longContext: 70, + instruction: 80, + }, + "glm-4.7-flash": { + coding: 50, + debugging: 40, + research: 40, + reasoning: 40, + speed: 95, + longContext: 50, + instruction: 65, + }, + "glm-4.7-flashx": { + coding: 45, + debugging: 35, + research: 35, + reasoning: 35, + speed: 98, + longContext: 45, + instruction: 60, + }, }; // ─── Base Task Requirements Data Table ─────────────────────────────────────── diff --git a/src/resources/extensions/sf/tests/auto-dashboard.test.ts b/src/resources/extensions/sf/tests/auto-dashboard.test.ts index 2e98d5fd8..6f336ce4e 100644 --- a/src/resources/extensions/sf/tests/auto-dashboard.test.ts +++ b/src/resources/extensions/sf/tests/auto-dashboard.test.ts @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { _resetWidgetModeForTests, @@ -268,7 +268,7 @@ test("widget mode respects project preference precedence and persists there", (t "utf-8", ); - t.after(() => { + afterEach(() => { cleanup(homeDir); cleanup(projectDir); _resetWidgetModeForTests(); diff --git a/src/resources/extensions/sf/tests/auto-lock-creation.test.ts b/src/resources/extensions/sf/tests/auto-lock-creation.test.ts index 6f8ec74e5..1a01fedff 100644 --- a/src/resources/extensions/sf/tests/auto-lock-creation.test.ts +++ b/src/resources/extensions/sf/tests/auto-lock-creation.test.ts @@ -10,7 +10,7 @@ import { import { createRequire } from "node:module"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { clearLock, @@ -141,7 +141,7 @@ test("bootstrap cleanup releases session lock artifacts", (t) => { const dir = mkdtempSync(join(tmpdir(), "sf-lock-test-")); mkdirSync(join(dir, ".sf"), { recursive: true }); - t.after(() => rmSync(dir, { recursive: true, force: true })); + afterEach(() => rmSync(dir, { recursive: true, force: true })); const result = acquireSessionLock(dir); assert.equal(result.acquired, true, "session lock should be acquired"); diff --git a/src/resources/extensions/sf/tests/auto-paused-session-validation.test.ts b/src/resources/extensions/sf/tests/auto-paused-session-validation.test.ts index acbd96579..d67622756 100644 --- a/src/resources/extensions/sf/tests/auto-paused-session-validation.test.ts +++ b/src/resources/extensions/sf/tests/auto-paused-session-validation.test.ts @@ -14,7 +14,7 @@ import { randomUUID } from "node:crypto"; import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { dirname, join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { fileURLToPath } from "node:url"; import { resolveMilestoneFile, resolveMilestonePath } from "../paths.ts"; @@ -92,7 +92,7 @@ function cleanup(base: string): void { test("resolveMilestonePath returns null for missing milestone", (t) => { const base = makeTmpBase(); mkdirSync(join(base, ".sf", "milestones"), { recursive: true }); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); const result = resolveMilestonePath(base, "M999"); assert.equal(result, null, "should return null for non-existent milestone"); @@ -101,7 +101,7 @@ test("resolveMilestonePath returns null for missing milestone", (t) => { test("resolveMilestonePath returns path for existing milestone", (t) => { const base = makeTmpBase(); mkdirSync(join(base, ".sf", "milestones", "M001"), { recursive: true }); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); const result = resolveMilestonePath(base, "M001"); assert.ok(result, "should return a path for existing milestone"); @@ -111,7 +111,7 @@ test("resolveMilestonePath returns path for existing milestone", (t) => { test("resolveMilestoneFile returns null when no SUMMARY exists", (t) => { const base = makeTmpBase(); mkdirSync(join(base, ".sf", "milestones", "M001"), { recursive: true }); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); const result = resolveMilestoneFile(base, "M001", "SUMMARY"); assert.equal(result, null, "should return null when no SUMMARY file"); @@ -122,7 +122,7 @@ test("resolveMilestoneFile returns path when SUMMARY exists (completed)", (t) => const mDir = join(base, ".sf", "milestones", "M001"); mkdirSync(mDir, { recursive: true }); writeFileSync(join(mDir, "M001-SUMMARY.md"), "# Summary\nDone."); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); const result = resolveMilestoneFile(base, "M001", "SUMMARY"); assert.ok(result, "should return a path when SUMMARY exists"); @@ -134,7 +134,7 @@ test("resolveMilestoneFile returns path when SUMMARY exists (completed)", (t) => test("stale milestone: missing dir means paused session should be discarded", (t) => { const base = makeTmpBase(); mkdirSync(join(base, ".sf", "milestones"), { recursive: true }); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); const mDir = resolveMilestonePath(base, "M999"); const summaryFile = resolveMilestoneFile(base, "M999", "SUMMARY"); @@ -150,7 +150,7 @@ test("stale milestone: completed (has SUMMARY) means paused session should be di const mDir = join(base, ".sf", "milestones", "M001"); mkdirSync(mDir, { recursive: true }); writeFileSync(join(mDir, "M001-SUMMARY.md"), "# Summary\nDone."); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); const dir = resolveMilestonePath(base, "M001"); const summaryFile = resolveMilestoneFile(base, "M001", "SUMMARY"); @@ -161,7 +161,7 @@ test("stale milestone: completed (has SUMMARY) means paused session should be di test("valid milestone: exists and has no SUMMARY means paused session is valid", (t) => { const base = makeTmpBase(); mkdirSync(join(base, ".sf", "milestones", "M001"), { recursive: true }); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); const dir = resolveMilestonePath(base, "M001"); const summaryFile = resolveMilestoneFile(base, "M001", "SUMMARY"); diff --git a/src/resources/extensions/sf/tests/auto-stale-lock-self-kill.test.ts b/src/resources/extensions/sf/tests/auto-stale-lock-self-kill.test.ts index 4e13f20de..eb0041139 100644 --- a/src/resources/extensions/sf/tests/auto-stale-lock-self-kill.test.ts +++ b/src/resources/extensions/sf/tests/auto-stale-lock-self-kill.test.ts @@ -8,7 +8,7 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { checkRemoteAutoSession, stopAutoRemote } from "../auto.ts"; import { readCrashLock, writeLock } from "../crash-recovery.ts"; @@ -22,7 +22,7 @@ function makeTmpProject(): string { test("#2730: checkRemoteAutoSession returns { running: false } when lock PID matches current process", (t) => { const dir = makeTmpProject(); - t.after(() => rmSync(dir, { recursive: true, force: true })); + afterEach(() => rmSync(dir, { recursive: true, force: true })); // Write a lock with the current process PID — simulates a stale lock // left behind after step-mode exit without full cleanup. @@ -42,7 +42,7 @@ test("#2730: checkRemoteAutoSession returns { running: false } when lock PID mat test("#2730: checkRemoteAutoSession still detects a genuine remote session (different PID)", (t) => { const dir = makeTmpProject(); - t.after(() => rmSync(dir, { recursive: true, force: true })); + afterEach(() => rmSync(dir, { recursive: true, force: true })); // Use parent PID — guaranteed alive, guaranteed not our PID. const remotePid = process.ppid; @@ -71,7 +71,7 @@ test("#2730: checkRemoteAutoSession still detects a genuine remote session (diff test("#2730: stopAutoRemote does not send SIGTERM when lock PID matches current process", (t) => { const dir = makeTmpProject(); - t.after(() => rmSync(dir, { recursive: true, force: true })); + afterEach(() => rmSync(dir, { recursive: true, force: true })); // Write a lock with our own PID writeLock(dir, "execute-task", "M001/S01/T01"); @@ -88,7 +88,7 @@ test("#2730: stopAutoRemote does not send SIGTERM when lock PID matches current test("#2730: stopAutoRemote clears stale lock from dead remote process without error", (t) => { const dir = makeTmpProject(); - t.after(() => rmSync(dir, { recursive: true, force: true })); + afterEach(() => rmSync(dir, { recursive: true, force: true })); // Simulate a stale lock from a process that no longer exists const lockData = { diff --git a/src/resources/extensions/sf/tests/captures.test.ts b/src/resources/extensions/sf/tests/captures.test.ts index 2ceb24f11..bcfa657a4 100644 --- a/src/resources/extensions/sf/tests/captures.test.ts +++ b/src/resources/extensions/sf/tests/captures.test.ts @@ -14,7 +14,7 @@ import assert from "node:assert/strict"; import { existsSync, mkdirSync, readFileSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { appendCapture, hasPendingCaptures, @@ -41,7 +41,7 @@ function makeTempDir(prefix: string): string { test("captures: appendCapture creates CAPTURES.md on first call", (t) => { const tmp = makeTempDir("cap-create"); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); const id = appendCapture(tmp, "first thought"); assert.ok(id.startsWith("CAP-"), "ID should start with CAP-"); @@ -64,7 +64,7 @@ test("captures: appendCapture creates CAPTURES.md on first call", (t) => { test("captures: appendCapture appends to existing file", (t) => { const tmp = makeTempDir("cap-append"); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); const id1 = appendCapture(tmp, "thought one"); const id2 = appendCapture(tmp, "thought two"); @@ -87,7 +87,7 @@ test("captures: appendCapture appends to existing file", (t) => { test("captures: loadAllCaptures parses entries correctly", (t) => { const tmp = makeTempDir("cap-load"); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); appendCapture(tmp, "alpha"); appendCapture(tmp, "beta"); @@ -102,7 +102,7 @@ test("captures: loadAllCaptures parses entries correctly", (t) => { test("captures: loadAllCaptures returns empty array when no file", (t) => { const tmp = makeTempDir("cap-nofile"); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); const all = loadAllCaptures(tmp); assert.strictEqual(all.length, 0); @@ -110,7 +110,7 @@ test("captures: loadAllCaptures returns empty array when no file", (t) => { test("captures: loadPendingCaptures filters resolved entries", (t) => { const tmp = makeTempDir("cap-pending"); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); const id1 = appendCapture(tmp, "pending one"); appendCapture(tmp, "pending two"); @@ -124,7 +124,7 @@ test("captures: loadPendingCaptures filters resolved entries", (t) => { test("captures: loadAllCaptures preserves resolved entries", (t) => { const tmp = makeTempDir("cap-all-resolved"); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); const id1 = appendCapture(tmp, "pending one"); appendCapture(tmp, "pending two"); @@ -141,14 +141,14 @@ test("captures: loadAllCaptures preserves resolved entries", (t) => { test("captures: hasPendingCaptures returns false when no file", (t) => { const tmp = makeTempDir("cap-has-nofile"); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); assert.strictEqual(hasPendingCaptures(tmp), false); }); test("captures: hasPendingCaptures returns true with pending entries", (t) => { const tmp = makeTempDir("cap-has-true"); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); appendCapture(tmp, "something"); assert.strictEqual(hasPendingCaptures(tmp), true); @@ -156,7 +156,7 @@ test("captures: hasPendingCaptures returns true with pending entries", (t) => { test("captures: hasPendingCaptures returns false when all resolved", (t) => { const tmp = makeTempDir("cap-has-false"); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); const id = appendCapture(tmp, "will resolve"); markCaptureResolved(tmp, id, "note", "done", "resolved it"); @@ -167,7 +167,7 @@ test("captures: hasPendingCaptures returns false when all resolved", (t) => { test("captures: markCaptureResolved updates entry in place", (t) => { const tmp = makeTempDir("cap-resolve"); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); const id1 = appendCapture(tmp, "keep pending"); const id2 = appendCapture(tmp, "will resolve"); @@ -350,7 +350,7 @@ test("triage: parseTriageOutput handles all five classification types", () => { test("captures: appendCapture handles special characters in text", (t) => { const tmp = makeTempDir("cap-special"); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); const _id = appendCapture(tmp, 'text with "quotes" and **bold** and `code`'); const all = loadAllCaptures(tmp); @@ -361,7 +361,7 @@ test("captures: appendCapture handles special characters in text", (t) => { test("captures: markCaptureResolved is no-op for non-existent ID", (t) => { const tmp = makeTempDir("cap-noop"); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); appendCapture(tmp, "real capture"); // Should not throw @@ -373,7 +373,7 @@ test("captures: markCaptureResolved is no-op for non-existent ID", (t) => { test("captures: markCaptureResolved is no-op when no file exists", (t) => { const tmp = makeTempDir("cap-nofile-resolve"); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); // Should not throw markCaptureResolved(tmp, "CAP-abc", "note", "test", "test"); @@ -381,7 +381,7 @@ test("captures: markCaptureResolved is no-op when no file exists", (t) => { test("captures: re-resolving a capture overwrites previous resolution", (t) => { const tmp = makeTempDir("cap-reresolve"); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); const id = appendCapture(tmp, "will re-resolve"); markCaptureResolved(tmp, id, "note", "first resolution", "first rationale"); @@ -431,7 +431,7 @@ test("triage: parseTriageOutput preserves affectedFiles and targetSlice", () => test("captures: markCaptureResolved stores milestone ID when provided", (t) => { const tmp = makeTempDir("cap-milestone"); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); const id = appendCapture(tmp, "fix dialog width"); markCaptureResolved( @@ -454,7 +454,7 @@ test("captures: markCaptureResolved stores milestone ID when provided", (t) => { test("captures: loadActionableCaptures excludes captures resolved in prior milestones", (t) => { const tmp = makeTempDir("cap-stale-filter"); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); // Capture resolved in M003 (prior milestone) const id1 = appendCapture(tmp, "dialog too narrow"); @@ -489,7 +489,7 @@ test("captures: loadActionableCaptures excludes captures resolved in prior miles test("captures: loadActionableCaptures without milestone returns all actionable", (t) => { const tmp = makeTempDir("cap-no-milestone-filter"); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); const id1 = appendCapture(tmp, "issue one"); markCaptureResolved(tmp, id1, "quick-task", "fix it", "small", "M003"); @@ -508,7 +508,7 @@ test("captures: loadActionableCaptures without milestone returns all actionable" test("captures: loadActionableCaptures excludes already-executed captures", (t) => { const tmp = makeTempDir("cap-executed-filter"); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); const id1 = appendCapture(tmp, "already done"); markCaptureResolved(tmp, id1, "quick-task", "fix it", "small", "M004"); @@ -524,7 +524,7 @@ test("captures: loadActionableCaptures excludes already-executed captures", (t) test("captures: stampCaptureMilestone adds milestone to capture missing it", (t) => { const tmp = makeTempDir("cap-stamp-milestone"); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); const id = appendCapture(tmp, "fix alignment"); markCaptureResolved(tmp, id, "quick-task", "fix it", "small"); @@ -549,7 +549,7 @@ test("captures: stampCaptureMilestone adds milestone to capture missing it", (t) test("captures: stampCaptureMilestone is no-op if milestone already present", (t) => { const tmp = makeTempDir("cap-stamp-noop"); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); const id = appendCapture(tmp, "fix alignment"); markCaptureResolved(tmp, id, "quick-task", "fix it", "small", "M003"); diff --git a/src/resources/extensions/sf/tests/claude-import-tui.test.ts b/src/resources/extensions/sf/tests/claude-import-tui.test.ts index c47744364..51c3a02b5 100644 --- a/src/resources/extensions/sf/tests/claude-import-tui.test.ts +++ b/src/resources/extensions/sf/tests/claude-import-tui.test.ts @@ -19,7 +19,7 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { after, before, describe, it, vi } from 'vitest'; +import { after, before, describe, it, vi, afterEach } from 'vitest'; import type { ExtensionCommandContext } from "@singularity-forge/pi-coding-agent"; import { discoverClaudePlugins, @@ -329,7 +329,7 @@ describe("TUI Command Flow Tests", { skip: skipReason }, () => { rmSync(isolatedAgentDir, { recursive: true, force: true }); process.env.SF_CODING_AGENT_DIR = isolatedAgentDir; - t.after(() => { + afterEach(() => { delete process.env.SF_CODING_AGENT_DIR; rmSync(isolatedAgentDir, { recursive: true, force: true }); }); diff --git a/src/resources/extensions/sf/tests/collect-from-manifest.test.ts b/src/resources/extensions/sf/tests/collect-from-manifest.test.ts index ad56af66f..aedb0d6ca 100644 --- a/src/resources/extensions/sf/tests/collect-from-manifest.test.ts +++ b/src/resources/extensions/sf/tests/collect-from-manifest.test.ts @@ -15,7 +15,7 @@ import assert from "node:assert/strict"; import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import type { SecretsManifest, SecretsManifestEntry } from "../types.ts"; // Dynamic imports for files.ts functions to avoid cascading failure @@ -112,7 +112,7 @@ test("collectSecretsFromManifest: categorizes entries — pending keys need coll const tmp = makeTempDir("manifest-collect"); const savedA = process.env.EXISTING_KEY_A; - t.after(() => { + afterEach(() => { delete process.env.EXISTING_KEY_A; if (savedA !== undefined) process.env.EXISTING_KEY_A = savedA; rmSync(tmp, { recursive: true, force: true }); @@ -170,7 +170,7 @@ test("collectSecretsFromManifest: existing keys are excluded from the collection const tmp = makeTempDir("manifest-collect-skip"); const savedA = process.env.ALREADY_SET_KEY; - t.after(() => { + afterEach(() => { delete process.env.ALREADY_SET_KEY; if (savedA !== undefined) process.env.ALREADY_SET_KEY = savedA; rmSync(tmp, { recursive: true, force: true }); @@ -219,7 +219,7 @@ test("collectSecretsFromManifest: manifest statuses are updated after collection const { collectSecretsFromManifest } = await loadOrchestrator(); const tmp = makeTempDir("manifest-update"); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); const manifest = makeManifest([ { key: "KEY_TO_COLLECT", status: "pending" }, @@ -273,7 +273,7 @@ test("collectSecretsFromManifest: applied keys hydrate process.env for the runni const tmp = makeTempDir("manifest-live-env"); const envKey = "CONTEXT7_API_KEY"; const saved = process.env[envKey]; - t.after(() => { + afterEach(() => { if (saved === undefined) delete process.env[envKey]; else process.env[envKey] = saved; rmSync(tmp, { recursive: true, force: true }); @@ -606,7 +606,7 @@ test("collectSecretsFromManifest: returns result with applied, skipped, and exis const tmp = makeTempDir("manifest-result"); const savedKey = process.env.RESULT_TEST_EXISTING; - t.after(() => { + afterEach(() => { delete process.env.RESULT_TEST_EXISTING; if (savedKey !== undefined) process.env.RESULT_TEST_EXISTING = savedKey; rmSync(tmp, { recursive: true, force: true }); diff --git a/src/resources/extensions/sf/tests/commands-inspect-open-db.test.ts b/src/resources/extensions/sf/tests/commands-inspect-open-db.test.ts index 863b3e7b0..44823c0b3 100644 --- a/src/resources/extensions/sf/tests/commands-inspect-open-db.test.ts +++ b/src/resources/extensions/sf/tests/commands-inspect-open-db.test.ts @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { handleInspect } from "../commands-inspect.ts"; import { closeDatabase, openDatabase } from "../sf-db.ts"; @@ -13,7 +13,7 @@ test("/sf inspect opens existing database when it was not yet opened in session" const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "sf-inspect-db-")); const prevCwd = process.cwd(); - t.after(() => { + afterEach(() => { process.chdir(prevCwd); closeDatabase(); fs.rmSync(tmp, { recursive: true, force: true }); diff --git a/src/resources/extensions/sf/tests/commands-logs.test.ts b/src/resources/extensions/sf/tests/commands-logs.test.ts index 743b07021..45581664f 100644 --- a/src/resources/extensions/sf/tests/commands-logs.test.ts +++ b/src/resources/extensions/sf/tests/commands-logs.test.ts @@ -9,7 +9,7 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { handleLogs } from "../commands-logs.ts"; @@ -69,7 +69,7 @@ test("logs shows empty state message when no logs exist", async (t) => { const ctx = createMockCtx(); const origCwd = process.cwd(); process.chdir(dir); - t.after(() => { + afterEach(() => { process.chdir(origCwd); rmSync(dir, { recursive: true, force: true }); }); @@ -93,7 +93,7 @@ test("logs lists activity logs", async (t) => { { role: "assistant", content: "Completing slice S01" }, ]); - t.after(() => { + afterEach(() => { process.chdir(origCwd); rmSync(dir, { recursive: true, force: true }); }); @@ -125,7 +125,7 @@ test("logs shows activity log details", async (t) => { { role: "assistant", content: "I ran the tests and wrote a file" }, ]); - t.after(() => { + afterEach(() => { process.chdir(origCwd); rmSync(dir, { recursive: true, force: true }); }); @@ -147,7 +147,7 @@ test("logs shows not found for invalid seq", async (t) => { const origCwd = process.cwd(); process.chdir(dir); - t.after(() => { + afterEach(() => { process.chdir(origCwd); rmSync(dir, { recursive: true, force: true }); }); @@ -169,7 +169,7 @@ test("logs debug lists debug logs", async (t) => { { ts: "2026-03-18T10:35:00Z", event: "debug-summary", dispatches: 5 }, ]); - t.after(() => { + afterEach(() => { process.chdir(origCwd); rmSync(dir, { recursive: true, force: true }); }); @@ -200,7 +200,7 @@ test("logs debug shows debug log summary", async (t) => { { ts: "2026-03-18T10:35:00Z", event: "debug-summary", dispatches: 5 }, ]); - t.after(() => { + afterEach(() => { process.chdir(origCwd); rmSync(dir, { recursive: true, force: true }); }); @@ -228,7 +228,7 @@ test("logs tail shows recent activity summaries", async (t) => { { role: "toolResult", toolCallId: "1", toolName: "bash", isError: true }, ]); - t.after(() => { + afterEach(() => { process.chdir(origCwd); rmSync(dir, { recursive: true, force: true }); }); @@ -267,7 +267,7 @@ test("logs clear removes old logs", async (t) => { ]); } - t.after(() => { + afterEach(() => { process.chdir(origCwd); rmSync(dir, { recursive: true, force: true }); }); diff --git a/src/resources/extensions/sf/tests/complete-slice-composer.test.ts b/src/resources/extensions/sf/tests/complete-slice-composer.test.ts index cd360c914..68cd3fca1 100644 --- a/src/resources/extensions/sf/tests/complete-slice-composer.test.ts +++ b/src/resources/extensions/sf/tests/complete-slice-composer.test.ts @@ -4,7 +4,7 @@ import assert from "node:assert/strict"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { buildCompleteSlicePrompt } from "../auto-prompts.ts"; import { invalidateAllCaches } from "../cache.ts"; @@ -104,7 +104,7 @@ function writeArtifacts(base: string): void { test("#4782 phase 3: buildCompleteSlicePrompt composes roadmap → plan → task summaries → templates in declared order", async (t) => { const base = makeBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); invalidateAllCaches(); seed(base, "M001"); @@ -157,7 +157,7 @@ test("#4782 phase 3: buildCompleteSlicePrompt composes roadmap → plan → task test("#4782 phase 3: buildCompleteSlicePrompt handles missing task summaries gracefully", async (t) => { const base = makeBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); invalidateAllCaches(); seed(base, "M001"); diff --git a/src/resources/extensions/sf/tests/complete-slice-skipped-tasks.test.ts b/src/resources/extensions/sf/tests/complete-slice-skipped-tasks.test.ts index b9017748b..dd64a1ba5 100644 --- a/src/resources/extensions/sf/tests/complete-slice-skipped-tasks.test.ts +++ b/src/resources/extensions/sf/tests/complete-slice-skipped-tasks.test.ts @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { buildCompleteSlicePrompt } from "../auto-prompts.ts"; import { closeDatabase, @@ -14,7 +14,7 @@ import { test("complete-slice prompt treats skipped tasks as non-executable gaps", async (t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-complete-skip-")); - t.after(() => { + afterEach(() => { closeDatabase(); rmSync(tmp, { recursive: true, force: true }); }); diff --git a/src/resources/extensions/sf/tests/crash-recovery.test.ts b/src/resources/extensions/sf/tests/crash-recovery.test.ts index 21819ec71..2444805ec 100644 --- a/src/resources/extensions/sf/tests/crash-recovery.test.ts +++ b/src/resources/extensions/sf/tests/crash-recovery.test.ts @@ -3,7 +3,7 @@ import { randomUUID } from "node:crypto"; import { mkdirSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { clearLock, @@ -449,7 +449,7 @@ test("assessInterruptedSession treats bootstrap crash as stale without paused me test("writeLock creates lock file and readCrashLock reads it", (t) => { const base = makeTmpBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); writeLock(base, "execute-task", "M001/S01/T01", "/tmp/session.jsonl"); const lock = readCrashLock(base); @@ -462,7 +462,7 @@ test("writeLock creates lock file and readCrashLock reads it", (t) => { test("readCrashLock returns null when no lock exists", (t) => { const base = makeTmpBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); const lock = readCrashLock(base); assert.equal(lock, null); @@ -472,7 +472,7 @@ test("readCrashLock returns null when no lock exists", (t) => { test("clearLock removes existing lock file", (t) => { const base = makeTmpBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); writeLock(base, "plan-slice", "M001/S01"); assert.ok(readCrashLock(base), "lock should exist before clear"); @@ -482,7 +482,7 @@ test("clearLock removes existing lock file", (t) => { test("clearLock is safe when no lock exists", (t) => { const base = makeTmpBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); assert.doesNotThrow(() => clearLock(base)); }); diff --git a/src/resources/extensions/sf/tests/definition-loader.test.ts b/src/resources/extensions/sf/tests/definition-loader.test.ts index fed8fca3a..32b5e26be 100644 --- a/src/resources/extensions/sf/tests/definition-loader.test.ts +++ b/src/resources/extensions/sf/tests/definition-loader.test.ts @@ -11,7 +11,7 @@ import assert from "node:assert/strict"; import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import type { WorkflowDefinition } from "../definition-loader.ts"; import { loadDefinition, @@ -64,7 +64,7 @@ steps: test("loadDefinition: valid 3-step YAML returns correct structure", (t) => { const dir = writeDefYaml(VALID_3STEP_YAML); - t.after(() => { + afterEach(() => { try { rmSync(dir, { recursive: true, @@ -255,7 +255,7 @@ test("validateDefinition: missing step name → error", () => { test("loadDefinition: missing file → descriptive error", (t) => { const dir = makeTmpDir(); - t.after(() => { + afterEach(() => { try { rmSync(dir, { recursive: true, @@ -287,7 +287,7 @@ steps: name: "A" prompt: "do A" `); - t.after(() => { + afterEach(() => { try { rmSync(dir, { recursive: true, @@ -325,7 +325,7 @@ steps: prompt: "do second" depends_on: [first] `); - t.after(() => { + afterEach(() => { try { rmSync(dir, { recursive: true, @@ -355,7 +355,7 @@ steps: prompt: "do second" context_from: [first] `); - t.after(() => { + afterEach(() => { try { rmSync(dir, { recursive: true, @@ -876,7 +876,7 @@ steps: name: "A" prompt: "do A" `); - t.after(() => { + afterEach(() => { try { rmSync(dir, { recursive: true, @@ -902,7 +902,7 @@ steps: name: "A" prompt: "do A" `); - t.after(() => { + afterEach(() => { try { rmSync(dir, { recursive: true, @@ -928,7 +928,7 @@ steps: name: "A" prompt: "do A" `); - t.after(() => { + afterEach(() => { try { rmSync(dir, { recursive: true, diff --git a/src/resources/extensions/sf/tests/detection.test.ts b/src/resources/extensions/sf/tests/detection.test.ts index c9cd6f738..2f5b42d96 100644 --- a/src/resources/extensions/sf/tests/detection.test.ts +++ b/src/resources/extensions/sf/tests/detection.test.ts @@ -12,7 +12,7 @@ import assert from "node:assert/strict"; import { mkdirSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { detectProjectSignals, detectProjectState, @@ -41,7 +41,7 @@ function cleanup(dir: string): void { test("detectProjectState: empty directory returns state=none", (t) => { const dir = makeTempDir("empty"); - t.after(() => cleanup(dir)); + afterEach(() => cleanup(dir)); const result = detectProjectState(dir); assert.equal(result.state, "none"); @@ -51,7 +51,7 @@ test("detectProjectState: empty directory returns state=none", (t) => { test("detectProjectState: directory with .sf/milestones/M001 returns v2-sf", (t) => { const dir = makeTempDir("v2-sf"); - t.after(() => cleanup(dir)); + afterEach(() => cleanup(dir)); mkdirSync(join(dir, ".sf", "milestones", "M001"), { recursive: true }); const result = detectProjectState(dir); @@ -62,7 +62,7 @@ test("detectProjectState: directory with .sf/milestones/M001 returns v2-sf", (t) test("detectProjectState: directory with empty .sf/milestones returns v2-sf-empty", (t) => { const dir = makeTempDir("v2-empty"); - t.after(() => cleanup(dir)); + afterEach(() => cleanup(dir)); mkdirSync(join(dir, ".sf", "milestones"), { recursive: true }); const result = detectProjectState(dir); @@ -73,7 +73,7 @@ test("detectProjectState: directory with empty .sf/milestones returns v2-sf-empt test("detectProjectState: directory with .planning/ returns v1-planning", (t) => { const dir = makeTempDir("v1-planning"); - t.after(() => cleanup(dir)); + afterEach(() => cleanup(dir)); mkdirSync(join(dir, ".planning", "phases", "01-setup"), { recursive: true }); writeFileSync(join(dir, ".planning", "ROADMAP.md"), "# Roadmap\n", "utf-8"); @@ -87,7 +87,7 @@ test("detectProjectState: directory with .planning/ returns v1-planning", (t) => test("detectProjectState: v2 takes priority over v1 when both exist", (t) => { const dir = makeTempDir("both"); - t.after(() => cleanup(dir)); + afterEach(() => cleanup(dir)); mkdirSync(join(dir, ".sf", "milestones", "M001"), { recursive: true }); mkdirSync(join(dir, ".planning"), { recursive: true }); @@ -97,7 +97,7 @@ test("detectProjectState: v2 takes priority over v1 when both exist", (t) => { test("detectProjectState: detects preferences in .sf/", (t) => { const dir = makeTempDir("prefs"); - t.after(() => cleanup(dir)); + afterEach(() => cleanup(dir)); mkdirSync(join(dir, ".sf", "milestones"), { recursive: true }); writeFileSync( @@ -114,14 +114,14 @@ test("detectProjectState: detects preferences in .sf/", (t) => { test("detectV1Planning: returns null for missing .planning/", (t) => { const dir = makeTempDir("no-v1"); - t.after(() => cleanup(dir)); + afterEach(() => cleanup(dir)); assert.equal(detectV1Planning(dir), null); }); test("detectV1Planning: returns null when .planning is a file", (t) => { const dir = makeTempDir("v1-file"); - t.after(() => cleanup(dir)); + afterEach(() => cleanup(dir)); writeFileSync(join(dir, ".planning"), "not a directory", "utf-8"); assert.equal(detectV1Planning(dir), null); @@ -129,7 +129,7 @@ test("detectV1Planning: returns null when .planning is a file", (t) => { test("detectV1Planning: detects phases directory with multiple phases", (t) => { const dir = makeTempDir("v1-phases"); - t.after(() => cleanup(dir)); + afterEach(() => cleanup(dir)); mkdirSync(join(dir, ".planning", "phases", "01-setup"), { recursive: true }); mkdirSync(join(dir, ".planning", "phases", "02-core"), { recursive: true }); @@ -142,7 +142,7 @@ test("detectV1Planning: detects phases directory with multiple phases", (t) => { test("detectV1Planning: detects ROADMAP.md", (t) => { const dir = makeTempDir("v1-roadmap"); - t.after(() => cleanup(dir)); + afterEach(() => cleanup(dir)); mkdirSync(join(dir, ".planning"), { recursive: true }); writeFileSync(join(dir, ".planning", "ROADMAP.md"), "# Roadmap", "utf-8"); @@ -157,7 +157,7 @@ test("detectV1Planning: detects ROADMAP.md", (t) => { test("detectProjectSignals: empty directory", (t) => { const dir = makeTempDir("signals-empty"); - t.after(() => cleanup(dir)); + afterEach(() => cleanup(dir)); const signals = detectProjectSignals(dir); assert.deepEqual(signals.detectedFiles, []); @@ -171,7 +171,7 @@ test("detectProjectSignals: empty directory", (t) => { test("detectProjectSignals: Node.js project", (t) => { const dir = makeTempDir("signals-node"); - t.after(() => cleanup(dir)); + afterEach(() => cleanup(dir)); writeFileSync( join(dir, "package.json"), @@ -204,7 +204,7 @@ test("detectProjectSignals: Node.js project", (t) => { test("detectProjectSignals: Rust project", (t) => { const dir = makeTempDir("signals-rust"); - t.after(() => cleanup(dir)); + afterEach(() => cleanup(dir)); writeFileSync(join(dir, "Cargo.toml"), '[package]\nname = "test"\n', "utf-8"); const signals = detectProjectSignals(dir); @@ -218,7 +218,7 @@ test("detectProjectSignals: Rust project", (t) => { test("detectProjectSignals: Go project", (t) => { const dir = makeTempDir("signals-go"); - t.after(() => cleanup(dir)); + afterEach(() => cleanup(dir)); writeFileSync(join(dir, "go.mod"), "module example.com/test\n", "utf-8"); const signals = detectProjectSignals(dir); @@ -229,7 +229,7 @@ test("detectProjectSignals: Go project", (t) => { test("detectProjectSignals: Python project", (t) => { const dir = makeTempDir("signals-python"); - t.after(() => cleanup(dir)); + afterEach(() => cleanup(dir)); writeFileSync(join(dir, "pyproject.toml"), "[tool.poetry]\n", "utf-8"); const signals = detectProjectSignals(dir); @@ -240,7 +240,7 @@ test("detectProjectSignals: Python project", (t) => { test("detectProjectSignals: monorepo detection via workspaces", (t) => { const dir = makeTempDir("signals-monorepo"); - t.after(() => cleanup(dir)); + afterEach(() => cleanup(dir)); writeFileSync( join(dir, "package.json"), @@ -253,7 +253,7 @@ test("detectProjectSignals: monorepo detection via workspaces", (t) => { test("detectProjectSignals: monorepo detection via turbo.json", (t) => { const dir = makeTempDir("signals-turbo"); - t.after(() => cleanup(dir)); + afterEach(() => cleanup(dir)); writeFileSync( join(dir, "package.json"), @@ -267,7 +267,7 @@ test("detectProjectSignals: monorepo detection via turbo.json", (t) => { test("detectProjectSignals: CI detection", (t) => { const dir = makeTempDir("signals-ci"); - t.after(() => cleanup(dir)); + afterEach(() => cleanup(dir)); mkdirSync(join(dir, ".github", "workflows"), { recursive: true }); const signals = detectProjectSignals(dir); @@ -276,7 +276,7 @@ test("detectProjectSignals: CI detection", (t) => { test("detectProjectSignals: test detection via jest config", (t) => { const dir = makeTempDir("signals-tests"); - t.after(() => cleanup(dir)); + afterEach(() => cleanup(dir)); writeFileSync(join(dir, "jest.config.ts"), "export default {}", "utf-8"); const signals = detectProjectSignals(dir); @@ -287,7 +287,7 @@ test("detectProjectSignals: package manager detection", (t) => { const dir1 = makeTempDir("pm-pnpm"); const dir2 = makeTempDir("pm-yarn"); const dir3 = makeTempDir("pm-bun"); - t.after(() => { + afterEach(() => { cleanup(dir1); cleanup(dir2); cleanup(dir3); @@ -308,7 +308,7 @@ test("detectProjectSignals: package manager detection", (t) => { test("detectProjectSignals: packageManager field overrides lockfile heuristic", (t) => { const dir = makeTempDir("pm-declared-npm"); - t.after(() => cleanup(dir)); + afterEach(() => cleanup(dir)); writeFileSync(join(dir, "bun.lockb"), "", "utf-8"); writeFileSync( @@ -322,7 +322,7 @@ test("detectProjectSignals: packageManager field overrides lockfile heuristic", test("detectProjectSignals: bun packageManager is normalized to npm for verification", (t) => { const dir = makeTempDir("pm-declared-bun"); - t.after(() => cleanup(dir)); + afterEach(() => cleanup(dir)); writeFileSync( join(dir, "package.json"), @@ -341,7 +341,7 @@ test("detectProjectSignals: bun packageManager is normalized to npm for verifica test("detectProjectSignals: skips default npm test script", (t) => { const dir = makeTempDir("signals-default-test"); - t.after(() => cleanup(dir)); + afterEach(() => cleanup(dir)); writeFileSync( join(dir, "package.json"), @@ -361,7 +361,7 @@ test("detectProjectSignals: skips default npm test script", (t) => { test("detectProjectSignals: pnpm uses pnpm commands", (t) => { const dir = makeTempDir("signals-pnpm-cmds"); - t.after(() => cleanup(dir)); + afterEach(() => cleanup(dir)); writeFileSync( join(dir, "package.json"), @@ -379,7 +379,7 @@ test("detectProjectSignals: pnpm uses pnpm commands", (t) => { test("detectProjectSignals: Ruby project with rspec", (t) => { const dir = makeTempDir("signals-ruby"); - t.after(() => cleanup(dir)); + afterEach(() => cleanup(dir)); writeFileSync( join(dir, "Gemfile"), @@ -395,7 +395,7 @@ test("detectProjectSignals: Ruby project with rspec", (t) => { test("detectProjectSignals: Makefile with test target", (t) => { const dir = makeTempDir("signals-make"); - t.after(() => cleanup(dir)); + afterEach(() => cleanup(dir)); writeFileSync( join(dir, "Makefile"), diff --git a/src/resources/extensions/sf/tests/dev-engine-wrapper.test.ts b/src/resources/extensions/sf/tests/dev-engine-wrapper.test.ts index 2a9fb4421..2ed0fb3be 100644 --- a/src/resources/extensions/sf/tests/dev-engine-wrapper.test.ts +++ b/src/resources/extensions/sf/tests/dev-engine-wrapper.test.ts @@ -10,7 +10,7 @@ import assert from "node:assert/strict"; import { mkdirSync, mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test, after, describe } from 'vitest'; +import { test, after, describe, afterEach } from 'vitest'; // ── bridgeDispatchAction mapping ──────────────────────────────────────────── @@ -75,7 +75,7 @@ describe("DevWorkflowEngine", () => { const tempDir = mkdtempSync(join(tmpdir(), "sf-engine-test-")); mkdirSync(join(tempDir, ".sf", "milestones"), { recursive: true }); - t.after(() => rmSync(tempDir, { recursive: true, force: true })); + afterEach(() => rmSync(tempDir, { recursive: true, force: true })); const state = await engine.deriveState(tempDir); @@ -259,7 +259,7 @@ describe("Kill switch (SF_ENGINE_BYPASS)", () => { test("SF_ENGINE_BYPASS=1 does not affect resolveEngine (bypass checked in autoLoop)", async (t) => { const { resolveEngine } = await import("../engine-resolver.ts"); process.env.SF_ENGINE_BYPASS = "1"; - t.after(() => delete process.env.SF_ENGINE_BYPASS); + afterEach(() => delete process.env.SF_ENGINE_BYPASS); // resolveEngine should still resolve normally — bypass is checked in autoLoop const { engine } = resolveEngine({ activeEngineId: null }); diff --git a/src/resources/extensions/sf/tests/dispatch-guard.test.ts b/src/resources/extensions/sf/tests/dispatch-guard.test.ts index 3cbdbe049..6ed5e89f4 100644 --- a/src/resources/extensions/sf/tests/dispatch-guard.test.ts +++ b/src/resources/extensions/sf/tests/dispatch-guard.test.ts @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { getPriorSliceCompletionBlocker } from "../dispatch-guard.ts"; import { closeDatabase, @@ -27,7 +27,7 @@ function teardownRepo(repo: string): void { test("dispatch guard blocks when prior milestone has incomplete slices", (t) => { const repo = setupRepo(); - t.after(() => teardownRepo(repo)); + afterEach(() => teardownRepo(repo)); mkdirSync(join(repo, ".sf", "milestones", "M002"), { recursive: true }); mkdirSync(join(repo, ".sf", "milestones", "M003"), { recursive: true }); @@ -88,7 +88,7 @@ test("dispatch guard blocks when prior milestone has incomplete slices", (t) => test("dispatch guard blocks later slice in same milestone when earlier incomplete", (t) => { const repo = setupRepo(); - t.after(() => teardownRepo(repo)); + afterEach(() => teardownRepo(repo)); mkdirSync(join(repo, ".sf", "milestones", "M002"), { recursive: true }); mkdirSync(join(repo, ".sf", "milestones", "M003"), { recursive: true }); @@ -151,7 +151,7 @@ test("dispatch guard blocks later slice in same milestone when earlier incomplet test("dispatch guard allows dispatch when all earlier slices complete", (t) => { const repo = setupRepo(); - t.after(() => teardownRepo(repo)); + afterEach(() => teardownRepo(repo)); mkdirSync(join(repo, ".sf", "milestones", "M003"), { recursive: true }); @@ -198,7 +198,7 @@ test("dispatch guard unblocks slice when positionally-earlier slice depends on i // Old behavior: S06 blocked because S05 (positionally earlier) is incomplete. // Fixed behavior: S06 has no unmet dependencies, so it can dispatch. const repo = setupRepo(); - t.after(() => teardownRepo(repo)); + afterEach(() => teardownRepo(repo)); mkdirSync(join(repo, ".sf", "milestones", "M001"), { recursive: true }); @@ -272,7 +272,7 @@ test("dispatch guard unblocks slice when positionally-earlier slice depends on i test("dispatch guard falls back to positional ordering when no dependencies declared", (t) => { const repo = setupRepo(); - t.after(() => teardownRepo(repo)); + afterEach(() => teardownRepo(repo)); mkdirSync(join(repo, ".sf", "milestones", "M001"), { recursive: true }); @@ -322,7 +322,7 @@ test("dispatch guard falls back to positional ordering when no dependencies decl test("dispatch guard ignores positionally-earlier reverse dependents for zero-dependency slices (#3720)", (t) => { const repo = setupRepo(); - t.after(() => teardownRepo(repo)); + afterEach(() => teardownRepo(repo)); mkdirSync(join(repo, ".sf", "milestones", "M015"), { recursive: true }); @@ -383,7 +383,7 @@ test("dispatch guard ignores positionally-earlier reverse dependents for zero-de test("dispatch guard treats zero-dependency slices as independent when a milestone uses explicit deps (#3998)", (t) => { const repo = setupRepo(); - t.after(() => teardownRepo(repo)); + afterEach(() => teardownRepo(repo)); mkdirSync(join(repo, ".sf", "milestones", "M022"), { recursive: true }); @@ -457,7 +457,7 @@ test("dispatch guard treats zero-dependency slices as independent when a milesto test("dispatch guard allows slice with all declared dependencies complete", (t) => { const repo = setupRepo(); - t.after(() => teardownRepo(repo)); + afterEach(() => teardownRepo(repo)); mkdirSync(join(repo, ".sf", "milestones", "M001"), { recursive: true }); @@ -515,7 +515,7 @@ test("dispatch guard allows slice with all declared dependencies complete", (t) test("dispatch guard skips completed milestone with SUMMARY even if it has unchecked remediation slices (#1716)", (t) => { const repo = setupRepo(); - t.after(() => teardownRepo(repo)); + afterEach(() => teardownRepo(repo)); mkdirSync(join(repo, ".sf", "milestones", "M001"), { recursive: true }); mkdirSync(join(repo, ".sf", "milestones", "M002"), { recursive: true }); @@ -588,7 +588,7 @@ test("dispatch guard skips completed milestone with SUMMARY even if it has unche test("dispatch guard works without git repo", (t) => { const repo = setupRepo(); - t.after(() => teardownRepo(repo)); + afterEach(() => teardownRepo(repo)); mkdirSync(join(repo, ".sf", "milestones", "M001"), { recursive: true }); @@ -623,7 +623,7 @@ test("dispatch guard works without git repo", (t) => { test("dispatch guard skips cross-milestone check when SF_MILESTONE_LOCK is set (#2797)", (t) => { const repo = setupRepo(); - t.after(() => { + afterEach(() => { delete process.env.SF_MILESTONE_LOCK; teardownRepo(repo); }); diff --git a/src/resources/extensions/sf/tests/dispatch-missing-task-plans.test.ts b/src/resources/extensions/sf/tests/dispatch-missing-task-plans.test.ts index 93a1ebe22..53b3744e9 100644 --- a/src/resources/extensions/sf/tests/dispatch-missing-task-plans.test.ts +++ b/src/resources/extensions/sf/tests/dispatch-missing-task-plans.test.ts @@ -13,7 +13,7 @@ import assert from "node:assert/strict"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import type { DispatchContext } from "../auto-dispatch.ts"; import { resolveDispatch } from "../auto-dispatch.ts"; import { @@ -97,7 +97,7 @@ function scaffoldTaskPlan( test("dispatch: missing task plan triggers plan-slice (not stop) — issue #909", async (t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-909-")); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); // Slice plan exists with tasks, but tasks/ directory is empty scaffoldSlicePlan(tmp, "M002", "S03"); @@ -118,7 +118,7 @@ test("dispatch: missing task plan triggers plan-slice (not stop) — issue #909" test("dispatch: present task plan proceeds to execute-task normally", async (t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-909-ok-")); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); scaffoldSlicePlan(tmp, "M002", "S03"); scaffoldTaskPlan(tmp, "M002", "S03", "T01"); @@ -139,7 +139,7 @@ test("dispatch: present task plan proceeds to execute-task normally", async (t) test("dispatch: stale Docker Compose staging task is auto-skipped when DB can advance state", async (t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-stale-staging-")); - t.after(() => { + afterEach(() => { closeDatabase(); rmSync(tmp, { recursive: true, force: true }); }); @@ -208,7 +208,7 @@ test("dispatch: plan-slice recovery loop — second call after plan-slice still // Simulate: plan-slice ran but T01-PLAN.md is still missing (e.g. agent crashed mid-write). // Dispatch should still re-dispatch plan-slice, not hard-stop. const tmp = mkdtempSync(join(tmpdir(), "sf-909-loop-")); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); scaffoldSlicePlan(tmp, "M002", "S03"); diff --git a/src/resources/extensions/sf/tests/dispatch-uat-last-completed.test.ts b/src/resources/extensions/sf/tests/dispatch-uat-last-completed.test.ts index 2c2d54141..8ac84d7ca 100644 --- a/src/resources/extensions/sf/tests/dispatch-uat-last-completed.test.ts +++ b/src/resources/extensions/sf/tests/dispatch-uat-last-completed.test.ts @@ -6,7 +6,7 @@ import assert from "node:assert/strict"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { dispatchDirectPhase } from "../auto-direct-dispatch.ts"; import { invalidateStateCache } from "../state.ts"; @@ -91,7 +91,7 @@ test("dispatch uat targets last completed slice, not activeSlice (#1693)", async }, } as any; - t.after(() => rmSync(base, { recursive: true, force: true })); + afterEach(() => rmSync(base, { recursive: true, force: true })); await dispatchDirectPhase(ctx, pi, "uat", base); @@ -167,7 +167,7 @@ test("dispatch uat warns when no completed slices exist", async (t) => { }, } as any; - t.after(() => rmSync(base, { recursive: true, force: true })); + afterEach(() => rmSync(base, { recursive: true, force: true })); await dispatchDirectPhase(ctx, pi, "uat", base); diff --git a/src/resources/extensions/sf/tests/dist-redirect.mjs b/src/resources/extensions/sf/tests/dist-redirect.mjs index ccb2dfd30..469bf6af3 100644 --- a/src/resources/extensions/sf/tests/dist-redirect.mjs +++ b/src/resources/extensions/sf/tests/dist-redirect.mjs @@ -13,6 +13,8 @@ export function resolve(specifier, context, nextResolve) { // source itself) must resolve to the TypeScript source entrypoint. if (specifier === "../../packages/pi-coding-agent/src/index.js") { specifier = new URL("packages/pi-coding-agent/src/index.ts", ROOT).href; + } else if (specifier === "vitest") { + specifier = "node:test"; } else if (specifier === "@singularity-forge/pi-coding-agent") { specifier = new URL("packages/pi-coding-agent/src/index.ts", ROOT).href; } else if (specifier === "@singularity-forge/pi-ai/oauth") { diff --git a/src/resources/extensions/sf/tests/doctor-scope-db-unavailable.test.ts b/src/resources/extensions/sf/tests/doctor-scope-db-unavailable.test.ts index 951096d64..7717b187c 100644 --- a/src/resources/extensions/sf/tests/doctor-scope-db-unavailable.test.ts +++ b/src/resources/extensions/sf/tests/doctor-scope-db-unavailable.test.ts @@ -51,7 +51,7 @@ test("filterDoctorIssues keeps project and environment issues in scoped reports" test("checkEngineHealth reports db_unavailable when sf.db exists but the DB is closed", async (t) => { const base = mkdtempSync(join(tmpdir(), "sf-doctor-db-unavailable-")); - t.after(() => rmSync(base, { recursive: true, force: true })); + afterEach(() => rmSync(base, { recursive: true, force: true })); const sfDir = join(base, ".sf"); mkdirSync(sfDir, { recursive: true }); diff --git a/src/resources/extensions/sf/tests/exit-command.test.ts b/src/resources/extensions/sf/tests/exit-command.test.ts index 5cd3254c0..4d8c6afec 100644 --- a/src/resources/extensions/sf/tests/exit-command.test.ts +++ b/src/resources/extensions/sf/tests/exit-command.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { registerExitCommand } from "../exit-command.ts"; @@ -40,7 +40,7 @@ test("/exit requests graceful shutdown instead of process.exit", async (t) => { ); }) as typeof process.exit; - t.after(() => { + afterEach(() => { process.exit = originalExit; }); @@ -98,7 +98,7 @@ test("/exit still shuts down gracefully when stopAuto throws (ESM module cache m ); }) as typeof process.exit; - t.after(() => { + afterEach(() => { process.exit = originalExit; }); diff --git a/src/resources/extensions/sf/tests/file-change-validator.test.ts b/src/resources/extensions/sf/tests/file-change-validator.test.ts index 001b59e2c..1972dbc7b 100644 --- a/src/resources/extensions/sf/tests/file-change-validator.test.ts +++ b/src/resources/extensions/sf/tests/file-change-validator.test.ts @@ -3,7 +3,7 @@ import { execFileSync } from "node:child_process"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { validateFileChanges } from "../safety/file-change-validator.ts"; @@ -17,7 +17,7 @@ function git(cwd: string, ...args: string[]): string { test("validateFileChanges ignores inline descriptions in expected output paths", (t) => { const base = mkdtempSync(join(tmpdir(), "sf-file-change-validator-")); - t.after(() => rmSync(base, { recursive: true, force: true })); + afterEach(() => rmSync(base, { recursive: true, force: true })); mkdirSync(join(base, "definitions"), { recursive: true }); git(base, "init"); diff --git a/src/resources/extensions/sf/tests/files-loadfile-eisdir.test.ts b/src/resources/extensions/sf/tests/files-loadfile-eisdir.test.ts index a8e098746..25b83479e 100644 --- a/src/resources/extensions/sf/tests/files-loadfile-eisdir.test.ts +++ b/src/resources/extensions/sf/tests/files-loadfile-eisdir.test.ts @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { loadFile } from "../files.ts"; @@ -11,7 +11,7 @@ test("loadFile returns null for directory paths instead of throwing EISDIR", asy const dirPath = path.join(tmp, "tasks"); fs.mkdirSync(dirPath); - t.after(() => { + afterEach(() => { fs.rmSync(tmp, { recursive: true, force: true }); }); diff --git a/src/resources/extensions/sf/tests/git-checkpoint.test.ts b/src/resources/extensions/sf/tests/git-checkpoint.test.ts index f059fe3d6..5d6b22916 100644 --- a/src/resources/extensions/sf/tests/git-checkpoint.test.ts +++ b/src/resources/extensions/sf/tests/git-checkpoint.test.ts @@ -6,7 +6,7 @@ import { execFileSync } from "node:child_process"; import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { describe, it } from 'vitest'; +import { describe, it, afterEach } from 'vitest'; import { cleanupCheckpoint, createCheckpoint, @@ -36,7 +36,7 @@ function createTempRepo(): string { describe("git-checkpoint rollback", () => { it("rolls back to checkpoint on checked-out branch", (t) => { const repo = createTempRepo(); - t.after(() => rmSync(repo, { recursive: true, force: true })); + afterEach(() => rmSync(repo, { recursive: true, force: true })); // Create checkpoint at initial commit const sha = createCheckpoint(repo, "unit-1"); @@ -64,7 +64,7 @@ describe("git-checkpoint rollback", () => { it("returns false on detached HEAD", (t) => { const repo = createTempRepo(); - t.after(() => rmSync(repo, { recursive: true, force: true })); + afterEach(() => rmSync(repo, { recursive: true, force: true })); const sha = git(["rev-parse", "HEAD"], repo); git(["checkout", "--detach", sha], repo); @@ -75,7 +75,7 @@ describe("git-checkpoint rollback", () => { it("cleans up checkpoint ref after rollback", (t) => { const repo = createTempRepo(); - t.after(() => rmSync(repo, { recursive: true, force: true })); + afterEach(() => rmSync(repo, { recursive: true, force: true })); const sha = createCheckpoint(repo, "unit-3"); assert.ok(sha); @@ -103,7 +103,7 @@ describe("git-checkpoint rollback", () => { it("cleanupCheckpoint removes the ref without error", (t) => { const repo = createTempRepo(); - t.after(() => rmSync(repo, { recursive: true, force: true })); + afterEach(() => rmSync(repo, { recursive: true, force: true })); const sha = createCheckpoint(repo, "unit-4"); assert.ok(sha); diff --git a/src/resources/extensions/sf/tests/graph-operations.test.ts b/src/resources/extensions/sf/tests/graph-operations.test.ts index 12169e778..2fa233141 100644 --- a/src/resources/extensions/sf/tests/graph-operations.test.ts +++ b/src/resources/extensions/sf/tests/graph-operations.test.ts @@ -16,7 +16,7 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { describe, it } from 'vitest'; +import { describe, it, afterEach } from 'vitest'; import type { WorkflowDefinition } from "../definition-loader.ts"; import { expandIteration, @@ -146,7 +146,7 @@ describe("writeGraph + readGraph round-trip", () => { describe("readGraph error paths", () => { it("throws with descriptive error when file is missing", (t) => { const dir = makeTmpDir(); - t.after(() => { + afterEach(() => { cleanupDir(dir); }); @@ -162,7 +162,7 @@ describe("readGraph error paths", () => { it("throws with descriptive error when YAML is malformed (missing steps)", (t) => { const dir = makeTmpDir(); - t.after(() => { + afterEach(() => { cleanupDir(dir); }); @@ -178,7 +178,7 @@ describe("readGraph error paths", () => { it("throws when steps is not an array", (t) => { const dir = makeTmpDir(); - t.after(() => { + afterEach(() => { cleanupDir(dir); }); diff --git a/src/resources/extensions/sf/tests/health-widget.test.ts b/src/resources/extensions/sf/tests/health-widget.test.ts index 0b5a9bd74..16a5682f2 100644 --- a/src/resources/extensions/sf/tests/health-widget.test.ts +++ b/src/resources/extensions/sf/tests/health-widget.test.ts @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { registerHooks } from "../bootstrap/register-hooks.ts"; import { buildHealthLines, @@ -48,7 +48,7 @@ function activeData( test("detectHealthWidgetProjectState: no .sf returns none", (t) => { const dir = makeTempDir("none"); - t.after(() => { + afterEach(() => { cleanup(dir); }); @@ -57,7 +57,7 @@ test("detectHealthWidgetProjectState: no .sf returns none", (t) => { test("detectHealthWidgetProjectState: bootstrapped .sf without milestones returns initialized", (t) => { const dir = makeTempDir("initialized"); - t.after(() => { + afterEach(() => { cleanup(dir); }); @@ -67,7 +67,7 @@ test("detectHealthWidgetProjectState: bootstrapped .sf without milestones return test("detectHealthWidgetProjectState: milestone without metrics returns active", (t) => { const dir = makeTempDir("active"); - t.after(() => { + afterEach(() => { cleanup(dir); }); @@ -209,7 +209,7 @@ test("formatRelativeTime: days", () => { test("detectHealthWidgetProjectState: metrics file alone does not imply project", (t) => { const dir = makeTempDir("metrics-only"); - t.after(() => { + afterEach(() => { cleanup(dir); }); @@ -228,7 +228,7 @@ test("session_start bootstraps the health widget alongside notifications", async const originalCwd = process.cwd(); process.chdir(dir); - t.after(() => { + afterEach(() => { process.chdir(originalCwd); cleanup(dir); }); diff --git a/src/resources/extensions/sf/tests/init-wizard.test.ts b/src/resources/extensions/sf/tests/init-wizard.test.ts index 1235c1594..a49424ee7 100644 --- a/src/resources/extensions/sf/tests/init-wizard.test.ts +++ b/src/resources/extensions/sf/tests/init-wizard.test.ts @@ -9,7 +9,7 @@ import assert from "node:assert/strict"; import { mkdirSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; // We test the detection module integration since the wizard's UI // requires interactive ctx/pi which can't be unit-tested directly. @@ -38,7 +38,7 @@ function cleanup(dir: string): void { test("init-wizard: clean folder detected as state=none", (t) => { const dir = makeTempDir("clean"); - t.after(() => { + afterEach(() => { cleanup(dir); }); diff --git a/src/resources/extensions/sf/tests/insert-slice-no-wipe.test.ts b/src/resources/extensions/sf/tests/insert-slice-no-wipe.test.ts index 451bd5df6..aed64fdb8 100644 --- a/src/resources/extensions/sf/tests/insert-slice-no-wipe.test.ts +++ b/src/resources/extensions/sf/tests/insert-slice-no-wipe.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { closeDatabase, @@ -10,7 +10,7 @@ import { } from "../sf-db.ts"; test("insertSlice with minimal args does not wipe populated fields", (t) => { - t.after(() => { + afterEach(() => { try { closeDatabase(); } catch { @@ -90,7 +90,7 @@ test("insertSlice with minimal args does not wipe populated fields", (t) => { }); test("insertSlice ON CONFLICT preserves completed status", (t) => { - t.after(() => { + afterEach(() => { try { closeDatabase(); } catch { @@ -121,7 +121,7 @@ test("insertSlice ON CONFLICT preserves completed status", (t) => { }); test("insertSlice ON CONFLICT allows explicit updates to non-empty values", (t) => { - t.after(() => { + afterEach(() => { try { closeDatabase(); } catch { diff --git a/src/resources/extensions/sf/tests/integration/all-milestones-complete-merge.test.ts b/src/resources/extensions/sf/tests/integration/all-milestones-complete-merge.test.ts index 633fab0e6..f9003a81e 100644 --- a/src/resources/extensions/sf/tests/integration/all-milestones-complete-merge.test.ts +++ b/src/resources/extensions/sf/tests/integration/all-milestones-complete-merge.test.ts @@ -23,7 +23,7 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { dirname, join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { fileURLToPath } from "node:url"; import { @@ -137,7 +137,7 @@ test("single milestone worktree is merged to main when all complete (#962)", (t) const savedCwd = process.cwd(); let tempDir = ""; - t.after(() => { + afterEach(() => { process.chdir(savedCwd); if (tempDir && existsSync(tempDir)) { rmSync(tempDir, { recursive: true, force: true }); @@ -202,7 +202,7 @@ test("last milestone worktree is merged when it's the final one (#962)", (t) => const savedCwd = process.cwd(); let tempDir = ""; - t.after(() => { + afterEach(() => { process.chdir(savedCwd); if (tempDir && existsSync(tempDir)) { rmSync(tempDir, { recursive: true, force: true }); diff --git a/src/resources/extensions/sf/tests/integration/auto-preflight.test.ts b/src/resources/extensions/sf/tests/integration/auto-preflight.test.ts index 7bbbe537c..e607bef23 100644 --- a/src/resources/extensions/sf/tests/integration/auto-preflight.test.ts +++ b/src/resources/extensions/sf/tests/integration/auto-preflight.test.ts @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { filterDoctorIssues, @@ -55,7 +55,7 @@ test("auto-preflight scopes to active milestone, ignoring historical", async (t) `# S01: Active Slice\n\n**Goal:** Active\n**Demo:** Active\n\n## Must-Haves\n- done\n\n## Tasks\n- [ ] **T01: Active Task** \`est:5m\`\n todo\n`, ); - t.after(() => rmSync(tmpBase, { recursive: true, force: true })); + afterEach(() => rmSync(tmpBase, { recursive: true, force: true })); const scope = await selectDoctorScope(tmpBase); assert.equal( diff --git a/src/resources/extensions/sf/tests/integration/auto-recovery.test.ts b/src/resources/extensions/sf/tests/integration/auto-recovery.test.ts index 4e784eaa7..b078789c8 100644 --- a/src/resources/extensions/sf/tests/integration/auto-recovery.test.ts +++ b/src/resources/extensions/sf/tests/integration/auto-recovery.test.ts @@ -11,7 +11,7 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { buildLoopRemediationSteps, @@ -55,7 +55,7 @@ function cleanup(base: string): void { test("resolveExpectedArtifactPath returns correct path for research-milestone", (t) => { const base = makeTmpBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); const result = resolveExpectedArtifactPath( "research-milestone", @@ -69,7 +69,7 @@ test("resolveExpectedArtifactPath returns correct path for research-milestone", test("resolveExpectedArtifactPath returns correct path for execute-task", (t) => { const base = makeTmpBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); const result = resolveExpectedArtifactPath( "execute-task", @@ -83,7 +83,7 @@ test("resolveExpectedArtifactPath returns correct path for execute-task", (t) => test("resolveExpectedArtifactPath returns correct path for complete-slice", (t) => { const base = makeTmpBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); const result = resolveExpectedArtifactPath( "complete-slice", @@ -96,7 +96,7 @@ test("resolveExpectedArtifactPath returns correct path for complete-slice", (t) test("resolveExpectedArtifactPath returns correct path for plan-slice", (t) => { const base = makeTmpBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); const result = resolveExpectedArtifactPath("plan-slice", "M001/S01", base); assert.ok(result); @@ -105,7 +105,7 @@ test("resolveExpectedArtifactPath returns correct path for plan-slice", (t) => { test("resolveExpectedArtifactPath returns null for unknown type", (t) => { const base = makeTmpBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); const result = resolveExpectedArtifactPath("unknown-type", "M001", base); assert.equal(result, null); @@ -113,7 +113,7 @@ test("resolveExpectedArtifactPath returns null for unknown type", (t) => { test("resolveExpectedArtifactPath returns correct path for all milestone-level types", (t) => { const base = makeTmpBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); const planResult = resolveExpectedArtifactPath( "plan-milestone", @@ -134,7 +134,7 @@ test("resolveExpectedArtifactPath returns correct path for all milestone-level t test("resolveExpectedArtifactPath returns correct path for all slice-level types", (t) => { const base = makeTmpBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); const researchResult = resolveExpectedArtifactPath( "research-slice", @@ -165,7 +165,7 @@ test("resolveExpectedArtifactPath for run-uat returns ASSESSMENT path, not UAT ( // verification path must match — otherwise verification fails and auto-mode // retries the unit in an infinite loop. const base = makeTmpBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); const result = resolveExpectedArtifactPath("run-uat", "M001/S01", base); assert.ok(result, "run-uat should resolve to a non-null artifact path"); @@ -177,7 +177,7 @@ test("resolveExpectedArtifactPath for run-uat returns ASSESSMENT path, not UAT ( test("diagnoseExpectedArtifact for run-uat references ASSESSMENT (#2873)", (t) => { const base = makeTmpBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); const diag = diagnoseExpectedArtifact("run-uat", "M001/S01", base); assert.ok(diag, "run-uat should have a diagnostic message"); @@ -191,7 +191,7 @@ test("verifyExpectedArtifact passes for run-uat when ASSESSMENT file exists (#28 // Regression test: run-uat writes S##-ASSESSMENT.md via sf_summary_save, // but verification looked for S##-UAT.md, causing false stuck retries. const base = makeTmpBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); // Write the ASSESSMENT file (what sf_summary_save actually produces) const assessPath = join( @@ -216,7 +216,7 @@ test("verifyExpectedArtifact passes for run-uat when ASSESSMENT file exists (#28 test("diagnoseExpectedArtifact returns description for known types", (t) => { const base = makeTmpBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); const research = diagnoseExpectedArtifact("research-milestone", "M001", base); assert.ok(research); @@ -233,7 +233,7 @@ test("diagnoseExpectedArtifact returns description for known types", (t) => { test("diagnoseExpectedArtifact returns null for unknown type", (t) => { const base = makeTmpBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); assert.equal(diagnoseExpectedArtifact("unknown", "M001", base), null); }); @@ -242,7 +242,7 @@ test("diagnoseExpectedArtifact returns null for unknown type", (t) => { test("buildLoopRemediationSteps returns steps for execute-task", (t) => { const base = makeTmpBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); const steps = buildLoopRemediationSteps("execute-task", "M001/S01/T01", base); assert.ok(steps); @@ -252,7 +252,7 @@ test("buildLoopRemediationSteps returns steps for execute-task", (t) => { test("buildLoopRemediationSteps returns steps for plan-slice", (t) => { const base = makeTmpBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); const steps = buildLoopRemediationSteps("plan-slice", "M001/S01", base); assert.ok(steps); @@ -262,7 +262,7 @@ test("buildLoopRemediationSteps returns steps for plan-slice", (t) => { test("buildLoopRemediationSteps returns steps for complete-slice", (t) => { const base = makeTmpBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); const steps = buildLoopRemediationSteps("complete-slice", "M001/S01", base); assert.ok(steps); @@ -272,7 +272,7 @@ test("buildLoopRemediationSteps returns steps for complete-slice", (t) => { test("buildLoopRemediationSteps returns null for unknown type", (t) => { const base = makeTmpBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); assert.equal(buildLoopRemediationSteps("unknown", "M001", base), null); }); @@ -284,7 +284,7 @@ test("verifyExpectedArtifact detects roadmap [x] change despite parse cache", (t // file length or first/last 100 chars. Without the fix, parseRoadmap // returns stale cached data with done=false even though the file has [x]. const base = makeTmpBase(); - t.after(() => { + afterEach(() => { clearParseCache(); cleanup(base); }); @@ -355,7 +355,7 @@ test("verifyExpectedArtifact detects roadmap [x] change despite parse cache", (t test("verifyExpectedArtifact rejects plan-slice with empty scaffold", (t) => { const base = makeTmpBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); const sliceDir = join(base, ".sf", "milestones", "M001", "slices", "S01"); mkdirSync(sliceDir, { recursive: true }); @@ -372,7 +372,7 @@ test("verifyExpectedArtifact rejects plan-slice with empty scaffold", (t) => { test("verifyExpectedArtifact accepts plan-slice with actual tasks", (t) => { const base = makeTmpBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); const sliceDir = join(base, ".sf", "milestones", "M001", "slices", "S01"); const tasksDir = join(sliceDir, "tasks"); @@ -413,7 +413,7 @@ test("verifyExpectedArtifact accepts plan-slice with actual tasks", (t) => { test("verifyExpectedArtifact accepts plan-slice with completed tasks", (t) => { const base = makeTmpBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); const sliceDir = join(base, ".sf", "milestones", "M001", "slices", "S01"); const tasksDir = join(sliceDir, "tasks"); @@ -456,7 +456,7 @@ test("verifyExpectedArtifact accepts plan-slice with completed tasks", (t) => { test("verifyExpectedArtifact plan-slice passes when all task plan files exist", (t) => { const base = makeTmpBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); const tasksDir = join( base, @@ -511,7 +511,7 @@ test("verifyExpectedArtifact plan-slice passes when all task plan files exist", test("verifyExpectedArtifact plan-slice fails when a task plan file is missing (#739)", (t) => { const base = makeTmpBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); const tasksDir = join( base, @@ -563,7 +563,7 @@ test("verifyExpectedArtifact plan-slice fails when a task plan file is missing ( test("verifyExpectedArtifact plan-slice fails for plan with no tasks (#699)", (t) => { const base = makeTmpBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); const planPath = join( base, @@ -595,7 +595,7 @@ test("verifyExpectedArtifact plan-slice fails for plan with no tasks (#699)", (t test("verifyExpectedArtifact accepts plan-slice with heading-style tasks (### T01 --)", (t) => { const base = makeTmpBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); const sliceDir = join(base, ".sf", "milestones", "M001", "slices", "S01"); const tasksDir = join(sliceDir, "tasks"); @@ -641,7 +641,7 @@ test("verifyExpectedArtifact accepts plan-slice with heading-style tasks (### T0 test("verifyExpectedArtifact accepts plan-slice with colon-style heading tasks (### T01:)", (t) => { const base = makeTmpBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); const sliceDir = join(base, ".sf", "milestones", "M001", "slices", "S01"); const tasksDir = join(sliceDir, "tasks"); @@ -682,7 +682,7 @@ test("verifyExpectedArtifact accepts plan-slice with colon-style heading tasks ( test("verifyExpectedArtifact execute-task rejects heading-style plan without checked checkbox (#3607)", (t) => { const base = makeTmpBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); const sliceDir = join(base, ".sf", "milestones", "M001", "slices", "S01"); const tasksDir = join(sliceDir, "tasks"); @@ -901,7 +901,7 @@ test("verifyExpectedArtifact plan-slice fails after deleting a rendered task pla test("verifyExpectedArtifact plan-slice fails when adversarial review is missing", (t) => { const base = makeTmpBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); writeFileSync( join(base, ".sf", "milestones", "M001", "slices", "S01", "S01-PLAN.md"), @@ -942,7 +942,7 @@ test("verifyExpectedArtifact plan-slice fails when adversarial review is missing test("verifyExpectedArtifact plan-slice fails when planning meeting routes back to researching", (t) => { const base = makeTmpBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); writeFileSync( join(base, ".sf", "milestones", "M001", "slices", "S01", "S01-PLAN.md"), @@ -1040,7 +1040,7 @@ test("verifyExpectedArtifact plan-slice fails when planning meeting routes back // stale directory listings and returns the same unit, looping forever. test("#793: invalidateAllCaches clears all caches so deriveState sees fresh disk state", async (t) => { const base = makeTmpBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); const mid = "M001"; const sid = "S01"; @@ -1125,7 +1125,7 @@ function makeGitBase(): string { test("hasImplementationArtifacts returns 'absent' when only .sf/ files committed (#1703)", (t) => { const base = makeGitBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); // Create a feature branch and commit only .sf/ files execFileSync("git", ["checkout", "-b", "feat/test-milestone"], { @@ -1157,7 +1157,7 @@ test("hasImplementationArtifacts returns 'absent' when only .sf/ files committed test("hasImplementationArtifacts returns 'present' when implementation files committed (#1703)", (t) => { const base = makeGitBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); // Create a feature branch with both .sf/ and implementation files execFileSync("git", ["checkout", "-b", "feat/test-impl"], { @@ -1191,7 +1191,7 @@ test("hasImplementationArtifacts returns 'present' when implementation files com test("hasImplementationArtifacts returns 'unknown' on non-git directory (fail-open)", (t) => { const base = join(tmpdir(), `sf-test-nogit-${randomUUID()}`); mkdirSync(base, { recursive: true }); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); const result = hasImplementationArtifacts(base); assert.equal( @@ -1205,7 +1205,7 @@ test("hasImplementationArtifacts returns 'unknown' on non-git directory (fail-op test("verifyExpectedArtifact complete-milestone fails with only .sf/ files (#1703)", (t) => { const base = makeGitBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); // Create feature branch with only .sf/ files execFileSync("git", ["checkout", "-b", "feat/ms-only-sf"], { @@ -1250,7 +1250,7 @@ function makeMockCtx(): { test("reconcileMergeState returns blocked and notifies error when nativeCommit fails (#2542)", (t) => { const base = makeGitBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); // Create a second branch with a commit, then start a merge on main execFileSync("git", ["checkout", "-b", "feature"], { @@ -1281,7 +1281,7 @@ test("reconcileMergeState returns blocked and notifies error when nativeCommit f // causing nativeCommit to throw a non-"nothing to commit" error. const objectsDir = join(base, ".git", "objects"); chmodSync(objectsDir, 0o444); - t.after(() => { + afterEach(() => { try { chmodSync(objectsDir, 0o755); } catch { @@ -1310,7 +1310,7 @@ test("reconcileMergeState returns blocked and notifies error when nativeCommit f test("reconcileMergeState returns clean when no merge state present", (t) => { const base = makeGitBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); const { ctx, notifications } = makeMockCtx(); const result = reconcileMergeState(base, ctx); @@ -1329,7 +1329,7 @@ test("reconcileMergeState returns clean when no merge state present", (t) => { test("reconcileMergeState blocks and preserves unresolved code conflicts", (t) => { const base = makeGitBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); writeFileSync(join(base, "conflict.txt"), "base\n"); execFileSync("git", ["add", "conflict.txt"], { cwd: base, stdio: "ignore" }); @@ -1404,7 +1404,7 @@ test("reconcileMergeState blocks and preserves unresolved code conflicts", (t) = test("verifyExpectedArtifact complete-milestone passes with impl files (#1703)", (t) => { const base = makeGitBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); // Create feature branch with implementation files AND milestone summary execFileSync("git", ["checkout", "-b", "feat/ms-with-impl"], { diff --git a/src/resources/extensions/sf/tests/integration/auto-secrets-gate.test.ts b/src/resources/extensions/sf/tests/integration/auto-secrets-gate.test.ts index f81629eb3..c55fed23e 100644 --- a/src/resources/extensions/sf/tests/integration/auto-secrets-gate.test.ts +++ b/src/resources/extensions/sf/tests/integration/auto-secrets-gate.test.ts @@ -15,7 +15,7 @@ import assert from "node:assert/strict"; import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { collectSecretsFromManifest } from "../../../get-secrets-from-user.ts"; import { getManifestStatus } from "../../files.ts"; @@ -48,7 +48,7 @@ function makeNoUICtx(cwd: string) { test("secrets gate: no manifest exists — getManifestStatus returns null", async (t) => { const tmp = makeTempDir("gate-no-manifest"); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); // No .sf directory at all const result = await getManifestStatus(tmp, "M001"); @@ -64,7 +64,7 @@ test("secrets gate: no manifest exists — getManifestStatus returns null", asyn test("secrets gate: pending keys exist — gate triggers collection, manifest updated on disk", async (t) => { const tmp = makeTempDir("gate-pending"); const savedA = process.env.SF_GATE_TEST_EXISTING; - t.after(() => { + afterEach(() => { delete process.env.SF_GATE_TEST_EXISTING; if (savedA !== undefined) process.env.SF_GATE_TEST_EXISTING = savedA; delete process.env.SF_GATE_TEST_PEND_A; @@ -197,7 +197,7 @@ test("secrets gate: pending keys exist — gate triggers collection, manifest up test("secrets gate: no pending keys — getManifestStatus shows pending.length === 0", async (t) => { const tmp = makeTempDir("gate-no-pending"); const savedKey = process.env.SF_GATE_TEST_ENVKEY; - t.after(() => { + afterEach(() => { delete process.env.SF_GATE_TEST_ENVKEY; if (savedKey !== undefined) process.env.SF_GATE_TEST_ENVKEY = savedKey; rmSync(tmp, { recursive: true, force: true }); diff --git a/src/resources/extensions/sf/tests/integration/continue-here.test.ts b/src/resources/extensions/sf/tests/integration/continue-here.test.ts index 85386f17a..747889963 100644 --- a/src/resources/extensions/sf/tests/integration/continue-here.test.ts +++ b/src/resources/extensions/sf/tests/integration/continue-here.test.ts @@ -10,7 +10,7 @@ */ import assert from "node:assert/strict"; -import { describe, it } from 'vitest'; +import { describe, it, afterEach } from 'vitest'; import { computeBudgets } from "../../context-budget.js"; @@ -190,7 +190,7 @@ describe("continue-here", () => { const tmpDir = fs.mkdtempSync( path.join(os.tmpdir(), "continue-here-test-"), ); - t.after(() => fs.rmSync(tmpDir, { recursive: true, force: true })); + afterEach(() => fs.rmSync(tmpDir, { recursive: true, force: true })); const record = writeUnitRuntimeRecord( tmpDir, @@ -265,7 +265,7 @@ describe("continue-here", () => { const tmpDir = fs.mkdtempSync( path.join(os.tmpdir(), "continue-here-monitor-"), ); - t.after(() => fs.rmSync(tmpDir, { recursive: true, force: true })); + afterEach(() => fs.rmSync(tmpDir, { recursive: true, force: true })); // Simulate the monitor's one-shot logic: // 1. Write initial runtime record (continueHereFired=false) diff --git a/src/resources/extensions/sf/tests/integration/doctor-completion-deferral.test.ts b/src/resources/extensions/sf/tests/integration/doctor-completion-deferral.test.ts index 23d21dbf0..da9705275 100644 --- a/src/resources/extensions/sf/tests/integration/doctor-completion-deferral.test.ts +++ b/src/resources/extensions/sf/tests/integration/doctor-completion-deferral.test.ts @@ -9,7 +9,7 @@ import assert from "node:assert/strict"; import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { runSFDoctor } from "../../doctor.ts"; function makeTmp(name: string): string { @@ -70,7 +70,7 @@ Done. test("doctor does not report any reconciliation issue codes", async (t) => { const tmp = makeTmp("no-reconciliation"); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); buildScaffold(tmp); diff --git a/src/resources/extensions/sf/tests/integration/doctor-delimiter-fix.test.ts b/src/resources/extensions/sf/tests/integration/doctor-delimiter-fix.test.ts index 2f57e508a..23a0a9a6a 100644 --- a/src/resources/extensions/sf/tests/integration/doctor-delimiter-fix.test.ts +++ b/src/resources/extensions/sf/tests/integration/doctor-delimiter-fix.test.ts @@ -15,7 +15,7 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { runSFDoctor } from "../../doctor.js"; test("doctor fix=true sanitizes em-dash in milestone title", async (t) => { @@ -43,7 +43,7 @@ test("doctor fix=true sanitizes em-dash in milestone title", async (t) => { ); writeFileSync(join(tDir, "T01-PLAN.md"), "# T01: Scaffold\n"); - t.after(() => rmSync(tmpBase, { recursive: true, force: true })); + afterEach(() => rmSync(tmpBase, { recursive: true, force: true })); // Run doctor with fix=true const report = await runSFDoctor(tmpBase, { fix: true }); @@ -91,7 +91,7 @@ test("doctor fix=false still reports delimiter_in_title as warning", async (t) = ); writeFileSync(join(tDir, "T01-PLAN.md"), "# T01: Init\n"); - t.after(() => rmSync(tmpBase, { recursive: true, force: true })); + afterEach(() => rmSync(tmpBase, { recursive: true, force: true })); const report = await runSFDoctor(tmpBase, { fix: false }); const delimIssues = report.issues.filter( diff --git a/src/resources/extensions/sf/tests/integration/doctor-fixlevel.test.ts b/src/resources/extensions/sf/tests/integration/doctor-fixlevel.test.ts index 6baa14a2e..2fe5c3495 100644 --- a/src/resources/extensions/sf/tests/integration/doctor-fixlevel.test.ts +++ b/src/resources/extensions/sf/tests/integration/doctor-fixlevel.test.ts @@ -19,7 +19,7 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { runSFDoctor } from "../../doctor.ts"; import { closeDatabase, @@ -102,7 +102,7 @@ const REMOVED_CODES = [ test("fixLevel:task — no reconciliation issue codes are reported", async (t) => { const tmp = makeTmp("task-level"); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); buildScaffold(tmp); @@ -119,7 +119,7 @@ test("fixLevel:task — no reconciliation issue codes are reported", async (t) = test("fixLevel:all — no reconciliation issue codes are reported", async (t) => { const tmp = makeTmp("all-level"); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); buildScaffold(tmp); @@ -161,7 +161,7 @@ test("fixLevel:all — no reconciliation issue codes are reported", async (t) => test("legacy roadmap fallback: future slices are treated as pending, active slice is not", async (t) => { const tmp = makeTmp("legacy-pending-fallback"); - t.after(() => { + afterEach(() => { try { closeDatabase(); } catch { @@ -239,7 +239,7 @@ test("legacy roadmap fallback: future slices are treated as pending, active slic test("db skipped slices do not report missing directories", async (t) => { const tmp = makeTmp("skipped-slice-dir"); - t.after(() => { + afterEach(() => { try { closeDatabase(); } catch { @@ -306,7 +306,7 @@ test("doctor source treats skipped DB slices as closed and directory-optional", test("fixLevel:all — delimiter_in_title still fixable", async (t) => { const tmp = makeTmp("delimiter-fix"); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); const sf = join(tmp, ".sf"); const m = join(sf, "milestones", "M001"); diff --git a/src/resources/extensions/sf/tests/integration/doctor-roadmap-summary-atomicity.test.ts b/src/resources/extensions/sf/tests/integration/doctor-roadmap-summary-atomicity.test.ts index c8726ac28..8bf23cba0 100644 --- a/src/resources/extensions/sf/tests/integration/doctor-roadmap-summary-atomicity.test.ts +++ b/src/resources/extensions/sf/tests/integration/doctor-roadmap-summary-atomicity.test.ts @@ -17,7 +17,7 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { runSFDoctor } from "../../doctor.ts"; function makeTmp(name: string): string { @@ -78,7 +78,7 @@ Done. test("fixLevel:task — roadmap checkbox is never toggled by doctor (reconciliation removed)", async (t) => { const tmp = makeTmp("no-roadmap-toggle"); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); buildScaffold(tmp); @@ -109,7 +109,7 @@ test("fixLevel:task — roadmap checkbox is never toggled by doctor (reconciliat test("fixLevel:all — roadmap checkbox is never toggled by doctor (reconciliation removed)", async (t) => { const tmp = makeTmp("all-no-toggle"); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); buildScaffold(tmp); @@ -139,7 +139,7 @@ test("fixLevel:all — roadmap checkbox is never toggled by doctor (reconciliati test("consecutive doctor runs produce no reconciliation codes", async (t) => { const tmp = makeTmp("consecutive-clean"); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); buildScaffold(tmp); diff --git a/src/resources/extensions/sf/tests/integration/gitignore-staging-2570.test.ts b/src/resources/extensions/sf/tests/integration/gitignore-staging-2570.test.ts index cc6860003..1e272717f 100644 --- a/src/resources/extensions/sf/tests/integration/gitignore-staging-2570.test.ts +++ b/src/resources/extensions/sf/tests/integration/gitignore-staging-2570.test.ts @@ -20,7 +20,7 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; // Dynamic import — isSfGitignored is the function under test (may not exist yet during TDD red phase) const { isSfGitignored } = await import("../../gitignore.ts"); @@ -59,7 +59,7 @@ function cleanup(dir: string): void { test("isSfGitignored returns true when .sf is in .gitignore (#2570)", (t) => { const dir = makeTempRepo(); - t.after(() => { + afterEach(() => { cleanup(dir); }); @@ -69,7 +69,7 @@ test("isSfGitignored returns true when .sf is in .gitignore (#2570)", (t) => { test("isSfGitignored returns true when .sf/ (with slash) is in .gitignore", (t) => { const dir = makeTempRepo(); - t.after(() => { + afterEach(() => { cleanup(dir); }); @@ -81,7 +81,7 @@ test("isSfGitignored returns true when .sf/ (with slash) is in .gitignore", (t) test("isSfGitignored returns false when .sf is NOT in .gitignore", (t) => { const dir = makeTempRepo(); - t.after(() => { + afterEach(() => { cleanup(dir); }); @@ -91,7 +91,7 @@ test("isSfGitignored returns false when .sf is NOT in .gitignore", (t) => { test("isSfGitignored returns false when no .gitignore exists", (t) => { const dir = makeTempRepo(); - t.after(() => { + afterEach(() => { cleanup(dir); }); @@ -132,7 +132,7 @@ test("smartStage does not stage .sf/ files when .sf is gitignored (#2570)", asyn const { GitServiceImpl } = await import("../../git-service.ts"); const dir = makeTempRepo(); - t.after(() => { + afterEach(() => { cleanup(dir); }); diff --git a/src/resources/extensions/sf/tests/integration/gitignore-tracked-sf.test.ts b/src/resources/extensions/sf/tests/integration/gitignore-tracked-sf.test.ts index 1734d8727..b63801404 100644 --- a/src/resources/extensions/sf/tests/integration/gitignore-tracked-sf.test.ts +++ b/src/resources/extensions/sf/tests/integration/gitignore-tracked-sf.test.ts @@ -20,7 +20,7 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { ensureGitignore, hasGitTrackedSfFiles } from "../../gitignore.ts"; import { migrateToExternalState } from "../../migrate-external.ts"; @@ -59,7 +59,7 @@ function cleanup(dir: string): void { test("hasGitTrackedSfFiles returns false when .sf/ does not exist", (t) => { const dir = makeTempRepo(); - t.after(() => { + afterEach(() => { cleanup(dir); }); @@ -68,7 +68,7 @@ test("hasGitTrackedSfFiles returns false when .sf/ does not exist", (t) => { test("hasGitTrackedSfFiles returns true when .sf/ has tracked files", (t) => { const dir = makeTempRepo(); - t.after(() => { + afterEach(() => { cleanup(dir); }); @@ -81,7 +81,7 @@ test("hasGitTrackedSfFiles returns true when .sf/ has tracked files", (t) => { test("hasGitTrackedSfFiles returns false when .sf/ exists but is untracked", (t) => { const dir = makeTempRepo(); - t.after(() => { + afterEach(() => { cleanup(dir); }); @@ -145,7 +145,7 @@ test("ensureGitignore excludes .sf when .sf/ has NO tracked files", (_t) => { test("ensureGitignore respects manageGitignore: false", (t) => { const dir = makeTempRepo(); - t.after(() => { + afterEach(() => { cleanup(dir); }); diff --git a/src/resources/extensions/sf/tests/integration/integration-proof.test.ts b/src/resources/extensions/sf/tests/integration/integration-proof.test.ts index 1228372a3..7928d1525 100644 --- a/src/resources/extensions/sf/tests/integration/integration-proof.test.ts +++ b/src/resources/extensions/sf/tests/integration/integration-proof.test.ts @@ -33,7 +33,7 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; // ── Post-unit diagnostics ───────────────────────────────────────────────── import { detectRogueFileWrites } from "../../auto-post-unit.ts"; // ── Cache invalidation ─────────────────────────────────────────────────── @@ -261,7 +261,7 @@ test("full lifecycle: migration through completion through doctor", async (t) => const base = createRealisticFixture(); const dbPath = join(base, ".sf", "sf.db"); - t.after(() => { + afterEach(() => { closeDatabase(); rmSync(base, { recursive: true, force: true }); }); @@ -485,7 +485,7 @@ test("recovery: DB loss → migrateFromMarkdown restores state, stale render det const base = createRealisticFixture(); const dbPath = join(base, ".sf", "sf.db"); - t.after(() => { + afterEach(() => { closeDatabase(); rmSync(base, { recursive: true, force: true }); }); @@ -588,7 +588,7 @@ test("undo/reset: undo task and reset slice revert DB + markdown", async (t) => const base = createRealisticFixture(); const dbPath = join(base, ".sf", "sf.db"); - t.after(() => { + afterEach(() => { closeDatabase(); rmSync(base, { recursive: true, force: true }); }); diff --git a/src/resources/extensions/sf/tests/native-git-bridge-exec-fallback.test.ts b/src/resources/extensions/sf/tests/native-git-bridge-exec-fallback.test.ts index 775d4705c..3c0631714 100644 --- a/src/resources/extensions/sf/tests/native-git-bridge-exec-fallback.test.ts +++ b/src/resources/extensions/sf/tests/native-git-bridge-exec-fallback.test.ts @@ -106,7 +106,7 @@ describe("native-git-bridge #4180: fallback runtime behaviour", () => { test("nativeIsRepo returns false for a plain directory", (t) => { const dir = mkdtempSync(join(tmpdir(), "ngb4180-notrepo-")); - t.after(() => rmSync(dir, { recursive: true, force: true })); + afterEach(() => rmSync(dir, { recursive: true, force: true })); assert.equal(nativeIsRepo(dir), false); }); diff --git a/src/resources/extensions/sf/tests/notifications-handler.test.ts b/src/resources/extensions/sf/tests/notifications-handler.test.ts index ef726088b..35a7cd78c 100644 --- a/src/resources/extensions/sf/tests/notifications-handler.test.ts +++ b/src/resources/extensions/sf/tests/notifications-handler.test.ts @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import { mkdirSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { handleNotificationsCommand } from "../commands/handlers/notifications-handler.ts"; import { @@ -34,7 +34,7 @@ test("notifications command falls back to text output when overlay returns undef initNotificationStore(base); appendNotification("Build complete", "success"); - t.after(() => { + afterEach(() => { _resetNotificationStore(); cleanup(base); }); @@ -69,7 +69,7 @@ test("notifications tail caps inline output and hints to open overlay", async (t appendNotification(`notification-${i + 1}`, "info"); } - t.after(() => { + afterEach(() => { _resetNotificationStore(); cleanup(base); }); diff --git a/src/resources/extensions/sf/tests/parallel-orchestrator-zombie-cleanup.test.ts b/src/resources/extensions/sf/tests/parallel-orchestrator-zombie-cleanup.test.ts index 3b9771ce0..8bb196319 100644 --- a/src/resources/extensions/sf/tests/parallel-orchestrator-zombie-cleanup.test.ts +++ b/src/resources/extensions/sf/tests/parallel-orchestrator-zombie-cleanup.test.ts @@ -13,7 +13,7 @@ import { randomUUID } from "node:crypto"; import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { getOrchestratorState, @@ -77,7 +77,7 @@ const _DEAD_PID = 2147483647; test("#2736: refreshWorkerStatuses deactivates orchestrator when all workers are error/stopped", (t) => { const base = makeTmpBase(); - t.after(() => { + afterEach(() => { resetOrchestrator(); cleanup(base); }); @@ -139,7 +139,7 @@ test("#2736: refreshWorkerStatuses deactivates orchestrator when all workers are test("#2736: refreshWorkerStatuses keeps orchestrator active when some workers are still running", (t) => { const base = makeTmpBase(); - t.after(() => { + afterEach(() => { resetOrchestrator(); cleanup(base); }); @@ -192,7 +192,7 @@ test("#2736: refreshWorkerStatuses keeps orchestrator active when some workers a test("#2736: getWorkerStatuses returns empty when all cached workers are in error state", (t) => { const base = makeTmpBase(); - t.after(() => { + afterEach(() => { resetOrchestrator(); cleanup(base); }); @@ -244,7 +244,7 @@ test("#2736: getWorkerStatuses returns empty when all cached workers are in erro test("#2736: restoreRuntimeState clears stale state when all workers are stopped", (t) => { const base = makeTmpBase(); - t.after(() => { + afterEach(() => { resetOrchestrator(); cleanup(base); }); diff --git a/src/resources/extensions/sf/tests/register-hooks-depth-verification.test.ts b/src/resources/extensions/sf/tests/register-hooks-depth-verification.test.ts index 142e1dbba..8c9911378 100644 --- a/src/resources/extensions/sf/tests/register-hooks-depth-verification.test.ts +++ b/src/resources/extensions/sf/tests/register-hooks-depth-verification.test.ts @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import { mkdirSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { registerHooks } from "../bootstrap/register-hooks.ts"; import { @@ -26,7 +26,7 @@ test("register-hooks unlocks milestone depth verification from question id witho process.chdir(dir); resetWriteGateState(); - t.after(() => { + afterEach(() => { resetWriteGateState(); process.chdir(originalCwd); rmSync(dir, { recursive: true, force: true }); diff --git a/src/resources/extensions/sf/tests/register-shortcuts.test.ts b/src/resources/extensions/sf/tests/register-shortcuts.test.ts index 30cdfdfc3..083a78704 100644 --- a/src/resources/extensions/sf/tests/register-shortcuts.test.ts +++ b/src/resources/extensions/sf/tests/register-shortcuts.test.ts @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import { mkdirSync, realpathSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { registerShortcuts } from "../bootstrap/register-shortcuts.ts"; @@ -31,7 +31,7 @@ test("dashboard shortcut resolves the project root instead of the current worktr const originalCwd = process.cwd(); process.chdir(worktreeRoot); - t.after(() => { + afterEach(() => { process.chdir(originalCwd); cleanup(projectRoot); }); @@ -120,7 +120,7 @@ test("parallel shortcut passes resolved project root into overlay", async (t) => const originalCwd = process.cwd(); process.chdir(worktreeRoot); - t.after(() => { + afterEach(() => { process.chdir(originalCwd); cleanup(base); }); diff --git a/src/resources/extensions/sf/tests/research-milestone-composer.test.ts b/src/resources/extensions/sf/tests/research-milestone-composer.test.ts index aceddf8e8..b6048a8c2 100644 --- a/src/resources/extensions/sf/tests/research-milestone-composer.test.ts +++ b/src/resources/extensions/sf/tests/research-milestone-composer.test.ts @@ -4,7 +4,7 @@ import assert from "node:assert/strict"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { buildResearchMilestonePrompt } from "../auto-prompts.ts"; import { invalidateAllCaches } from "../cache.ts"; @@ -58,7 +58,7 @@ function seed(base: string, mid: string): void { test("#4782 phase 3: buildResearchMilestonePrompt emits milestone-context then research template via composer", async (t) => { const base = makeBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); invalidateAllCaches(); seed(base, "M001"); @@ -98,7 +98,7 @@ test("#4782 phase 3: buildResearchMilestonePrompt emits milestone-context then r test("#4782 phase 3: buildResearchMilestonePrompt still includes project + requirements + decisions in declared order", async (t) => { const base = makeBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); invalidateAllCaches(); seed(base, "M001"); diff --git a/src/resources/extensions/sf/tests/run-uat-composer.test.ts b/src/resources/extensions/sf/tests/run-uat-composer.test.ts index 2dd102049..93411edc3 100644 --- a/src/resources/extensions/sf/tests/run-uat-composer.test.ts +++ b/src/resources/extensions/sf/tests/run-uat-composer.test.ts @@ -6,7 +6,7 @@ import assert from "node:assert/strict"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { buildRunUatPrompt } from "../auto-prompts.ts"; import { invalidateAllCaches } from "../cache.ts"; @@ -69,7 +69,7 @@ function seed(base: string, mid: string): void { test("#4782 phase 3: buildRunUatPrompt inlines slice UAT, slice summary, project via composer", async (t) => { const base = makeBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); invalidateAllCaches(); seed(base, "M001"); @@ -109,7 +109,7 @@ test("#4782 phase 3: buildRunUatPrompt inlines slice UAT, slice summary, project test("#4782 phase 3: buildRunUatPrompt omits optional slice summary when file is missing", async (t) => { const base = makeBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); invalidateAllCaches(); seed(base, "M001"); diff --git a/src/resources/extensions/sf/tests/secure-env-collect.test.ts b/src/resources/extensions/sf/tests/secure-env-collect.test.ts index 3368ba09f..2ec9db0d0 100644 --- a/src/resources/extensions/sf/tests/secure-env-collect.test.ts +++ b/src/resources/extensions/sf/tests/secure-env-collect.test.ts @@ -10,7 +10,7 @@ import assert from "node:assert/strict"; import { mkdirSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { checkExistingEnvKeys, detectDestination, @@ -263,7 +263,7 @@ test("secure_env_collect #2997: undefined from ctx.ui.custom() is treated as ski const { collectSecretsFromManifest } = await loadOrchestrator(); const tmp = makeTempDir("sec-undefined-test"); - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); @@ -306,7 +306,7 @@ test("secure_env_collect #2997: null from ctx.ui.custom() is still treated as sk const { collectSecretsFromManifest } = await loadOrchestrator(); const tmp = makeTempDir("sec-null-test"); - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); @@ -344,7 +344,7 @@ test("secure_env_collect: falls back to secure input prompt when custom UI is un const { collectSecretsFromManifest } = await loadOrchestrator(); const tmp = makeTempDir("sec-input-fallback-test"); - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); diff --git a/src/resources/extensions/sf/tests/subagent-agent-discovery.test.ts b/src/resources/extensions/sf/tests/subagent-agent-discovery.test.ts index 48b1556fc..bba567f66 100644 --- a/src/resources/extensions/sf/tests/subagent-agent-discovery.test.ts +++ b/src/resources/extensions/sf/tests/subagent-agent-discovery.test.ts @@ -2,13 +2,13 @@ import assert from "node:assert/strict"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { discoverAgents } from "../../subagent/agents.ts"; function makeProjectRoot(t: test.TestContext): string { const root = mkdtempSync(join(tmpdir(), "sf-subagent-agents-")); - t.after(() => rmSync(root, { recursive: true, force: true })); + afterEach(() => rmSync(root, { recursive: true, force: true })); return root; } diff --git a/src/resources/extensions/sf/tests/subagent-model-dispatch.test.ts b/src/resources/extensions/sf/tests/subagent-model-dispatch.test.ts index d09f6551a..1eeaf8ab7 100644 --- a/src/resources/extensions/sf/tests/subagent-model-dispatch.test.ts +++ b/src/resources/extensions/sf/tests/subagent-model-dispatch.test.ts @@ -18,7 +18,7 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { dirname, join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { fileURLToPath } from "node:url"; import { validatePreferences } from "../preferences-validation.ts"; @@ -194,7 +194,7 @@ test("auto-dispatch: passes model to buildGateEvaluatePrompt", () => { test("buildReactiveExecutePrompt: output contains model string when subagentModel provided", async (t) => { const { buildReactiveExecutePrompt } = await import("../auto-prompts.ts"); const repo = mkdtempSync(join(tmpdir(), "sf-subagent-model-reactive-")); - t.after(() => rmSync(repo, { recursive: true, force: true })); + afterEach(() => rmSync(repo, { recursive: true, force: true })); const sf = join(repo, ".sf", "milestones", "M001", "slices", "S01"); mkdirSync(join(sf, "tasks"), { recursive: true }); @@ -252,7 +252,7 @@ test("buildReactiveExecutePrompt: output contains model string when subagentMode test("buildReactiveExecutePrompt: no model instruction when subagentModel omitted", async (t) => { const { buildReactiveExecutePrompt } = await import("../auto-prompts.ts"); const repo = mkdtempSync(join(tmpdir(), "sf-subagent-model-none-")); - t.after(() => rmSync(repo, { recursive: true, force: true })); + afterEach(() => rmSync(repo, { recursive: true, force: true })); const sf = join(repo, ".sf", "milestones", "M001", "slices", "S01"); mkdirSync(join(sf, "tasks"), { recursive: true }); diff --git a/src/resources/extensions/sf/tests/unit-context-composer.test.ts b/src/resources/extensions/sf/tests/unit-context-composer.test.ts index ed6f0ca95..bf857d7fb 100644 --- a/src/resources/extensions/sf/tests/unit-context-composer.test.ts +++ b/src/resources/extensions/sf/tests/unit-context-composer.test.ts @@ -6,7 +6,7 @@ import assert from "node:assert/strict"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { buildReassessRoadmapPrompt } from "../auto-prompts.ts"; import { invalidateAllCaches } from "../cache.ts"; import { @@ -162,7 +162,7 @@ function writeArtifacts(base: string): void { test("#4782 phase 2: buildReassessRoadmapPrompt emits composer-shaped context with manifest-declared artifacts", async (t) => { const base = makeFixtureBase(); - t.after(() => cleanup(base)); + afterEach(() => cleanup(base)); invalidateAllCaches(); seed(base, "M001"); diff --git a/src/resources/extensions/sf/tests/uok-loop-adapter-writer.test.ts b/src/resources/extensions/sf/tests/uok-loop-adapter-writer.test.ts index bccc774ed..3244c3c83 100644 --- a/src/resources/extensions/sf/tests/uok-loop-adapter-writer.test.ts +++ b/src/resources/extensions/sf/tests/uok-loop-adapter-writer.test.ts @@ -1,4 +1,4 @@ -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import assert from "node:assert/strict"; import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; @@ -20,7 +20,7 @@ function readAuditPayloads(basePath: string): Array> { test("uok turn observer adds writer sequence metadata to audit events", (t) => { const basePath = mkdtempSync(join(tmpdir(), "sf-uok-loop-writer-")); resetWriterTokensForTests(); - t.after(() => { + afterEach(() => { resetWriterTokensForTests(); rmSync(basePath, { recursive: true, force: true }); }); diff --git a/src/resources/extensions/sf/tests/uok-parity-report.test.ts b/src/resources/extensions/sf/tests/uok-parity-report.test.ts index 13a4a32b8..a9e80b776 100644 --- a/src/resources/extensions/sf/tests/uok-parity-report.test.ts +++ b/src/resources/extensions/sf/tests/uok-parity-report.test.ts @@ -1,4 +1,4 @@ -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import assert from "node:assert/strict"; import { mkdtempSync, readFileSync, rmSync, appendFileSync, mkdirSync } from "node:fs"; import { tmpdir } from "node:os"; @@ -23,7 +23,7 @@ test("uok parity report summarizes paths, statuses, and fallback use", () => { test("uok parity report writes runtime report artifact", (t) => { const basePath = mkdtempSync(join(tmpdir(), "sf-uok-parity-")); - t.after(() => { + afterEach(() => { rmSync(basePath, { recursive: true, force: true }); }); diff --git a/src/resources/extensions/sf/tests/uok-writer.test.ts b/src/resources/extensions/sf/tests/uok-writer.test.ts index a05241276..5ad44a239 100644 --- a/src/resources/extensions/sf/tests/uok-writer.test.ts +++ b/src/resources/extensions/sf/tests/uok-writer.test.ts @@ -1,4 +1,4 @@ -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import assert from "node:assert/strict"; import { mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; @@ -15,7 +15,7 @@ import { test("uok writer enforces one active token per turn", (t) => { const basePath = mkdtempSync(join(tmpdir(), "sf-uok-writer-")); resetWriterTokensForTests(); - t.after(() => { + afterEach(() => { resetWriterTokensForTests(); rmSync(basePath, { recursive: true, force: true }); }); @@ -38,7 +38,7 @@ test("uok writer enforces one active token per turn", (t) => { test("uok writer produces monotonic sequence records across turns", (t) => { const basePath = mkdtempSync(join(tmpdir(), "sf-uok-writer-seq-")); resetWriterTokensForTests(); - t.after(() => { + afterEach(() => { resetWriterTokensForTests(); rmSync(basePath, { recursive: true, force: true }); }); diff --git a/src/resources/extensions/sf/tests/workflow-logger.test.ts b/src/resources/extensions/sf/tests/workflow-logger.test.ts index 1b932f41d..e176b3d59 100644 --- a/src/resources/extensions/sf/tests/workflow-logger.test.ts +++ b/src/resources/extensions/sf/tests/workflow-logger.test.ts @@ -367,7 +367,7 @@ describe("workflow-logger", () => { written.push(chunk); return true; }; - t.after(() => { + afterEach(() => { process.stderr.write = orig; }); @@ -383,7 +383,7 @@ describe("workflow-logger", () => { written.push(chunk); return true; }; - t.after(() => { + afterEach(() => { process.stderr.write = orig; }); @@ -398,7 +398,7 @@ describe("workflow-logger", () => { written.push(chunk); return true; }; - t.after(() => { + afterEach(() => { process.stderr.write = orig; }); @@ -414,7 +414,7 @@ describe("workflow-logger", () => { written.push(chunk); return true; }; - t.after(() => { + afterEach(() => { process.stderr.write = orig; setStderrLoggingEnabled(previous); }); diff --git a/src/resources/extensions/sf/tests/worktree-preferences-sync.test.ts b/src/resources/extensions/sf/tests/worktree-preferences-sync.test.ts index 474a8298f..3b968652f 100644 --- a/src/resources/extensions/sf/tests/worktree-preferences-sync.test.ts +++ b/src/resources/extensions/sf/tests/worktree-preferences-sync.test.ts @@ -21,7 +21,7 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { syncSfStateToWorktree, @@ -61,7 +61,7 @@ const PREFS_CONTENT = [ test("#2684: syncSfStateToWorktree forward-syncs PREFERENCES.md when missing from worktree", (t) => { const mainBase = makeTempDir("main"); const wtBase = makeTempDir("wt"); - t.after(() => cleanup(mainBase, wtBase)); + afterEach(() => cleanup(mainBase, wtBase)); // Project root has canonical PREFERENCES.md writeFile(mainBase, ".sf/PREFERENCES.md", PREFS_CONTENT); @@ -89,7 +89,7 @@ test("#2684: syncSfStateToWorktree forward-syncs PREFERENCES.md when missing fro test("syncSfStateToWorktree still accepts legacy lowercase preferences.md", (t) => { const mainBase = makeTempDir("main"); const wtBase = makeTempDir("wt"); - t.after(() => cleanup(mainBase, wtBase)); + afterEach(() => cleanup(mainBase, wtBase)); writeFile(mainBase, ".sf/preferences.md", PREFS_CONTENT); mkdirSync(join(wtBase, ".sf"), { recursive: true }); @@ -114,7 +114,7 @@ test("syncSfStateToWorktree still accepts legacy lowercase preferences.md", (t) test("#2684: syncSfStateToWorktree does NOT overwrite existing worktree preferences file", (t) => { const mainBase = makeTempDir("main"); const wtBase = makeTempDir("wt"); - t.after(() => cleanup(mainBase, wtBase)); + afterEach(() => cleanup(mainBase, wtBase)); const rootPrefs = "# Root preferences\nold: true"; const wtPrefs = "# Worktree preferences\nmodified: true"; @@ -135,7 +135,7 @@ test("#2684: syncWorktreeStateBack does NOT overwrite project root PREFERENCES.m const mainBase = makeTempDir("main"); const wtBase = makeTempDir("wt"); const mid = "M001"; - t.after(() => cleanup(mainBase, wtBase)); + afterEach(() => cleanup(mainBase, wtBase)); const rootPrefs = "# Root preferences\nauthoritative: true"; const wtPrefs = "# Worktree preferences\nstale-copy: true"; diff --git a/src/tests/app-smoke.test.ts b/src/tests/app-smoke.test.ts index 246a46b20..bcd1b2724 100644 --- a/src/tests/app-smoke.test.ts +++ b/src/tests/app-smoke.test.ts @@ -22,7 +22,7 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { fileURLToPath } from "node:url"; const projectRoot = join(fileURLToPath(import.meta.url), "..", "..", ".."); @@ -101,7 +101,7 @@ test("loader sets all 4 SF_ env vars and PI_PACKAGE_DIR", async (t) => { const scriptPath = join(tmp, "check-env.ts"); writeFileSync(scriptPath, script); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); try { const _output = execSync( `node --experimental-strip-types -e " @@ -352,7 +352,7 @@ test("initResources skips copy when managed version matches current version", as const tmp = mkdtempSync(join(tmpdir(), "sf-resources-skip-")); const fakeAgentDir = join(tmp, "agent"); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); // First run: full sync (no manifest yet) initResources(fakeAgentDir); const version = readManagedResourceVersion(fakeAgentDir); @@ -430,7 +430,7 @@ test("loadStoredEnvKeys hydrates process.env from auth.json", async (t) => { delete process.env[v]; } - t.after(() => { + afterEach(() => { for (const v of envVarsToRestore) { if (origValues[v]) process.env[v] = origValues[v]; else delete process.env[v]; @@ -497,7 +497,7 @@ test("loadStoredEnvKeys does not overwrite existing env vars", async (t) => { const origBrave = process.env.BRAVE_API_KEY; process.env.BRAVE_API_KEY = "existing-env-key"; - t.after(() => { + afterEach(() => { if (origBrave) process.env.BRAVE_API_KEY = origBrave; else delete process.env.BRAVE_API_KEY; rmSync(tmp, { recursive: true, force: true }); @@ -523,7 +523,7 @@ test("deriveState returns pre-planning phase for empty .sf/ directory", async (t // Create minimal .sf/ structure with no milestones mkdirSync(join(tmp, ".sf"), { recursive: true }); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); const state = await deriveState(tmp); assert.equal( @@ -546,7 +546,7 @@ test("deriveState returns pre-planning phase when no .sf/ directory exists", asy // Use a temp dir with no .sf/ subdirectory at all const tmp = mkdtempSync(join(tmpdir(), "sf-state-nosf-")); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); // Should not throw — missing .sf/ is a valid "no project" state const state = await deriveState(tmp); @@ -563,7 +563,7 @@ test("deriveState shape is structurally complete", async (t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-state-shape-")); mkdirSync(join(tmp, ".sf"), { recursive: true }); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); const state = await deriveState(tmp); // All required fields present @@ -610,7 +610,7 @@ test("runSFDoctor completes without throwing on empty .sf/ directory", async (t) const tmp = mkdtempSync(join(tmpdir(), "sf-doctor-smoke-")); mkdirSync(join(tmp, ".sf"), { recursive: true }); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); // audit-only mode (fix: false) — should never throw const report = await runSFDoctor(tmp, { fix: false }); @@ -642,7 +642,7 @@ test("runSFDoctor issue objects have required fields", async (t) => { mkdirSync(mDir, { recursive: true }); writeFileSync(join(mDir, "M001-CONTEXT.md"), "# Context\n"); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); const report = await runSFDoctor(tmp, { fix: false }); // Should find at least one issue (missing roadmap for M001) @@ -675,7 +675,7 @@ test("runSFDoctor with fix:false never modifies the filesystem", async (t) => { const sentinelPath = join(sfDir, "SENTINEL.md"); writeFileSync(sentinelPath, "# sentinel\n"); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); await runSFDoctor(tmp, { fix: false }); assert.ok( diff --git a/src/tests/artifact-manager.test.ts b/src/tests/artifact-manager.test.ts index cb670343f..77e61f627 100644 --- a/src/tests/artifact-manager.test.ts +++ b/src/tests/artifact-manager.test.ts @@ -7,7 +7,7 @@ import assert from "node:assert/strict"; import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { ArtifactManager } from "../../packages/pi-coding-agent/src/core/artifact-manager.ts"; @@ -28,7 +28,7 @@ function makeTmpSession(): { sessionFile: string; cleanup: () => void } { test("save creates artifact file with sequential ID", (t) => { const { sessionFile, cleanup } = makeTmpSession(); - t.after(cleanup); + afterEach(cleanup); const mgr = new ArtifactManager(sessionFile); const id0 = mgr.save("output 0", "bash"); const id1 = mgr.save("output 1", "bash"); @@ -47,7 +47,7 @@ test("save creates artifact file with sequential ID", (t) => { test("artifact directory is named after session file without .jsonl", (t) => { const { sessionFile, cleanup } = makeTmpSession(); - t.after(cleanup); + afterEach(cleanup); const mgr = new ArtifactManager(sessionFile); const expectedDir = sessionFile.slice(0, -6); // strip .jsonl assert.equal(mgr.dir, expectedDir); @@ -55,7 +55,7 @@ test("artifact directory is named after session file without .jsonl", (t) => { test("artifact directory is created lazily on first write", (t) => { const { sessionFile, cleanup } = makeTmpSession(); - t.after(cleanup); + afterEach(cleanup); const mgr = new ArtifactManager(sessionFile); const artifactDir = mgr.dir; @@ -70,7 +70,7 @@ test("artifact directory is created lazily on first write", (t) => { test("exists returns true for saved artifact", (t) => { const { sessionFile, cleanup } = makeTmpSession(); - t.after(cleanup); + afterEach(cleanup); const mgr = new ArtifactManager(sessionFile); const id = mgr.save("content", "bash"); assert.ok(mgr.exists(id)); @@ -78,7 +78,7 @@ test("exists returns true for saved artifact", (t) => { test("exists returns false for missing artifact", (t) => { const { sessionFile, cleanup } = makeTmpSession(); - t.after(cleanup); + afterEach(cleanup); const mgr = new ArtifactManager(sessionFile); assert.equal(mgr.exists("999"), false); }); @@ -89,7 +89,7 @@ test("exists returns false for missing artifact", (t) => { test("allocatePath returns path without writing", (t) => { const { sessionFile, cleanup } = makeTmpSession(); - t.after(cleanup); + afterEach(cleanup); const mgr = new ArtifactManager(sessionFile); const { id, path } = mgr.allocatePath("fetch"); @@ -105,7 +105,7 @@ test("allocatePath returns path without writing", (t) => { test("new manager picks up where previous left off", (t) => { const { sessionFile, cleanup } = makeTmpSession(); - t.after(cleanup); + afterEach(cleanup); const mgr1 = new ArtifactManager(sessionFile); mgr1.save("first", "bash"); mgr1.save("second", "bash"); @@ -123,7 +123,7 @@ test("new manager picks up where previous left off", (t) => { test("listFiles returns all artifact filenames", (t) => { const { sessionFile, cleanup } = makeTmpSession(); - t.after(cleanup); + afterEach(cleanup); const mgr = new ArtifactManager(sessionFile); mgr.save("a", "bash"); mgr.save("b", "fetch"); @@ -136,7 +136,7 @@ test("listFiles returns all artifact filenames", (t) => { test("listFiles returns empty for nonexistent dir", (t) => { const { sessionFile, cleanup } = makeTmpSession(); - t.after(cleanup); + afterEach(cleanup); const mgr = new ArtifactManager(sessionFile); assert.deepEqual(mgr.listFiles(), []); }); diff --git a/src/tests/bg-shell-session-cleanup.test.ts b/src/tests/bg-shell-session-cleanup.test.ts index f5ffb078b..34e5ead71 100644 --- a/src/tests/bg-shell-session-cleanup.test.ts +++ b/src/tests/bg-shell-session-cleanup.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { cleanupAll, @@ -23,7 +23,7 @@ function isPidAlive(pid: number | undefined): boolean { const sleeperCommand = "sleep 30"; test("cleanupSessionProcesses reaps only session-scoped processes from the previous session", async (t) => { - t.after(cleanupAll); + afterEach(cleanupAll); const owned = startProcess({ command: sleeperCommand, cwd: process.cwd(), diff --git a/src/tests/blob-store.test.ts b/src/tests/blob-store.test.ts index c5a5cb63f..5d39486df 100644 --- a/src/tests/blob-store.test.ts +++ b/src/tests/blob-store.test.ts @@ -8,7 +8,7 @@ import { createHash } from "node:crypto"; import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { BlobStore, @@ -35,7 +35,7 @@ function sha256(data: Buffer): string { test("put stores data and returns correct hash", (t) => { const { dir, cleanup } = makeTmpDir(); - t.after(cleanup); + afterEach(cleanup); const store = new BlobStore(join(dir, "blobs")); const data = Buffer.from("hello world"); const result = store.put(data); @@ -47,7 +47,7 @@ test("put stores data and returns correct hash", (t) => { test("put is idempotent — same data returns same hash, no duplicate write", (t) => { const { dir, cleanup } = makeTmpDir(); - t.after(cleanup); + afterEach(cleanup); const store = new BlobStore(join(dir, "blobs")); const data = Buffer.from("duplicate test"); const r1 = store.put(data); @@ -59,7 +59,7 @@ test("put is idempotent — same data returns same hash, no duplicate write", (t test("get retrieves stored data", (t) => { const { dir, cleanup } = makeTmpDir(); - t.after(cleanup); + afterEach(cleanup); const store = new BlobStore(join(dir, "blobs")); const data = Buffer.from("retrieve me"); const { hash } = store.put(data); @@ -70,7 +70,7 @@ test("get retrieves stored data", (t) => { test("get returns null for nonexistent hash", (t) => { const { dir, cleanup } = makeTmpDir(); - t.after(cleanup); + afterEach(cleanup); const store = new BlobStore(join(dir, "blobs")); const fakeHash = "a".repeat(64); assert.equal(store.get(fakeHash), null); @@ -78,7 +78,7 @@ test("get returns null for nonexistent hash", (t) => { test("has returns true for stored blob", (t) => { const { dir, cleanup } = makeTmpDir(); - t.after(cleanup); + afterEach(cleanup); const store = new BlobStore(join(dir, "blobs")); const { hash } = store.put(Buffer.from("exists")); assert.ok(store.has(hash)); @@ -86,14 +86,14 @@ test("has returns true for stored blob", (t) => { test("has returns false for missing blob", (t) => { const { dir, cleanup } = makeTmpDir(); - t.after(cleanup); + afterEach(cleanup); const store = new BlobStore(join(dir, "blobs")); assert.equal(store.has("b".repeat(64)), false); }); test("ref property returns correct blob: URI", (t) => { const { dir, cleanup } = makeTmpDir(); - t.after(cleanup); + afterEach(cleanup); const store = new BlobStore(join(dir, "blobs")); const data = Buffer.from("ref test"); const result = store.put(data); @@ -106,7 +106,7 @@ test("ref property returns correct blob: URI", (t) => { test("get rejects non-hex hash (path traversal attempt)", (t) => { const { dir, cleanup } = makeTmpDir(); - t.after(cleanup); + afterEach(cleanup); const store = new BlobStore(join(dir, "blobs")); assert.equal(store.get("../../etc/passwd"), null); assert.equal(store.get("../../../foo"), null); @@ -115,7 +115,7 @@ test("get rejects non-hex hash (path traversal attempt)", (t) => { test("has rejects non-hex hash", (t) => { const { dir, cleanup } = makeTmpDir(); - t.after(cleanup); + afterEach(cleanup); const store = new BlobStore(join(dir, "blobs")); assert.equal(store.has("../../etc/passwd"), false); assert.equal(store.has("short"), false); @@ -124,7 +124,7 @@ test("has rejects non-hex hash", (t) => { test("get rejects hash with wrong length", (t) => { const { dir, cleanup } = makeTmpDir(); - t.after(cleanup); + afterEach(cleanup); const store = new BlobStore(join(dir, "blobs")); assert.equal(store.get("a".repeat(63)), null); // too short assert.equal(store.get("a".repeat(65)), null); // too long @@ -162,7 +162,7 @@ test("parseBlobRef rejects invalid hash format", () => { test("externalizeImageData stores base64 and returns blob ref", (t) => { const { dir, cleanup } = makeTmpDir(); - t.after(cleanup); + afterEach(cleanup); const store = new BlobStore(join(dir, "blobs")); const base64 = Buffer.from("image bytes").toString("base64"); const ref = externalizeImageData(store, base64); @@ -173,7 +173,7 @@ test("externalizeImageData stores base64 and returns blob ref", (t) => { test("externalizeImageData passes through existing blob refs", (t) => { const { dir, cleanup } = makeTmpDir(); - t.after(cleanup); + afterEach(cleanup); const store = new BlobStore(join(dir, "blobs")); const existingRef = `blob:sha256:${"c".repeat(64)}`; assert.equal(externalizeImageData(store, existingRef), existingRef); @@ -181,7 +181,7 @@ test("externalizeImageData passes through existing blob refs", (t) => { test("resolveImageData round-trips with externalizeImageData", (t) => { const { dir, cleanup } = makeTmpDir(); - t.after(cleanup); + afterEach(cleanup); const store = new BlobStore(join(dir, "blobs")); const base64 = Buffer.from("round trip test").toString("base64"); const ref = externalizeImageData(store, base64); @@ -192,14 +192,14 @@ test("resolveImageData round-trips with externalizeImageData", (t) => { test("resolveImageData returns non-ref strings unchanged", (t) => { const { dir, cleanup } = makeTmpDir(); - t.after(cleanup); + afterEach(cleanup); const store = new BlobStore(join(dir, "blobs")); assert.equal(resolveImageData(store, "plain text"), "plain text"); }); test("resolveImageData returns ref unchanged when blob is missing", (t) => { const { dir, cleanup } = makeTmpDir(); - t.after(cleanup); + afterEach(cleanup); const store = new BlobStore(join(dir, "blobs")); const missingRef = `blob:sha256:${"d".repeat(64)}`; assert.equal(resolveImageData(store, missingRef), missingRef); diff --git a/src/tests/create-sf-extension-paths.test.ts b/src/tests/create-sf-extension-paths.test.ts index 552c34c42..001d22a14 100644 --- a/src/tests/create-sf-extension-paths.test.ts +++ b/src/tests/create-sf-extension-paths.test.ts @@ -40,11 +40,11 @@ const docsToCheck: { file: string; label: string }[] = [ test("create-sf-extension docs use ~/.pi/agent/extensions/ for community extensions", async (t) => { if (!skillDirExists) { - t.skip("create-sf-extension skill not present in this repo"); + return; // skip: "create-sf-extension skill not present in this repo"; return; } for (const { file, label } of docsToCheck) { - await t.test( + test( `${label} references ~/.pi/agent/extensions/ for global extensions`, () => { const content = readSkillFile(file); @@ -61,11 +61,11 @@ test("create-sf-extension docs use ~/.pi/agent/extensions/ for community extensi test("create-sf-extension docs do NOT direct users to install in ~/.sf/agent/extensions/", async (t) => { if (!skillDirExists) { - t.skip("create-sf-extension skill not present in this repo"); + return; // skip: "create-sf-extension skill not present in this repo"; return; } for (const { file, label } of docsToCheck) { - await t.test( + test( `${label} does not tell users to place extensions in ~/.sf/agent/extensions/`, () => { const content = readSkillFile(file); diff --git a/src/tests/extension-discovery.test.ts b/src/tests/extension-discovery.test.ts index 6b0128657..45ec6176b 100644 --- a/src/tests/extension-discovery.test.ts +++ b/src/tests/extension-discovery.test.ts @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import { mkdirSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test, describe } from 'vitest'; +import { test, describe, afterEach } from 'vitest'; import { discoverExtensionEntryPaths, resolveExtensionEntries, @@ -20,7 +20,7 @@ function makeTempDir(): string { describe("resolveExtensionEntries", () => { test("returns index.ts when no package.json exists", (t) => { const dir = makeTempDir(); - t.after(() => rmSync(dir, { recursive: true, force: true })); + afterEach(() => rmSync(dir, { recursive: true, force: true })); writeFileSync(join(dir, "index.ts"), "export default function() {}"); const entries = resolveExtensionEntries(dir); assert.equal(entries.length, 1); @@ -29,7 +29,7 @@ describe("resolveExtensionEntries", () => { test("returns index.js when no package.json and no index.ts", (t) => { const dir = makeTempDir(); - t.after(() => rmSync(dir, { recursive: true, force: true })); + afterEach(() => rmSync(dir, { recursive: true, force: true })); writeFileSync(join(dir, "index.js"), "module.exports = function() {}"); const entries = resolveExtensionEntries(dir); assert.equal(entries.length, 1); @@ -38,7 +38,7 @@ describe("resolveExtensionEntries", () => { test("returns declared extensions from pi.extensions array", (t) => { const dir = makeTempDir(); - t.after(() => rmSync(dir, { recursive: true, force: true })); + afterEach(() => rmSync(dir, { recursive: true, force: true })); writeFileSync( join(dir, "package.json"), JSON.stringify({ @@ -54,7 +54,7 @@ describe("resolveExtensionEntries", () => { test("returns empty array when pi manifest has no extensions (library opt-out)", (t) => { const dir = makeTempDir(); - t.after(() => rmSync(dir, { recursive: true, force: true })); + afterEach(() => rmSync(dir, { recursive: true, force: true })); writeFileSync( join(dir, "package.json"), JSON.stringify({ @@ -73,7 +73,7 @@ describe("resolveExtensionEntries", () => { test("returns empty array when pi.extensions is an empty array", (t) => { const dir = makeTempDir(); - t.after(() => rmSync(dir, { recursive: true, force: true })); + afterEach(() => rmSync(dir, { recursive: true, force: true })); writeFileSync( join(dir, "package.json"), JSON.stringify({ @@ -87,7 +87,7 @@ describe("resolveExtensionEntries", () => { test("falls back to index.ts when package.json has no pi field", (t) => { const dir = makeTempDir(); - t.after(() => rmSync(dir, { recursive: true, force: true })); + afterEach(() => rmSync(dir, { recursive: true, force: true })); writeFileSync( join(dir, "package.json"), JSON.stringify({ name: "some-pkg" }), @@ -102,7 +102,7 @@ describe("resolveExtensionEntries", () => { describe("discoverExtensionEntryPaths", () => { test("ignores TypeScript declaration files in top-level extensions directory", (t) => { const root = makeTempDir(); - t.after(() => rmSync(root, { recursive: true, force: true })); + afterEach(() => rmSync(root, { recursive: true, force: true })); writeFileSync(join(root, "ask-user-questions.d.ts"), "export {}"); writeFileSync(join(root, "get-secrets-from-user.d.ts"), "export {}"); writeFileSync(join(root, "real-extension.ts"), "export default function() {}"); @@ -116,7 +116,7 @@ describe("discoverExtensionEntryPaths", () => { test("skips library directories with pi: {} opt-out", (t) => { const root = makeTempDir(); - t.after(() => rmSync(root, { recursive: true, force: true })); + afterEach(() => rmSync(root, { recursive: true, force: true })); // Real extension const extDir = join(root, "my-ext"); mkdirSync(extDir); diff --git a/src/tests/google-search-auth.repro.test.ts b/src/tests/google-search-auth.repro.test.ts index 7a497be9b..b867cb993 100644 --- a/src/tests/google-search-auth.repro.test.ts +++ b/src/tests/google-search-auth.repro.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import googleSearchExtension from "../resources/extensions/google-search/index.js"; function createMockPI() { @@ -78,7 +78,7 @@ test("fix: google-search uses OAuth if GEMINI_API_KEY is missing", async (t) => }; }; - t.after(() => { + afterEach(() => { global.fetch = originalFetch; restoreEnv("GEMINI_API_KEY", originalKey); restoreEnv("GOOGLE_GENERATIVE_AI_API_KEY", originalAlias); @@ -115,7 +115,7 @@ test("google-search warns if NO authentication is present", async (t) => { delete process.env.GEMINI_API_KEY; delete process.env.GOOGLE_GENERATIVE_AI_API_KEY; - t.after(() => { + afterEach(() => { restoreEnv("GEMINI_API_KEY", originalKey); restoreEnv("GOOGLE_GENERATIVE_AI_API_KEY", originalAlias); }); @@ -154,7 +154,7 @@ test("google-search uses GEMINI_API_KEY if present (precedence)", async (t) => { process.env.GEMINI_API_KEY = "mock-api-key"; process.env.GOOGLE_GENERATIVE_AI_API_KEY = "mock-alias-key"; - t.after(() => { + afterEach(() => { restoreEnv("GEMINI_API_KEY", originalKey); restoreEnv("GOOGLE_GENERATIVE_AI_API_KEY", originalAlias); }); @@ -190,7 +190,7 @@ test("google-search accepts GOOGLE_GENERATIVE_AI_API_KEY", async (t) => { delete process.env.GEMINI_API_KEY; process.env.GOOGLE_GENERATIVE_AI_API_KEY = "mock-alias-key"; - t.after(() => { + afterEach(() => { restoreEnv("GEMINI_API_KEY", originalKey); restoreEnv("GOOGLE_GENERATIVE_AI_API_KEY", originalAlias); }); diff --git a/src/tests/google-search-oauth-shape.test.ts b/src/tests/google-search-oauth-shape.test.ts index 186bd0b1c..1d2be7358 100644 --- a/src/tests/google-search-oauth-shape.test.ts +++ b/src/tests/google-search-oauth-shape.test.ts @@ -11,7 +11,7 @@ */ import assert from "node:assert/strict"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import googleSearchExtension from "../resources/extensions/google-search/index.js"; // ── Helpers ───────────────────────────────────────────────────────────────── @@ -92,7 +92,7 @@ test("#2963: OAuth fallback URL must include ?alt=sse query parameter", async (t return { ok: true, text: async () => makeOkSSEBody() }; }; - t.after(() => { + afterEach(() => { global.fetch = originalFetch; if (originalKey !== undefined) process.env.GEMINI_API_KEY = originalKey; else delete process.env.GEMINI_API_KEY; @@ -134,7 +134,7 @@ test("#2963: OAuth fallback body must include userAgent field", async (t) => { return { ok: true, text: async () => makeOkSSEBody() }; }; - t.after(() => { + afterEach(() => { global.fetch = originalFetch; if (originalKey !== undefined) process.env.GEMINI_API_KEY = originalKey; else delete process.env.GEMINI_API_KEY; @@ -178,7 +178,7 @@ test("#2963: OAuth fallback body must contain google_search tool in correct form return { ok: true, text: async () => makeOkSSEBody() }; }; - t.after(() => { + afterEach(() => { global.fetch = originalFetch; if (originalKey !== undefined) process.env.GEMINI_API_KEY = originalKey; else delete process.env.GEMINI_API_KEY; @@ -223,7 +223,7 @@ test("#2963: OAuth fallback body has correct top-level structure", async (t) => return { ok: true, text: async () => makeOkSSEBody() }; }; - t.after(() => { + afterEach(() => { global.fetch = originalFetch; if (originalKey !== undefined) process.env.GEMINI_API_KEY = originalKey; else delete process.env.GEMINI_API_KEY; diff --git a/src/tests/integration/e2e-headless.test.ts b/src/tests/integration/e2e-headless.test.ts index 44ab0ff4e..e4bdca582 100644 --- a/src/tests/integration/e2e-headless.test.ts +++ b/src/tests/integration/e2e-headless.test.ts @@ -21,7 +21,7 @@ import { spawn } from "node:child_process"; import { existsSync, mkdirSync, mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; const projectRoot = process.cwd(); const loaderPath = join(projectRoot, "dist", "loader.js"); @@ -167,7 +167,7 @@ function assertNoCrashMarkers(output: string): void { test("headless --output-format json emits a single HeadlessJsonResult on stdout", async (t) => { const tmpDir = createTempWithSf("sf-e2e-json-batch-"); - t.after(() => { + afterEach(() => { rmSync(tmpDir, { recursive: true, force: true }); }); @@ -262,7 +262,7 @@ test("headless --output-format json emits a single HeadlessJsonResult on stdout" test("headless exits with code 11 after SIGINT", async (t) => { const tmpDir = createTempWithSf("sf-e2e-sigint-"); - t.after(() => { + afterEach(() => { rmSync(tmpDir, { recursive: true, force: true }); }); @@ -339,7 +339,7 @@ test("headless exits with code 11 after SIGINT", async (t) => { test("headless --output-format stream-json emits NDJSON on stdout", async (t) => { const tmpDir = createTempWithSf("sf-e2e-stream-json-"); - t.after(() => { + afterEach(() => { rmSync(tmpDir, { recursive: true, force: true }); }); @@ -401,7 +401,7 @@ test("headless --output-format stream-json emits NDJSON on stdout", async (t) => test("headless --resume with nonexistent ID exits 1 with descriptive error", async (t) => { const tmpDir = createTempWithSf("sf-e2e-resume-bad-"); - t.after(() => { + afterEach(() => { rmSync(tmpDir, { recursive: true, force: true }); }); @@ -441,7 +441,7 @@ test("headless --resume with nonexistent ID exits 1 with descriptive error", asy test("headless --output-format with invalid value exits 1", async (t) => { const tmpDir = createTempWithSf("sf-e2e-bad-format-"); - t.after(() => { + afterEach(() => { rmSync(tmpDir, { recursive: true, force: true }); }); diff --git a/src/tests/integration/e2e-smoke.test.ts b/src/tests/integration/e2e-smoke.test.ts index e97ad0384..a7aa02828 100644 --- a/src/tests/integration/e2e-smoke.test.ts +++ b/src/tests/integration/e2e-smoke.test.ts @@ -19,7 +19,7 @@ import { execFileSync, spawn } from "node:child_process"; import { existsSync, mkdirSync, mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; const projectRoot = process.cwd(); const loaderPath = join(projectRoot, "dist", "loader.js"); @@ -429,7 +429,7 @@ test("sf -h is equivalent to --help", async () => { test("sf headless without .sf/ directory exits 1 with clean error", async (t) => { const tmpDir = mkdtempSync(join(tmpdir(), "sf-e2e-no-sf-")); - t.after(() => { + afterEach(() => { rmSync(tmpDir, { recursive: true, force: true }); }); @@ -454,7 +454,7 @@ test("sf headless without .sf/ directory exits 1 with clean error", async (t) => test("sf headless new-milestone without --context exits 1", async (t) => { const tmpDir = mkdtempSync(join(tmpdir(), "sf-e2e-no-ctx-")); - t.after(() => { + afterEach(() => { rmSync(tmpDir, { recursive: true, force: true }); }); @@ -484,7 +484,7 @@ test("sf headless new-milestone without --context exits 1", async (t) => { test("sf headless --timeout with invalid value exits 1", async (t) => { const tmpDir = mkdtempSync(join(tmpdir(), "sf-e2e-bad-timeout-")); - t.after(() => { + afterEach(() => { rmSync(tmpDir, { recursive: true, force: true }); }); @@ -514,7 +514,7 @@ test("sf headless --timeout with invalid value exits 1", async (t) => { test("sf headless --timeout with negative value exits 1", async (t) => { const tmpDir = mkdtempSync(join(tmpdir(), "sf-e2e-neg-timeout-")); - t.after(() => { + afterEach(() => { rmSync(tmpDir, { recursive: true, force: true }); }); @@ -540,7 +540,7 @@ test("sf headless --timeout with negative value exits 1", async (t) => { test("sf headless query returns JSON from the built CLI", async (t) => { const tmpDir = createTempGitRepo("sf-e2e-query-"); - t.after(() => { + afterEach(() => { rmSync(tmpDir, { recursive: true, force: true }); }); @@ -568,7 +568,7 @@ test("sf headless query returns JSON from the built CLI", async (t) => { test("sf worktree list loads the built worktree CLI without module errors", async (t) => { const tmpDir = createTempGitRepo("sf-e2e-worktree-"); - t.after(() => { + afterEach(() => { rmSync(tmpDir, { recursive: true, force: true }); }); diff --git a/src/tests/integration/pack-install.test.ts b/src/tests/integration/pack-install.test.ts index b93cea060..edde0904e 100644 --- a/src/tests/integration/pack-install.test.ts +++ b/src/tests/integration/pack-install.test.ts @@ -22,7 +22,7 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { createGunzip } from "node:zlib"; const projectRoot = process.cwd(); @@ -147,7 +147,7 @@ test("npm pack produces tarball with required files", async (t) => { assert.ok(existsSync(tarballPath), "tarball created"); - t.after(() => { + afterEach(() => { rmSync(tarballPath, { force: true }); rmSync(sandbox.rootDir, { recursive: true, force: true }); }); @@ -214,7 +214,7 @@ test("tarball installs and sf binary resolves", async (t) => { const sandbox = createNpmSandbox("sf-install-test-"); const tarballPath = packTarball(sandbox); - t.after(() => { + afterEach(() => { rmSync(tarballPath, { force: true }); rmSync(sandbox.rootDir, { recursive: true, force: true }); }); @@ -338,7 +338,7 @@ test("sf exits early with a clear message when synced resources are newer than t JSON.stringify({ sfVersion: "999.0.0" }), ); - t.after(() => { + afterEach(() => { rmSync(fakeHome, { recursive: true, force: true }); }); diff --git a/src/tests/integration/web-auto-dashboard-lock-reconciliation.test.ts b/src/tests/integration/web-auto-dashboard-lock-reconciliation.test.ts index f102314e5..284edb21e 100644 --- a/src/tests/integration/web-auto-dashboard-lock-reconciliation.test.ts +++ b/src/tests/integration/web-auto-dashboard-lock-reconciliation.test.ts @@ -15,7 +15,7 @@ import assert from "node:assert/strict"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { collectAuthoritativeAutoDashboardData } from "../../web/auto-dashboard-service.ts"; @@ -86,7 +86,7 @@ const INACTIVE_PAYLOAD = { test("#2705 regression: subprocess reports active=false but session lock exists with live PID → reconcile to active=true", async (t) => { const fixture = makeTempFixture(); - t.after(() => fixture.cleanup()); + afterEach(() => fixture.cleanup()); const modulePath = writeAutoModule(fixture.projectCwd, INACTIVE_PAYLOAD); @@ -122,7 +122,7 @@ test("#2705 regression: subprocess reports active=false but session lock exists test("#2705: subprocess reports active=false and no session lock → remains inactive", async (t) => { const fixture = makeTempFixture(); - t.after(() => fixture.cleanup()); + afterEach(() => fixture.cleanup()); const modulePath = writeAutoModule(fixture.projectCwd, INACTIVE_PAYLOAD); @@ -144,7 +144,7 @@ test("#2705: subprocess reports active=false and no session lock → remains ina test("#2705: subprocess reports active=false but paused-session.json exists → reconcile to paused=true", async (t) => { const fixture = makeTempFixture(); - t.after(() => fixture.cleanup()); + afterEach(() => fixture.cleanup()); const modulePath = writeAutoModule(fixture.projectCwd, INACTIVE_PAYLOAD); @@ -176,7 +176,7 @@ test("#2705: subprocess reports active=false but paused-session.json exists → test("#2705: subprocess reports active=true → no reconciliation needed", async (t) => { const fixture = makeTempFixture(); - t.after(() => fixture.cleanup()); + afterEach(() => fixture.cleanup()); const activePayload = { active: true, @@ -209,7 +209,7 @@ test("#2705: subprocess reports active=true → no reconciliation needed", async test("#2705: session lock exists but PID is dead → remains inactive (stale lock)", async (t) => { const fixture = makeTempFixture(); - t.after(() => fixture.cleanup()); + afterEach(() => fixture.cleanup()); const modulePath = writeAutoModule(fixture.projectCwd, INACTIVE_PAYLOAD); diff --git a/src/tests/integration/web-bridge-contract.test.ts b/src/tests/integration/web-bridge-contract.test.ts index fec28b12d..a6298c302 100644 --- a/src/tests/integration/web-bridge-contract.test.ts +++ b/src/tests/integration/web-bridge-contract.test.ts @@ -5,7 +5,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { PassThrough } from "node:stream"; import { StringDecoder } from "node:string_decoder"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; const repoRoot = process.cwd(); const bridge = await import("../../web/bridge-service.ts"); @@ -347,7 +347,7 @@ test("/api/boot returns current-project workspace data, resumable sessions, onbo getOnboardingNeeded: () => false, }); - t.after(async () => { + afterEach(async () => { await bridge.resetBridgeServiceForTests(); fixture.cleanup(); }); @@ -449,7 +449,7 @@ test("/api/boot uses the authoritative auto helper by default and stays snapshot getOnboardingNeeded: () => false, }); - t.after(async () => { + afterEach(async () => { await bridge.resetBridgeServiceForTests(); fixture.cleanup(); }); @@ -544,7 +544,7 @@ test("bridge service is a singleton for the project runtime and /api/session/com getOnboardingNeeded: () => false, }); - t.after(async () => { + afterEach(async () => { await bridge.resetBridgeServiceForTests(); fixture.cleanup(); }); @@ -627,7 +627,7 @@ test("/api/session/events streams bridge status, agent events, and extension_ui_ getOnboardingNeeded: () => false, }); - t.after(async () => { + afterEach(async () => { await bridge.resetBridgeServiceForTests(); fixture.cleanup(); }); @@ -734,7 +734,7 @@ test("bridge command/runtime failures are inspectable and redact secret material getOnboardingNeeded: () => false, }); - t.after(async () => { + afterEach(async () => { await bridge.resetBridgeServiceForTests(); onboarding.resetOnboardingServiceForTests(); fixture.cleanup(); @@ -823,7 +823,7 @@ test("/api/boot lists sessions from the real filesystem via readdirSync (#1936)" getOnboardingNeeded: () => false, }); - t.after(async () => { + afterEach(async () => { await bridge.resetBridgeServiceForTests(); fixture.cleanup(); }); diff --git a/src/tests/integration/web-bridge-terminal-contract.test.ts b/src/tests/integration/web-bridge-terminal-contract.test.ts index 7ef107148..70507ef52 100644 --- a/src/tests/integration/web-bridge-terminal-contract.test.ts +++ b/src/tests/integration/web-bridge-terminal-contract.test.ts @@ -5,7 +5,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { PassThrough } from "node:stream"; import { StringDecoder } from "node:string_decoder"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; const repoRoot = process.cwd(); const bridge = await import("../../web/bridge-service.ts"); @@ -242,7 +242,7 @@ test("/api/bridge-terminal/stream attaches to the main bridge runtime and forwar spawn: harness.spawn, }); - t.after(async () => { + afterEach(async () => { await bridge.resetBridgeServiceForTests(); fixture.cleanup(); }); @@ -330,7 +330,7 @@ test("bridge-terminal input and resize routes forward browser terminal traffic o spawn: harness.spawn, }); - t.after(async () => { + afterEach(async () => { await bridge.resetBridgeServiceForTests(); fixture.cleanup(); }); @@ -415,7 +415,7 @@ test("session_state_changed from the native main-session TUI refreshes bridge st spawn: harness.spawn, }); - t.after(async () => { + afterEach(async () => { await bridge.resetBridgeServiceForTests(); fixture.cleanup(); }); diff --git a/src/tests/integration/web-cli-entry.test.ts b/src/tests/integration/web-cli-entry.test.ts index 24b3f30c0..1fdf6d1f0 100644 --- a/src/tests/integration/web-cli-entry.test.ts +++ b/src/tests/integration/web-cli-entry.test.ts @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { pathToFileURL } from "node:url"; const { resolveSfCliEntry } = await import("../../web/cli-entry.ts"); @@ -24,7 +24,7 @@ test("resolveSfCliEntry prefers the built loader for packaged standalone interac "src/resources/extensions/sf/tests/resolve-ts.mjs", ]); - t.after(() => { + afterEach(() => { rmSync(packageRoot, { recursive: true, force: true }); }); @@ -50,7 +50,7 @@ test("resolveSfCliEntry prefers the source loader for source-dev interactive ses "src/resources/extensions/sf/tests/resolve-ts.mjs", ]); - t.after(() => { + afterEach(() => { rmSync(packageRoot, { recursive: true, force: true }); }); @@ -87,7 +87,7 @@ test("resolveSfCliEntry prefers the source loader for source-dev interactive ses test("resolveSfCliEntry appends rpc arguments for bridge sessions", (t) => { const packageRoot = makeFixture(["dist/loader.js"]); - t.after(() => { + afterEach(() => { rmSync(packageRoot, { recursive: true, force: true }); }); diff --git a/src/tests/integration/web-command-parity-contract.test.ts b/src/tests/integration/web-command-parity-contract.test.ts index e483dce09..c00c97f3a 100644 --- a/src/tests/integration/web-command-parity-contract.test.ts +++ b/src/tests/integration/web-command-parity-contract.test.ts @@ -119,7 +119,7 @@ test("authoritative built-ins never fall through to prompt/follow_up in browser for (const builtin of BUILTIN_SLASH_COMMANDS) { const expectedKind = EXPECTED_BUILTIN_OUTCOMES.get(builtin.name); const outcome = dispatchBrowserSlashCommand(`/${builtin.name}`); - await t.test(`/${builtin.name} -> ${expectedKind}`, () => { + test(`/${builtin.name} -> ${expectedKind}`, () => { assert.ok( expectedKind, `missing explicit browser expectation for /${builtin.name}`, @@ -137,7 +137,7 @@ test("authoritative built-ins never fall through to prompt/follow_up in browser }); if (outcome.kind === "reject") { - await t.test(`/${builtin.name} reject notice is browser-visible`, () => { + test(`/${builtin.name} reject notice is browser-visible`, () => { const outcome = dispatchBrowserSlashCommand(`/${builtin.name}`); const notice = getBrowserSlashCommandTerminalNotice(outcome); assert.ok( @@ -164,25 +164,25 @@ test("authoritative built-ins never fall through to prompt/follow_up in browser }); test("browser-local aliases and legacy helpers stay explicit", async (t) => { - await t.test("/state dispatches to rpc get_state", () => { + test("/state dispatches to rpc get_state", () => { const outcome = dispatchBrowserSlashCommand("/state"); assert.equal(outcome.kind, "rpc"); assert.equal((outcome as any).command.type, "get_state"); }); - await t.test("/new-session dispatches to rpc new_session", () => { + test("/new-session dispatches to rpc new_session", () => { const outcome = dispatchBrowserSlashCommand("/new-session"); assert.equal(outcome.kind, "rpc"); assert.equal((outcome as any).command.type, "new_session"); }); - await t.test("/refresh dispatches to local refresh_workspace", () => { + test("/refresh dispatches to local refresh_workspace", () => { const outcome = dispatchBrowserSlashCommand("/refresh"); assert.equal(outcome.kind, "local"); assert.equal((outcome as any).action, "refresh_workspace"); }); - await t.test("/clear dispatches to local clear_terminal", () => { + test("/clear dispatches to local clear_terminal", () => { const outcome = dispatchBrowserSlashCommand("/clear"); assert.equal(outcome.kind, "local"); assert.equal((outcome as any).action, "clear_terminal"); @@ -224,11 +224,11 @@ test("registered SF command roots stay on the prompt/extension path", async () = }); test("current SF command family samples dispatch to correct outcomes after S02", async (t) => { - await t.test("/sf (bare) still passes through to bridge", () => { + test("/sf (bare) still passes through to bridge", () => { assertPromptPassthrough("/sf"); }); - await t.test("/sf status now dispatches to surface", () => { + test("/sf status now dispatches to surface", () => { const outcome = dispatchBrowserSlashCommand("/sf status"); assert.equal( outcome.kind, @@ -238,7 +238,7 @@ test("current SF command family samples dispatch to correct outcomes after S02", assert.equal(outcome.surface, "sf-status"); }); - await t.test( + test( "/worktree list, /wt list, /kill, /exit still pass through", () => { assertPromptPassthrough("/worktree list"); @@ -248,7 +248,7 @@ test("current SF command family samples dispatch to correct outcomes after S02", }, ); - await t.test( + test( "/sf status dispatches to surface regardless of streaming state", () => { const streaming = dispatchBrowserSlashCommand("/sf status", { @@ -317,7 +317,7 @@ test("every registered /sf subcommand has an explicit browser dispatch outcome", ); for (const [subcommand, expectedKind] of EXPECTED_SF_OUTCOMES) { - await t.test(`/sf ${subcommand} -> ${expectedKind}`, () => { + test(`/sf ${subcommand} -> ${expectedKind}`, () => { const outcome = dispatchBrowserSlashCommand(`/sf ${subcommand}`); assert.equal( outcome.kind, @@ -327,7 +327,7 @@ test("every registered /sf subcommand has an explicit browser dispatch outcome", }); if (expectedKind === "surface") { - await t.test(`/sf ${subcommand} opens sf-${subcommand} surface`, () => { + test(`/sf ${subcommand} opens sf-${subcommand} surface`, () => { const outcome = dispatchBrowserSlashCommand(`/sf ${subcommand}`) as any; assert.equal( outcome.surface, @@ -338,7 +338,7 @@ test("every registered /sf subcommand has an explicit browser dispatch outcome", } if (expectedKind === "prompt") { - await t.test(`/sf ${subcommand} preserves exact input text`, () => { + test(`/sf ${subcommand} preserves exact input text`, () => { const outcome = dispatchBrowserSlashCommand(`/sf ${subcommand}`) as any; assert.equal( outcome.command.message, @@ -349,7 +349,7 @@ test("every registered /sf subcommand has an explicit browser dispatch outcome", } if (expectedKind === "local") { - await t.test(`/sf ${subcommand} dispatches to sf_help action`, () => { + test(`/sf ${subcommand} dispatches to sf_help action`, () => { const outcome = dispatchBrowserSlashCommand(`/sf ${subcommand}`) as any; assert.equal( outcome.action, @@ -360,7 +360,7 @@ test("every registered /sf subcommand has an explicit browser dispatch outcome", } if (expectedKind === "view-navigate") { - await t.test( + test( `/sf ${subcommand} navigates to the ${subcommand} view`, () => { const outcome = dispatchBrowserSlashCommand( @@ -378,19 +378,19 @@ test("every registered /sf subcommand has an explicit browser dispatch outcome", }); test("SF dispatch edge cases", async (t) => { - await t.test("/sf (bare, no subcommand) passes through to bridge", () => { + test("/sf (bare, no subcommand) passes through to bridge", () => { const outcome = dispatchBrowserSlashCommand("/sf"); assert.equal(outcome.kind, "prompt"); assert.equal(outcome.command.message, "/sf"); }); - await t.test("/sf help dispatches to local sf_help action", () => { + test("/sf help dispatches to local sf_help action", () => { const outcome = dispatchBrowserSlashCommand("/sf help"); assert.equal(outcome.kind, "local"); assert.equal(outcome.action, "sf_help"); }); - await t.test("/sf unknown-xyz passes through to bridge", () => { + test("/sf unknown-xyz passes through to bridge", () => { const outcome = dispatchBrowserSlashCommand("/sf unknown-xyz"); assert.equal( outcome.kind, @@ -409,7 +409,7 @@ test("SF dispatch edge cases", async (t) => { ); }); - await t.test("/export is built-in session export, not sf-export", () => { + test("/export is built-in session export, not sf-export", () => { const outcome = dispatchBrowserSlashCommand("/export"); assert.equal(outcome.kind, "surface"); assert.equal( @@ -419,7 +419,7 @@ test("SF dispatch edge cases", async (t) => { ); }); - await t.test( + test( "/sf export is SF milestone export, distinct from built-in /export", () => { const outcome = dispatchBrowserSlashCommand("/sf export"); @@ -432,7 +432,7 @@ test("SF dispatch edge cases", async (t) => { }, ); - await t.test("/sf forensics detailed preserves sub-args", () => { + test("/sf forensics detailed preserves sub-args", () => { const outcome = dispatchBrowserSlashCommand("/sf forensics detailed"); assert.equal(outcome.kind, "surface"); assert.equal(outcome.surface, "sf-forensics"); @@ -443,14 +443,14 @@ test("SF dispatch edge cases", async (t) => { ); }); - await t.test("SF surface commands produce system terminal notice", () => { + test("SF surface commands produce system terminal notice", () => { const outcome = dispatchBrowserSlashCommand("/sf status"); const notice = getBrowserSlashCommandTerminalNotice(outcome); assert.ok(notice, "surface outcome should produce a terminal notice"); assert.equal(notice.type, "system"); }); - await t.test("SF passthrough commands produce no terminal notice", () => { + test("SF passthrough commands produce no terminal notice", () => { const outcome = dispatchBrowserSlashCommand("/sf auto"); const notice = getBrowserSlashCommandTerminalNotice(outcome); assert.equal( @@ -473,7 +473,7 @@ test("every SF surface dispatches through the contract wiring end-to-end", async ); for (const [subcommand] of sfSurfaces) { - await t.test( + test( `/sf ${subcommand} -> dispatch -> open request -> surface state`, () => { const outcome = dispatchBrowserSlashCommand(`/sf ${subcommand}`); @@ -663,7 +663,7 @@ test("session-oriented slash surfaces open the correct sections and carry action ] as const; for (const scenario of cases) { - await t.test(scenario.input, () => { + test(scenario.input, () => { const outcome = dispatchBrowserSlashCommand(scenario.input); assert.equal(outcome.kind, "surface"); @@ -799,7 +799,7 @@ test("session browser action state keeps resume and rename mutations inspectable test("deferred built-ins expose explicit rejection reasons in the browser", async (t) => { for (const commandName of DEFERRED_BROWSER_REJECTS) { - await t.test(`/${commandName}`, () => { + test(`/${commandName}`, () => { const outcome = dispatchBrowserSlashCommand(`/${commandName}`); assert.equal(outcome.kind, "reject"); assert.equal( diff --git a/src/tests/integration/web-live-interaction-contract.test.ts b/src/tests/integration/web-live-interaction-contract.test.ts index 06936c41b..ba2dfb8e3 100644 --- a/src/tests/integration/web-live-interaction-contract.test.ts +++ b/src/tests/integration/web-live-interaction-contract.test.ts @@ -5,7 +5,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { PassThrough } from "node:stream"; import { StringDecoder } from "node:string_decoder"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; const repoRoot = process.cwd(); const bridge = await import("../../web/bridge-service.ts"); @@ -464,7 +464,7 @@ test("(a) SSE emits extension_ui_request with method 'select' → typed payload setupBridge(harness, fixture); - t.after(async () => { + afterEach(async () => { await bridge.resetBridgeServiceForTests(); onboarding.resetOnboardingServiceForTests(); fixture.cleanup(); @@ -582,7 +582,7 @@ test("(c) Responding to a UI request posts extension_ui_response with correct id setupBridge(harness, fixture); - t.after(async () => { + afterEach(async () => { await bridge.resetBridgeServiceForTests(); onboarding.resetOnboardingServiceForTests(); fixture.cleanup(); @@ -637,7 +637,7 @@ test("(d) Dismissing a UI request posts cancelled: true and removes from pending setupBridge(harness, fixture); - t.after(async () => { + afterEach(async () => { await bridge.resetBridgeServiceForTests(); onboarding.resetOnboardingServiceForTests(); fixture.cleanup(); @@ -743,7 +743,7 @@ test("(e) SSE emits message_update with text delta → streamingAssistantText ac setupBridge(harness, fixture); - t.after(async () => { + afterEach(async () => { await bridge.resetBridgeServiceForTests(); onboarding.resetOnboardingServiceForTests(); fixture.cleanup(); @@ -1036,7 +1036,7 @@ test("(h) steer and abort commands post the correct RPC command type", async (t) setupBridge(harness, fixture); - t.after(async () => { + afterEach(async () => { await bridge.resetBridgeServiceForTests(); onboarding.resetOnboardingServiceForTests(); fixture.cleanup(); @@ -1245,7 +1245,7 @@ test("(session-controls) browser session RPCs round-trip through /api/session/co setupBridge(harness, fixture); - t.after(async () => { + afterEach(async () => { await bridge.resetBridgeServiceForTests(); onboarding.resetOnboardingServiceForTests(); fixture.cleanup(); diff --git a/src/tests/integration/web-live-state-contract.test.ts b/src/tests/integration/web-live-state-contract.test.ts index a985bd36e..23155a579 100644 --- a/src/tests/integration/web-live-state-contract.test.ts +++ b/src/tests/integration/web-live-state-contract.test.ts @@ -5,7 +5,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { PassThrough } from "node:stream"; import { StringDecoder } from "node:string_decoder"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; const repoRoot = process.cwd(); const bridge = await import("../../web/bridge-service.ts"); @@ -409,7 +409,7 @@ test("/api/session/events exposes explicit live_state_invalidation events for ag setupBridge(harness, fixture); - t.after(async () => { + afterEach(async () => { await bridge.resetBridgeServiceForTests(); onboarding.resetOnboardingServiceForTests(); fixture.cleanup(); @@ -589,7 +589,7 @@ test("workspace cache only busts on real boundaries and session mutations emit t }, }); - t.after(async () => { + afterEach(async () => { await bridge.resetBridgeServiceForTests(); onboarding.resetOnboardingServiceForTests(); fixture.cleanup(); @@ -776,7 +776,7 @@ test("turn_end events invalidate workspace so milestones list reflects current s }, }); - t.after(async () => { + afterEach(async () => { await bridge.resetBridgeServiceForTests(); onboarding.resetOnboardingServiceForTests(); fixture.cleanup(); diff --git a/src/tests/integration/web-mode-assembled.test.ts b/src/tests/integration/web-mode-assembled.test.ts index 9589b8522..537469be9 100644 --- a/src/tests/integration/web-mode-assembled.test.ts +++ b/src/tests/integration/web-mode-assembled.test.ts @@ -5,7 +5,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { PassThrough } from "node:stream"; import { StringDecoder } from "node:string_decoder"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; const repoRoot = process.cwd(); @@ -406,7 +406,7 @@ test("assembled lifecycle: boot → onboard → prompt → streaming text → to }), }); - t.after(async () => { + afterEach(async () => { onboarding.resetOnboardingServiceForTests(); await bridge.resetBridgeServiceForTests(); fixture.cleanup(); @@ -882,7 +882,7 @@ test("assembled settings controls keep retry visibility and daily-use mutations getEnvApiKey: () => undefined, }); - t.after(async () => { + afterEach(async () => { await bridge.resetBridgeServiceForTests(); onboarding.resetOnboardingServiceForTests(); fixture.cleanup(); @@ -1108,7 +1108,7 @@ test("assembled recovery route exposes actionable browser diagnostics without ra }), }); - t.after(async () => { + afterEach(async () => { await bridge.resetBridgeServiceForTests(); fixture.cleanup(); }); @@ -1226,7 +1226,7 @@ test("assembled slash-command behavior keeps built-ins safe while preserving SF getEnvApiKey: () => undefined, }); - t.after(async () => { + afterEach(async () => { await bridge.resetBridgeServiceForTests(); onboarding.resetOnboardingServiceForTests(); fixture.cleanup(); diff --git a/src/tests/integration/web-mode-cli.test.ts b/src/tests/integration/web-mode-cli.test.ts index ce2bb6a18..3e6765dfe 100644 --- a/src/tests/integration/web-mode-cli.test.ts +++ b/src/tests/integration/web-mode-cli.test.ts @@ -8,7 +8,7 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; const projectRoot = process.cwd(); @@ -69,7 +69,7 @@ test("cli.ts branches to web mode before interactive startup and preserves cwd-s | { cwd: string; projectSessionsDir: string; agentDir: string } | undefined; - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); @@ -144,7 +144,7 @@ test("launchWebMode prefers the packaged standalone host and opens the resolved const pidFilePath = join(tmp, "web-server.pid"); - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); @@ -243,7 +243,7 @@ test("stopWebMode kills process by PID and removes PID file", (t) => { let stderrOutput = ""; let _killedPid: number | undefined; - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); @@ -272,7 +272,7 @@ test("stopWebMode reports error when no PID file exists", (t) => { const pidFilePath = join(tmp, "web-server.pid"); let stderrOutput = ""; - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); @@ -298,7 +298,7 @@ test('runWebCliBranch handles "web stop" subcommand without --web flag', async ( const pidFilePath = join(tmp, "web-server.pid"); let _stderrOutput = ""; - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); @@ -369,7 +369,7 @@ test("sf web is handled as web start with path", async (t) => { mkdirSync(projectDir, { recursive: true }); let launchedCwd = ""; - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); @@ -411,7 +411,7 @@ test("sf web start resolves path and launches", async (t) => { mkdirSync(projectDir, { recursive: true }); let launchedCwd = ""; - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); @@ -454,7 +454,7 @@ test("sf --web resolves path and launches", async (t) => { mkdirSync(projectDir, { recursive: true }); let launchedCwd = ""; - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); @@ -524,7 +524,7 @@ test("launch failure surfaces status and reason before browser open", async (t) let openedUrl = ""; let stderrOutput = ""; - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); @@ -564,7 +564,7 @@ test("registerInstance and readInstanceRegistry round-trip", (t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-web-registry-")); const registryPath = join(tmp, "web-instances.json"); - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); @@ -590,7 +590,7 @@ test("unregisterInstance removes a single entry", (t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-web-unreg-")); const registryPath = join(tmp, "web-instances.json"); - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); @@ -663,7 +663,7 @@ test("sf web stop is parsed and dispatched with resolved path", async (t) const tmp = mkdtempSync(join(tmpdir(), "sf-web-stop-path-")); let stopOptions: { projectCwd?: string; all?: boolean } | undefined; - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); @@ -698,7 +698,7 @@ test("resolveContextAwareCwd returns project cwd when inside a project under dev const projectA = join(devRoot, "projectA"); const prefsPath = join(tmp, "web-preferences.json"); - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); @@ -714,7 +714,7 @@ test("resolveContextAwareCwd returns cwd unchanged when AT dev root", (t) => { const devRoot = join(tmp, "devroot"); const prefsPath = join(tmp, "web-preferences.json"); - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); @@ -730,7 +730,7 @@ test("resolveContextAwareCwd returns cwd unchanged when no dev root configured", const prefsPath = join(tmp, "web-preferences.json"); const cwd = join(tmp, "somedir"); - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); @@ -746,7 +746,7 @@ test("resolveContextAwareCwd returns cwd unchanged when prefs file missing", (t) const prefsPath = join(tmp, "nonexistent-prefs.json"); const cwd = join(tmp, "somedir"); - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); @@ -762,7 +762,7 @@ test("resolveContextAwareCwd returns cwd unchanged when dev root path is stale", const cwd = join(tmp, "somedir"); const staleDevRoot = join(tmp, "nonexistent-devroot"); - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); @@ -780,7 +780,7 @@ test("resolveContextAwareCwd resolves nested cwd to one-level-deep project", (t) const nested = join(projectA, "src", "components", "deep"); const prefsPath = join(tmp, "web-preferences.json"); - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); @@ -797,7 +797,7 @@ test("resolveContextAwareCwd returns cwd unchanged when outside dev root", (t) = const outsideDir = join(tmp, "elsewhere"); const prefsPath = join(tmp, "web-preferences.json"); - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); @@ -832,7 +832,7 @@ test("launchWebMode kills stale instance for same cwd before spawning", async (t let stderrOutput = ""; let spawnCalled = false; - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); @@ -891,7 +891,7 @@ test("launchWebMode does not log cleanup when no stale instance exists", async ( let stderrOutput = ""; - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); diff --git a/src/tests/integration/web-mode-network-flags.test.ts b/src/tests/integration/web-mode-network-flags.test.ts index c246f08b3..2edbf73d6 100644 --- a/src/tests/integration/web-mode-network-flags.test.ts +++ b/src/tests/integration/web-mode-network-flags.test.ts @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; const cliWeb = await import("../../cli-web-branch.ts"); const webMode = await import("../../web-mode.ts"); @@ -115,7 +115,7 @@ test("launchWebMode forwards custom host, port, and allowed origins to subproces let spawnEnv: Record | undefined; - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); @@ -170,7 +170,7 @@ test("launchWebMode omits SF_WEB_ALLOWED_ORIGINS when none provided", async (t) let spawnEnv: Record | undefined; - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); @@ -208,7 +208,7 @@ test("runWebCliBranch forwards --host, --port, --allowed-origins to launchWebMod let receivedOptions: Record | undefined; - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); diff --git a/src/tests/integration/web-mode-onboarding.test.ts b/src/tests/integration/web-mode-onboarding.test.ts index e4669203a..74fa31678 100644 --- a/src/tests/integration/web-mode-onboarding.test.ts +++ b/src/tests/integration/web-mode-onboarding.test.ts @@ -5,7 +5,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { PassThrough } from "node:stream"; import { StringDecoder } from "node:string_decoder"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { killProcessOnPort, @@ -346,7 +346,7 @@ test("successful browser onboarding restarts the stale bridge child and unlocks }), }); - t.after(async () => { + afterEach(async () => { onboarding.resetOnboardingServiceForTests(); await bridge.resetBridgeServiceForTests(); fixture.cleanup(); @@ -427,7 +427,7 @@ test("refresh failures keep the workspace locked and expose the failed bridge-re }), }); - t.after(async () => { + afterEach(async () => { onboarding.resetOnboardingServiceForTests(); await bridge.resetBridgeServiceForTests(); fixture.cleanup(); @@ -496,7 +496,7 @@ test("refresh failures keep the workspace locked and expose the failed bridge-re test("fresh sf --web browser onboarding stays locked on failed validation and unlocks after a successful retry", async (t) => { if (process.platform === "win32") { - t.skip("runtime launch test uses POSIX browser-open stubs"); + return; // skip: "runtime launch test uses POSIX browser-open stubs"; return; } @@ -505,7 +505,7 @@ test("fresh sf --web browser onboarding stays locked on failed validation and un const browserLogPath = join(tempRoot, "browser-open.log"); let port: number | null = null; - t.after(async () => { + afterEach(async () => { if (port !== null) { await killProcessOnPort(port); } diff --git a/src/tests/integration/web-mode-windows-hide.test.ts b/src/tests/integration/web-mode-windows-hide.test.ts index 57a7faa0d..4fe9f106d 100644 --- a/src/tests/integration/web-mode-windows-hide.test.ts +++ b/src/tests/integration/web-mode-windows-hide.test.ts @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; const webMode = await import("../../web-mode.ts"); @@ -23,7 +23,7 @@ test("launchWebMode passes windowsHide: true in spawn options", async (t) => { let capturedOptions: Record | undefined; - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); @@ -76,7 +76,7 @@ test("launchWebMode source-dev host also passes windowsHide: true", async (t) => let capturedOptions: Record | undefined; - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); diff --git a/src/tests/integration/web-multi-project-contract.test.ts b/src/tests/integration/web-multi-project-contract.test.ts index 564c3210f..b78993289 100644 --- a/src/tests/integration/web-multi-project-contract.test.ts +++ b/src/tests/integration/web-multi-project-contract.test.ts @@ -5,7 +5,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { PassThrough } from "node:stream"; import { StringDecoder } from "node:string_decoder"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; const repoRoot = process.cwd(); const bridge = await import("../../web/bridge-service.ts"); @@ -266,7 +266,7 @@ test("multi-project: getProjectBridgeServiceForCwd returns distinct instances fo getOnboardingNeeded: () => false, }); - t.after(async () => { + afterEach(async () => { await bridge.resetBridgeServiceForTests(); fixtureA.cleanup(); fixtureB.cleanup(); @@ -302,7 +302,7 @@ test("multi-project: getProjectBridgeServiceForCwd returns same instance for sam getOnboardingNeeded: () => false, }); - t.after(async () => { + afterEach(async () => { await bridge.resetBridgeServiceForTests(); fixtureA.cleanup(); }); @@ -359,7 +359,7 @@ test("multi-project: each bridge receives commands independently", async (t) => getOnboardingNeeded: () => false, }); - t.after(async () => { + afterEach(async () => { await bridge.resetBridgeServiceForTests(); fixtureA.cleanup(); fixtureB.cleanup(); @@ -414,7 +414,7 @@ test("multi-project: SSE subscribers are isolated per bridge", async (t) => { getOnboardingNeeded: () => false, }); - t.after(async () => { + afterEach(async () => { await bridge.resetBridgeServiceForTests(); fixtureA.cleanup(); fixtureB.cleanup(); @@ -489,7 +489,7 @@ test("multi-project: resolveProjectCwd falls back to SF_WEB_PROJECT_CWD when no getOnboardingNeeded: () => false, }); - t.after(() => { + afterEach(() => { bridge.configureBridgeServiceForTests(null); }); @@ -516,7 +516,7 @@ test("multi-project: getProjectBridgeService backward compat shim works", async getOnboardingNeeded: () => false, }); - t.after(async () => { + afterEach(async () => { await bridge.resetBridgeServiceForTests(); fixture.cleanup(); }); @@ -559,7 +559,7 @@ test("multi-project: resetBridgeServiceForTests clears all registry entries", as getOnboardingNeeded: () => false, }); - t.after(async () => { + afterEach(async () => { await bridge.resetBridgeServiceForTests(); fixtureA.cleanup(); fixtureB.cleanup(); diff --git a/src/tests/integration/web-onboarding-contract.test.ts b/src/tests/integration/web-onboarding-contract.test.ts index 81cd3f642..e3bdf412e 100644 --- a/src/tests/integration/web-onboarding-contract.test.ts +++ b/src/tests/integration/web-onboarding-contract.test.ts @@ -5,7 +5,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { PassThrough } from "node:stream"; import { StringDecoder } from "node:string_decoder"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; const repoRoot = process.cwd(); const bridge = await import("../../web/bridge-service.ts"); @@ -359,7 +359,7 @@ test("boot and onboarding routes expose locked required state plus explicitly sk getEnvApiKey: noEnvApiKey, }); - t.after(async () => { + afterEach(async () => { onboarding.resetOnboardingServiceForTests(); await bridge.resetBridgeServiceForTests(); restoreOnboardingEnv(); @@ -429,7 +429,7 @@ test("runtime env-backed auth unlocks boot onboarding state and reports the envi provider === "github-copilot" ? process.env.GITHUB_TOKEN : undefined, }); - t.after(async () => { + afterEach(async () => { if (previousGithubToken === undefined) { delete process.env.GITHUB_TOKEN; } else { @@ -477,7 +477,7 @@ test("failed API-key validation stays locked, redacts the error, and is reflecte }), }); - t.after(async () => { + afterEach(async () => { onboarding.resetOnboardingServiceForTests(); await bridge.resetBridgeServiceForTests(); restoreOnboardingEnv(); @@ -540,7 +540,7 @@ test("direct prompt commands cannot bypass onboarding while required setup is st getEnvApiKey: noEnvApiKey, }); - t.after(async () => { + afterEach(async () => { onboarding.resetOnboardingServiceForTests(); await bridge.resetBridgeServiceForTests(); restoreOnboardingEnv(); @@ -596,7 +596,7 @@ test("bridge auth refresh failures remain inspectable and keep the workspace loc }, }); - t.after(async () => { + afterEach(async () => { onboarding.resetOnboardingServiceForTests(); await bridge.resetBridgeServiceForTests(); restoreOnboardingEnv(); @@ -657,7 +657,7 @@ test("successful API-key validation persists the credential and unlocks onboardi }), }); - t.after(async () => { + afterEach(async () => { onboarding.resetOnboardingServiceForTests(); await bridge.resetBridgeServiceForTests(); restoreOnboardingEnv(); @@ -714,7 +714,7 @@ test("logout_provider removes saved auth, refreshes the bridge, and relocks onbo getEnvApiKey: noEnvApiKey, }); - t.after(async () => { + afterEach(async () => { onboarding.resetOnboardingServiceForTests(); await bridge.resetBridgeServiceForTests(); restoreOnboardingEnv(); @@ -777,7 +777,7 @@ test("logout_provider fails clearly for environment-backed auth that the browser provider === "github-copilot" ? process.env.GITHUB_TOKEN : undefined, }); - t.after(async () => { + afterEach(async () => { if (previousGithubToken === undefined) { delete process.env.GITHUB_TOKEN; } else { diff --git a/src/tests/integration/web-recovery-diagnostics-contract.test.ts b/src/tests/integration/web-recovery-diagnostics-contract.test.ts index ede835046..b71bae6ba 100644 --- a/src/tests/integration/web-recovery-diagnostics-contract.test.ts +++ b/src/tests/integration/web-recovery-diagnostics-contract.test.ts @@ -5,7 +5,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { PassThrough } from "node:stream"; import { StringDecoder } from "node:string_decoder"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; const repoRoot = process.cwd(); const bridge = await import("../../web/bridge-service.ts"); @@ -295,7 +295,7 @@ test("/api/recovery returns structured recovery diagnostics and redacts secrets" }), }); - t.after(async () => { + afterEach(async () => { await bridge.resetBridgeServiceForTests(); fixture.cleanup(); }); @@ -381,7 +381,7 @@ test("/api/recovery prefers the current-project resumable session when the live getOnboardingState: async () => readyOnboardingState(), }); - t.after(async () => { + afterEach(async () => { await bridge.resetBridgeServiceForTests(); fixture.cleanup(); }); @@ -437,7 +437,7 @@ test("/api/recovery returns a structured empty-project payload without leaking r getOnboardingState: async () => readyOnboardingState(), }); - t.after(async () => { + afterEach(async () => { await bridge.resetBridgeServiceForTests(); fixture.cleanup(); }); diff --git a/src/tests/integration/web-session-parity-contract.test.ts b/src/tests/integration/web-session-parity-contract.test.ts index 9f559fe9d..83951a69e 100644 --- a/src/tests/integration/web-session-parity-contract.test.ts +++ b/src/tests/integration/web-session-parity-contract.test.ts @@ -12,7 +12,7 @@ import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; import { PassThrough } from "node:stream"; import { StringDecoder } from "node:string_decoder"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; const repoRoot = process.cwd(); const bridge = await import("../../web/bridge-service.ts"); @@ -344,7 +344,7 @@ test("/api/session/browser stays current-project scoped and carries threaded/sea configureBridgeFixture(fixture, harness); - t.after(async () => { + afterEach(async () => { await bridge.resetBridgeServiceForTests(); onboarding.resetOnboardingServiceForTests(); fixture.cleanup(); @@ -455,7 +455,7 @@ test("/api/session/manage renames the active session through bridge-aware RPC in } as any), }); - t.after(async () => { + afterEach(async () => { await bridge.resetBridgeServiceForTests(); onboarding.resetOnboardingServiceForTests(); fixture.cleanup(); @@ -567,7 +567,7 @@ test("/api/session/manage renames inactive sessions via authoritative session-fi } as any), }); - t.after(async () => { + afterEach(async () => { await bridge.resetBridgeServiceForTests(); onboarding.resetOnboardingServiceForTests(); fixture.cleanup(); @@ -619,7 +619,7 @@ test("/api/git returns a current-project-scoped repo summary and ignores changes const projectCwd = join(repoRoot, "apps", "current-project"); const docsDir = join(repoRoot, "docs"); - t.after(() => { + afterEach(() => { rmSync(root, { recursive: true, force: true }); }); @@ -687,7 +687,7 @@ test("/api/git returns a current-project-scoped repo summary and ignores changes test("/api/git exposes an explicit not-a-repo state instead of failing silently", async (t) => { const projectCwd = mkdtempSync(join(tmpdir(), "sf-web-not-repo-")); - t.after(() => { + afterEach(() => { rmSync(projectCwd, { recursive: true, force: true }); }); diff --git a/src/tests/integration/web-state-surfaces-contract.test.ts b/src/tests/integration/web-state-surfaces-contract.test.ts index f3080d144..fd2c21c16 100644 --- a/src/tests/integration/web-state-surfaces-contract.test.ts +++ b/src/tests/integration/web-state-surfaces-contract.test.ts @@ -8,7 +8,7 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; // ─── Imports ────────────────────────────────────────────────────────── const workspaceIndex = await import( @@ -35,7 +35,7 @@ function makeSfFixture(): { root: string; sfDir: string; cleanup: () => void } { test("indexWorkspace extracts risk, depends, and demo from roadmap", async (t) => { const { root, sfDir, cleanup } = makeSfFixture(); - t.after(() => { + afterEach(() => { cleanup(); }); @@ -93,7 +93,7 @@ test("indexWorkspace extracts risk, depends, and demo from roadmap", async (t) = test("indexWorkspace handles slices without risk/depends/demo", async (t) => { const { root, sfDir, cleanup } = makeSfFixture(); - t.after(() => { + afterEach(() => { cleanup(); }); @@ -239,7 +239,7 @@ test("files API returns tree listing of .sf/ directory", async (t) => { const { root, sfDir, cleanup } = makeSfFixture(); const origEnv = process.env.SF_WEB_PROJECT_CWD; - t.after(() => { + afterEach(() => { process.env.SF_WEB_PROJECT_CWD = origEnv; cleanup(); }); @@ -290,7 +290,7 @@ test("files API returns file content for valid path", async (t) => { const { root, sfDir, cleanup } = makeSfFixture(); const origEnv = process.env.SF_WEB_PROJECT_CWD; - t.after(() => { + afterEach(() => { process.env.SF_WEB_PROJECT_CWD = origEnv; cleanup(); }); @@ -312,7 +312,7 @@ test("files API returns content for nested files", async (t) => { const { root, sfDir, cleanup } = makeSfFixture(); const origEnv = process.env.SF_WEB_PROJECT_CWD; - t.after(() => { + afterEach(() => { process.env.SF_WEB_PROJECT_CWD = origEnv; cleanup(); }); @@ -338,7 +338,7 @@ test("files API rejects path traversal with ../", async (t) => { const { root, cleanup } = makeSfFixture(); const origEnv = process.env.SF_WEB_PROJECT_CWD; - t.after(() => { + afterEach(() => { process.env.SF_WEB_PROJECT_CWD = origEnv; cleanup(); }); @@ -359,7 +359,7 @@ test("files API rejects absolute paths", async (t) => { const { root, cleanup } = makeSfFixture(); const origEnv = process.env.SF_WEB_PROJECT_CWD; - t.after(() => { + afterEach(() => { process.env.SF_WEB_PROJECT_CWD = origEnv; cleanup(); }); @@ -380,7 +380,7 @@ test("files API returns 404 for missing files", async (t) => { const { root, cleanup } = makeSfFixture(); const origEnv = process.env.SF_WEB_PROJECT_CWD; - t.after(() => { + afterEach(() => { process.env.SF_WEB_PROJECT_CWD = origEnv; cleanup(); }); @@ -401,7 +401,7 @@ test("files API returns empty tree when .sf/ does not exist", async (t) => { const root = mkdtempSync(join(tmpdir(), "sf-state-surfaces-empty-")); const origEnv = process.env.SF_WEB_PROJECT_CWD; - t.after(() => { + afterEach(() => { process.env.SF_WEB_PROJECT_CWD = origEnv; rmSync(root, { recursive: true, force: true }); }); diff --git a/src/tests/integration/web-workflow-action-execution.test.ts b/src/tests/integration/web-workflow-action-execution.test.ts index d953f5acc..0c075ef96 100644 --- a/src/tests/integration/web-workflow-action-execution.test.ts +++ b/src/tests/integration/web-workflow-action-execution.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; const { derivePendingWorkflowCommandLabel, @@ -40,7 +40,7 @@ test("navigateToSFView dispatches the shared browser navigation event", (t) => { (globalThis as { window?: EventTarget }).window = fakeWindow; - t.after(() => { + afterEach(() => { (globalThis as { window?: EventTarget }).window = originalWindow; }); @@ -63,7 +63,7 @@ test("executeWorkflowActionInPowerMode calls dispatch and navigates to the appro (globalThis as { window?: EventTarget }).window = fakeWindow; (globalThis as any).localStorage = { getItem: () => null, setItem: () => {} }; - t.after(() => { + afterEach(() => { (globalThis as { window?: EventTarget }).window = originalWindow; (globalThis as any).localStorage = originalLocalStorage; }); diff --git a/src/tests/llm-context-tavily.test.ts b/src/tests/llm-context-tavily.test.ts index e5a7b284b..2647ab851 100644 --- a/src/tests/llm-context-tavily.test.ts +++ b/src/tests/llm-context-tavily.test.ts @@ -11,7 +11,7 @@ */ import assert from "node:assert/strict"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import type { TavilyResult } from "../resources/extensions/search-the-web/tavily.ts"; import { publishedDateToAge } from "../resources/extensions/search-the-web/tavily.ts"; import { budgetContent } from "../resources/extensions/search-the-web/tool-llm-context.ts"; @@ -417,7 +417,7 @@ test("Tavily LLM context request uses POST with Bearer auth and advanced search const { captured, restore } = mockFetch(tavilyResponse); - t.after(restore); + afterEach(restore); // Simulate what the Tavily LLM context path will build const requestBody = { query, diff --git a/src/tests/marketplace-discovery.test.ts b/src/tests/marketplace-discovery.test.ts index 768e5493f..c413005de 100644 --- a/src/tests/marketplace-discovery.test.ts +++ b/src/tests/marketplace-discovery.test.ts @@ -13,7 +13,7 @@ import assert from "node:assert"; import * as fs from "node:fs"; import * as path from "node:path"; -import { describe, it } from 'vitest'; +import { describe, it, afterEach } from 'vitest'; import { discoverMarketplace, inspectPlugin, @@ -352,7 +352,7 @@ describe("Marketplace Discovery Contract Tests", { skip: skipReason }, () => { const tmpDir = "/tmp/test-no-marketplace-" + Date.now(); fs.mkdirSync(tmpDir, { recursive: true }); - t.after(() => fs.rmSync(tmpDir, { recursive: true, force: true })); + afterEach(() => fs.rmSync(tmpDir, { recursive: true, force: true })); const result = discoverMarketplace(tmpDir); assert.strictEqual(result.status, "error"); @@ -371,7 +371,7 @@ describe("Marketplace Discovery Contract Tests", { skip: skipReason }, () => { "{ this is not valid json }", ); - t.after(() => fs.rmSync(tmpDir, { recursive: true, force: true })); + afterEach(() => fs.rmSync(tmpDir, { recursive: true, force: true })); const result = discoverMarketplace(tmpDir); assert.strictEqual(result.status, "error"); @@ -391,7 +391,7 @@ describe("Marketplace Discovery Contract Tests", { skip: skipReason }, () => { JSON.stringify({ description: "test" }), ); - t.after(() => fs.rmSync(tmpDir, { recursive: true, force: true })); + afterEach(() => fs.rmSync(tmpDir, { recursive: true, force: true })); const parseResult = parseMarketplaceJson(tmpDir); assert.strictEqual(parseResult.success, false); @@ -416,7 +416,7 @@ describe("Marketplace Discovery Contract Tests", { skip: skipReason }, () => { }), ); - t.after(() => fs.rmSync(tmpDir, { recursive: true, force: true })); + afterEach(() => fs.rmSync(tmpDir, { recursive: true, force: true })); const result = discoverMarketplace(tmpDir); // Marketplace should parse ok, but the missing plugin should have error status diff --git a/src/tests/native-search.test.ts b/src/tests/native-search.test.ts index 17c3d4a89..23e4d8316 100644 --- a/src/tests/native-search.test.ts +++ b/src/tests/native-search.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { BRAVE_TOOL_NAMES, CUSTOM_SEARCH_TOOL_NAMES, @@ -341,7 +341,7 @@ test("model_select disables Brave tools when Anthropic + no BRAVE_API_KEY", asyn const originalKey = process.env.BRAVE_API_KEY; delete process.env.BRAVE_API_KEY; - t.after(() => { + afterEach(() => { if (originalKey) process.env.BRAVE_API_KEY = originalKey; else delete process.env.BRAVE_API_KEY; }); @@ -376,7 +376,7 @@ test("model_select disables all custom search tools when Anthropic even with BRA const originalKey = process.env.BRAVE_API_KEY; process.env.BRAVE_API_KEY = "test-key"; - t.after(() => { + afterEach(() => { if (originalKey) process.env.BRAVE_API_KEY = originalKey; else delete process.env.BRAVE_API_KEY; }); @@ -410,7 +410,7 @@ test("model_select re-enables Brave tools when switching away from Anthropic", a const originalKey = process.env.BRAVE_API_KEY; delete process.env.BRAVE_API_KEY; - t.after(() => { + afterEach(() => { if (originalKey) process.env.BRAVE_API_KEY = originalKey; else delete process.env.BRAVE_API_KEY; }); @@ -486,7 +486,7 @@ test("model_select shows warning for non-Anthropic without Brave key", async (t) delete process.env[k]; } - t.after(() => { + afterEach(() => { for (const k of keys) { if (originals[k] !== undefined) process.env[k] = originals[k]; else delete process.env[k]; @@ -543,7 +543,7 @@ test("before_provider_request removes Brave tools from payload when no BRAVE_API const originalKey = process.env.BRAVE_API_KEY; delete process.env.BRAVE_API_KEY; - t.after(() => { + afterEach(() => { if (originalKey) process.env.BRAVE_API_KEY = originalKey; else delete process.env.BRAVE_API_KEY; }); @@ -600,7 +600,7 @@ test("before_provider_request removes all custom search tools from payload even const originalKey = process.env.BRAVE_API_KEY; process.env.BRAVE_API_KEY = "test-key"; - t.after(() => { + afterEach(() => { if (originalKey) process.env.BRAVE_API_KEY = originalKey; else delete process.env.BRAVE_API_KEY; }); @@ -657,7 +657,7 @@ test("model_select re-enable does not duplicate Brave tools across toggle cycles const originalKey = process.env.BRAVE_API_KEY; delete process.env.BRAVE_API_KEY; - t.after(() => { + afterEach(() => { if (originalKey) process.env.BRAVE_API_KEY = originalKey; else delete process.env.BRAVE_API_KEY; }); @@ -1205,7 +1205,7 @@ test("getMiniMaxSearchApiKey returns MINIMAX_CODE_PLAN_KEY when set", async (t) const original = process.env.MINIMAX_CODE_PLAN_KEY; const original2 = process.env.MINIMAX_CODING_API_KEY; const original3 = process.env.MINIMAX_API_KEY; - t.after(() => { + afterEach(() => { process.env.MINIMAX_CODE_PLAN_KEY = original; process.env.MINIMAX_CODING_API_KEY = original2; process.env.MINIMAX_API_KEY = original3; @@ -1227,7 +1227,7 @@ test("getMiniMaxSearchApiKey falls back to MINIMAX_CODING_API_KEY", async (t) => const original = process.env.MINIMAX_CODE_PLAN_KEY; const original2 = process.env.MINIMAX_CODING_API_KEY; const original3 = process.env.MINIMAX_API_KEY; - t.after(() => { + afterEach(() => { process.env.MINIMAX_CODE_PLAN_KEY = original; process.env.MINIMAX_CODING_API_KEY = original2; process.env.MINIMAX_API_KEY = original3; @@ -1248,7 +1248,7 @@ test("getMiniMaxSearchApiKey falls back to MINIMAX_API_KEY", async (t) => { const original = process.env.MINIMAX_CODE_PLAN_KEY; const original2 = process.env.MINIMAX_CODING_API_KEY; const original3 = process.env.MINIMAX_API_KEY; - t.after(() => { + afterEach(() => { process.env.MINIMAX_CODE_PLAN_KEY = original; process.env.MINIMAX_CODING_API_KEY = original2; process.env.MINIMAX_API_KEY = original3; @@ -1269,7 +1269,7 @@ test("getMiniMaxSearchApiKey returns empty string when no keys set", async (t) = const original = process.env.MINIMAX_CODE_PLAN_KEY; const original2 = process.env.MINIMAX_CODING_API_KEY; const original3 = process.env.MINIMAX_API_KEY; - t.after(() => { + afterEach(() => { process.env.MINIMAX_CODE_PLAN_KEY = original; process.env.MINIMAX_CODING_API_KEY = original2; process.env.MINIMAX_API_KEY = original3; @@ -1291,7 +1291,7 @@ test("resolveSearchProvider returns minimax when MINIMAX_API_KEY is set and pref const original2 = process.env.MINIMAX_CODING_API_KEY; const original3 = process.env.MINIMAX_API_KEY; const originalTavily = process.env.TAVILY_API_KEY; - t.after(() => { + afterEach(() => { process.env.MINIMAX_CODE_PLAN_KEY = original; process.env.MINIMAX_CODING_API_KEY = original2; process.env.MINIMAX_API_KEY = original3; @@ -1315,7 +1315,7 @@ test("resolveSearchProvider returns minimax when MINIMAX_API_KEY is set and pref test("resolveSearchProvider prefers tavily over minimax in auto mode", async (t) => { const original = process.env.TAVILY_API_KEY; const original2 = process.env.MINIMAX_API_KEY; - t.after(() => { + afterEach(() => { process.env.TAVILY_API_KEY = original; process.env.MINIMAX_API_KEY = original2; }); @@ -1335,7 +1335,7 @@ test("resolveSearchProvider prefers tavily over minimax in auto mode", async (t) test("resolveSearchProvider with explicit minimax preference returns minimax when key exists", async (t) => { const original = process.env.MINIMAX_API_KEY; const originalTavily = process.env.TAVILY_API_KEY; - t.after(() => { + afterEach(() => { process.env.MINIMAX_API_KEY = original; process.env.TAVILY_API_KEY = originalTavily; }); @@ -1357,7 +1357,7 @@ test("resolveSearchProvider minimax preference falls back when key missing", asy const originalCA = process.env.MINIMAX_CODING_API_KEY; const originalTavily = process.env.TAVILY_API_KEY; const originalBrave = process.env.BRAVE_API_KEY; - t.after(() => { + afterEach(() => { process.env.MINIMAX_API_KEY = original; process.env.MINIMAX_CODE_PLAN_KEY = originalCP; process.env.MINIMAX_CODING_API_KEY = originalCA; @@ -1387,7 +1387,7 @@ test("resolveSearchProvider returns null when no keys set", async (t) => { originals[k] = process.env[k]; delete process.env[k]; } - t.after(() => { + afterEach(() => { for (const k of keys) { if (originals[k] !== undefined) process.env[k] = originals[k]; else delete process.env[k]; diff --git a/src/tests/node-modules-symlink.test.ts b/src/tests/node-modules-symlink.test.ts index f2925c4ea..6c89d3d82 100644 --- a/src/tests/node-modules-symlink.test.ts +++ b/src/tests/node-modules-symlink.test.ts @@ -19,7 +19,7 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { dirname, join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { fileURLToPath } from "node:url"; // --- Integration tests via initResources (source/monorepo path) --- @@ -29,7 +29,7 @@ test("initResources creates node_modules symlink in agent dir", async (t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-symlink-")); const fakeAgentDir = join(tmp, "agent"); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); initResources(fakeAgentDir); const nodeModulesPath = join(fakeAgentDir, "node_modules"); @@ -53,7 +53,7 @@ test("initResources replaces a real directory blocking node_modules with a symli const tmp = mkdtempSync(join(tmpdir(), "sf-symlink-realdir-")); const fakeAgentDir = join(tmp, "agent"); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); initResources(fakeAgentDir); const nodeModulesPath = join(fakeAgentDir, "node_modules"); @@ -90,7 +90,7 @@ test("initResources replaces a stale symlink with a correct one", async (t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-symlink-stale-")); const fakeAgentDir = join(tmp, "agent"); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); initResources(fakeAgentDir); const nodeModulesPath = join(fakeAgentDir, "node_modules"); @@ -126,7 +126,7 @@ test("initResources replaces symlink whose target was deleted", async (t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-symlink-missing-")); const fakeAgentDir = join(tmp, "agent"); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); initResources(fakeAgentDir); const nodeModulesPath = join(fakeAgentDir, "node_modules"); @@ -165,7 +165,7 @@ test("pnpm layout: merged node_modules contains entries from both hoisted and in // @singularity-forge/ ← workspace scope (NOT hoisted) // @singularity-forge/ ← workspace scope (NOT hoisted) const tmp = mkdtempSync(join(tmpdir(), "sf-pnpm-merge-")); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); const hoisted = join(tmp, "node_modules"); const pkgRoot = join(hoisted, "sf-run"); @@ -247,7 +247,7 @@ test("pnpm layout: non-@sf internal deps (e.g. @anthropic-ai) are included in me // dropping optionalDependencies like @anthropic-ai/claude-agent-sdk // that npm installs internally rather than hoisting. const tmp = mkdtempSync(join(tmpdir(), "sf-pnpm-internal-optional-")); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); const hoisted = join(tmp, "node_modules"); const pkgRoot = join(hoisted, "sf-run"); @@ -304,7 +304,7 @@ test("pnpm layout: non-@sf internal deps (e.g. @anthropic-ai) are included in me test("hasMissingWorkspaceScopes detects pnpm layout", (t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-pnpm-detect-")); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); const hoisted = join(tmp, "hoisted"); const internal = join(tmp, "internal"); @@ -345,7 +345,7 @@ test("hasMissingWorkspaceScopes detects pnpm layout", (t) => { test("merged node_modules marker uses fingerprint including directory entries", (t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-pnpm-marker-")); - t.after(() => rmSync(tmp, { recursive: true, force: true })); + afterEach(() => rmSync(tmp, { recursive: true, force: true })); // Simulate two directories with known entries const hoisted = join(tmp, "hoisted"); diff --git a/src/tests/non-extension-library.test.ts b/src/tests/non-extension-library.test.ts index 0d1f626f9..4c13aaca6 100644 --- a/src/tests/non-extension-library.test.ts +++ b/src/tests/non-extension-library.test.ts @@ -20,7 +20,7 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { dirname, join, parse } from "node:path"; -import { test, describe } from 'vitest'; +import { test, describe, afterEach } from 'vitest'; function makeTempDir(): string { const dir = join( @@ -63,7 +63,7 @@ function isNonExtensionLibrary(resolvedPath: string): boolean { describe("isNonExtensionLibrary — defense-in-depth for #1709", () => { test("returns true for a file inside a directory with pi: {} (cmux pattern)", (t) => { const root = makeTempDir(); - t.after(() => rmSync(root, { recursive: true, force: true })); + afterEach(() => rmSync(root, { recursive: true, force: true })); const libDir = join(root, "cmux"); mkdirSync(libDir); writeFileSync( @@ -89,7 +89,7 @@ describe("isNonExtensionLibrary — defense-in-depth for #1709", () => { test("returns true for pi.extensions as empty array", (t) => { const root = makeTempDir(); - t.after(() => rmSync(root, { recursive: true, force: true })); + afterEach(() => rmSync(root, { recursive: true, force: true })); const libDir = join(root, "lib-empty"); mkdirSync(libDir); writeFileSync( @@ -113,7 +113,7 @@ describe("isNonExtensionLibrary — defense-in-depth for #1709", () => { test("returns false for a directory without pi manifest (broken extension)", (t) => { const root = makeTempDir(); - t.after(() => rmSync(root, { recursive: true, force: true })); + afterEach(() => rmSync(root, { recursive: true, force: true })); const extDir = join(root, "broken-ext"); mkdirSync(extDir); writeFileSync( @@ -136,7 +136,7 @@ describe("isNonExtensionLibrary — defense-in-depth for #1709", () => { test("returns false when pi.extensions declares actual entries", (t) => { const root = makeTempDir(); - t.after(() => rmSync(root, { recursive: true, force: true })); + afterEach(() => rmSync(root, { recursive: true, force: true })); const extDir = join(root, "declared-ext"); mkdirSync(extDir); writeFileSync( @@ -160,7 +160,7 @@ describe("isNonExtensionLibrary — defense-in-depth for #1709", () => { test("returns false when no package.json exists at all", (t) => { const root = makeTempDir(); - t.after(() => rmSync(root, { recursive: true, force: true })); + afterEach(() => rmSync(root, { recursive: true, force: true })); const noManifest = join(root, "no-manifest"); mkdirSync(noManifest); writeFileSync(join(noManifest, "index.js"), "module.exports = {};"); @@ -176,7 +176,7 @@ describe("isNonExtensionLibrary — defense-in-depth for #1709", () => { test("handles malformed package.json gracefully", (t) => { const root = makeTempDir(); - t.after(() => rmSync(root, { recursive: true, force: true })); + afterEach(() => rmSync(root, { recursive: true, force: true })); const badDir = join(root, "bad-json"); mkdirSync(badDir); writeFileSync(join(badDir, "package.json"), "not valid json {{{"); @@ -191,7 +191,7 @@ describe("isNonExtensionLibrary — defense-in-depth for #1709", () => { test("pi manifest with other fields but no extensions still opts out", (t) => { const root = makeTempDir(); - t.after(() => rmSync(root, { recursive: true, force: true })); + afterEach(() => rmSync(root, { recursive: true, force: true })); const libDir = join(root, "lib-with-skills"); mkdirSync(libDir); writeFileSync( diff --git a/src/tests/provider-manager-remove.test.ts b/src/tests/provider-manager-remove.test.ts index a226b8956..e68b2a494 100644 --- a/src/tests/provider-manager-remove.test.ts +++ b/src/tests/provider-manager-remove.test.ts @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import { mkdtempSync, readFileSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; const { ModelsJsonWriter } = await import( "../../packages/pi-coding-agent/src/core/models-json-writer.ts" @@ -100,7 +100,7 @@ function createComponent(options: { test("provider manager skips remove when provider has no auth", (t) => { const modelsJsonPath = createTempModelsJsonPath(); const rootDir = join(modelsJsonPath, ".."); - t.after(() => rmSync(rootDir, { recursive: true, force: true })); + afterEach(() => rmSync(rootDir, { recursive: true, force: true })); const { component, removedProviders, getRefreshCalls, getRenderCalls } = createComponent({ @@ -120,7 +120,7 @@ test("provider manager skips remove when provider has no auth", (t) => { test("provider manager removes provider models with confirmation when auth is stored", (t) => { const modelsJsonPath = createTempModelsJsonPath(); const rootDir = join(modelsJsonPath, ".."); - t.after(() => rmSync(rootDir, { recursive: true, force: true })); + afterEach(() => rmSync(rootDir, { recursive: true, force: true })); const { component, removedProviders, getRefreshCalls, getRenderCalls } = createComponent({ @@ -152,7 +152,7 @@ test("provider manager removes provider models with confirmation when auth is st test("provider manager clamps selection after removing the selected provider", (t) => { const modelsJsonPath = createTempModelsJsonPath(); const rootDir = join(modelsJsonPath, ".."); - t.after(() => rmSync(rootDir, { recursive: true, force: true })); + afterEach(() => rmSync(rootDir, { recursive: true, force: true })); const { component } = createComponent({ modelsJsonPath, diff --git a/src/tests/provider.test.ts b/src/tests/provider.test.ts index 0caffaf5d..ffde70cab 100644 --- a/src/tests/provider.test.ts +++ b/src/tests/provider.test.ts @@ -11,7 +11,7 @@ import assert from "node:assert/strict"; import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; // ─── Helpers ───────────────────────────────────────────────────────────────── @@ -63,7 +63,7 @@ test("resolveSearchProvider returns tavily when only TAVILY_API_KEY is set", asy "../resources/extensions/search-the-web/provider.ts" ); const { cleanup } = makeTmpAuth(); - t.after(() => { + afterEach(() => { cleanup(); }); @@ -276,7 +276,7 @@ test("getSearchProviderPreference returns auto when no preference stored", async "../resources/extensions/search-the-web/provider.ts" ); const { authPath, cleanup } = makeTmpAuth(); - t.after(() => { + afterEach(() => { cleanup(); }); @@ -291,7 +291,7 @@ test("getSearchProviderPreference reads from auth.json via AuthStorage", async ( const { authPath, cleanup } = makeTmpAuth({ search_provider: { type: "api_key", key: "tavily" }, }); - t.after(() => { + afterEach(() => { cleanup(); }); @@ -303,7 +303,7 @@ test("setSearchProviderPreference writes to auth.json via AuthStorage", async (t const { getSearchProviderPreference, setSearchProviderPreference } = await import("../resources/extensions/search-the-web/provider.ts"); const { authPath, cleanup } = makeTmpAuth(); - t.after(() => { + afterEach(() => { cleanup(); }); @@ -330,7 +330,7 @@ test("getSearchProviderPreference returns auto for invalid stored value", async const { authPath, cleanup } = makeTmpAuth({ search_provider: { type: "api_key", key: "google" }, }); - t.after(() => { + afterEach(() => { cleanup(); }); diff --git a/src/tests/read-tool-offset-clamp.test.ts b/src/tests/read-tool-offset-clamp.test.ts index 5dc46b35a..4c07c15ab 100644 --- a/src/tests/read-tool-offset-clamp.test.ts +++ b/src/tests/read-tool-offset-clamp.test.ts @@ -10,7 +10,7 @@ import assert from "node:assert/strict"; import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { createReadTool } from "../../packages/pi-coding-agent/src/core/tools/read.ts"; @@ -37,7 +37,7 @@ function writeLines(dir: string, name: string, lineCount: number): string { test("read tool: offset exceeding file length should NOT throw (#3007)", async (t) => { const { dir, cleanup } = makeTmpDir(); - t.after(cleanup); + afterEach(cleanup); writeLines(dir, "small-artifact.md", 13); const readTool = createReadTool(dir); @@ -66,7 +66,7 @@ test("read tool: offset exceeding file length should NOT throw (#3007)", async ( test("read tool: offset 100 on a 5-line file clamps to last line", async (t) => { const { dir, cleanup } = makeTmpDir(); - t.after(cleanup); + afterEach(cleanup); writeLines(dir, "tiny-file.txt", 5); const readTool = createReadTool(dir); @@ -84,7 +84,7 @@ test("read tool: offset 100 on a 5-line file clamps to last line", async (t) => test("read tool: offset at exact last line works normally", async (t) => { const { dir, cleanup } = makeTmpDir(); - t.after(cleanup); + afterEach(cleanup); writeLines(dir, "exact-offset.txt", 5); const readTool = createReadTool(dir); @@ -100,7 +100,7 @@ test("read tool: offset at exact last line works normally", async (t) => { test("read tool: clamped offset includes notice about adjustment", async (t) => { const { dir, cleanup } = makeTmpDir(); - t.after(cleanup); + afterEach(cleanup); writeLines(dir, "notice-test.md", 10); const readTool = createReadTool(dir); diff --git a/src/tests/resource-loader-content-hash.test.ts b/src/tests/resource-loader-content-hash.test.ts index 8ff63d8d0..e1aaa73ff 100644 --- a/src/tests/resource-loader-content-hash.test.ts +++ b/src/tests/resource-loader-content-hash.test.ts @@ -8,7 +8,7 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; /** * Regression test for SF build #4787. @@ -29,7 +29,7 @@ test("computeResourceFingerprint detects same-size content edits (#4787)", async const { computeResourceFingerprint } = await import("../resource-loader.ts"); const tmp = mkdtempSync(join(tmpdir(), "sf-fingerprint-content-")); - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); @@ -62,7 +62,7 @@ test("syncResourceDir overwrites same-size stale content on refresh (#4787)", as const { syncResourceDir } = await import("../resource-loader.ts"); const tmp = mkdtempSync(join(tmpdir(), "sf-sync-samesize-")); - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); diff --git a/src/tests/resource-loader.test.ts b/src/tests/resource-loader.test.ts index d10a7e348..445607844 100644 --- a/src/tests/resource-loader.test.ts +++ b/src/tests/resource-loader.test.ts @@ -9,7 +9,7 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { join, parse } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; function overrideHomeEnv(homeDir: string): () => void { const original = { @@ -70,7 +70,7 @@ test("hasStaleCompiledExtensionSiblings only flags top-level .ts/.js sibling pai const extensionsDir = join(tmp, "extensions"); const bundledDir = join(tmp, "bundled"); - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); @@ -108,7 +108,7 @@ test("buildResourceLoader excludes duplicate top-level pi extensions when bundle const fakeAgentDir = join(tmp, ".sf", "agent"); const restoreHomeEnv = overrideHomeEnv(tmp); - t.after(() => { + afterEach(() => { restoreHomeEnv(); rmSync(tmp, { recursive: true, force: true }); }); @@ -191,7 +191,7 @@ test("initResources prunes stale top-level extension siblings next to bundled co "ask-user-questions.js", ); - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); diff --git a/src/tests/resource-sync-staleness.test.ts b/src/tests/resource-sync-staleness.test.ts index 80b335702..6a59baf29 100644 --- a/src/tests/resource-sync-staleness.test.ts +++ b/src/tests/resource-sync-staleness.test.ts @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; /** * Integration test for resource sync staleness detection. @@ -25,7 +25,7 @@ test("resource manifest includes contentHash", async (t) => { const tmpDir = mkdtempSync(join(tmpdir(), "sf-resource-test-")); const manifestPath = join(tmpDir, "managed-resources.json"); - t.after(() => { + afterEach(() => { rmSync(tmpDir, { recursive: true, force: true }); }); diff --git a/src/tests/rtk-execution-seams.test.ts b/src/tests/rtk-execution-seams.test.ts index 3b74a2c24..7570b1978 100644 --- a/src/tests/rtk-execution-seams.test.ts +++ b/src/tests/rtk-execution-seams.test.ts @@ -8,7 +8,7 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { createAsyncBashTool } from "../resources/extensions/async-jobs/async-bash-tool.ts"; import { AsyncJobManager } from "../resources/extensions/async-jobs/job-manager.ts"; import { runOnSession } from "../resources/extensions/bg-shell/interaction.ts"; @@ -212,10 +212,10 @@ test("async_bash executes the RTK-rewritten command", async () => { test("bg_shell start and runOnSession both execute RTK-rewritten commands", async (t) => { if (process.platform === "win32") { - t.skip("bg_shell requires bash; Windows CI runners don't have Git Bash"); + return; // skip: "bg_shell requires bash; Windows CI runners don't have Git Bash"; return; } - t.after(cleanupAll); + afterEach(cleanupAll); await withFakeRtk({ "echo raw": "echo rewritten" }, async () => { const oneshot = startProcess({ diff --git a/src/tests/search-loop-guard.test.ts b/src/tests/search-loop-guard.test.ts index e1b0c7e00..d07ac1c36 100644 --- a/src/tests/search-loop-guard.test.ts +++ b/src/tests/search-loop-guard.test.ts @@ -10,7 +10,7 @@ */ import assert from "node:assert/strict"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import searchExtension from "../resources/extensions/search-the-web/index.ts"; import { registerSearchTool, @@ -146,7 +146,7 @@ test("search loop guard fires after MAX_CONSECUTIVE_DUPES duplicates", async (t) delete process.env.OLLAMA_API_KEY; const restoreFetch = mockFetch(makeBraveResponse()); - t.after(() => { + afterEach(() => { restoreFetch(); restoreSearchEnv(); }); @@ -183,7 +183,7 @@ test("search loop guard resets at session_start boundary", async (t) => { const restoreFetch = mockFetch(makeBraveResponse()); const query = "session boundary query"; - t.after(() => { + afterEach(() => { restoreFetch(); restoreSearchEnv(); }); @@ -225,7 +225,7 @@ test("search loop guard stays armed after firing — subsequent duplicates immed // Use a unique query so module-level state from previous test doesn't interfere const query = "persistent loop query"; - t.after(() => { + afterEach(() => { restoreFetch(); restoreSearchEnv(); }); @@ -273,7 +273,7 @@ test("search loop guard resets cleanly when a different query is issued", async const queryA = "query alpha reset test"; const queryB = "query beta reset test"; - t.after(() => { + afterEach(() => { restoreFetch(); restoreSearchEnv(); }); @@ -302,7 +302,7 @@ test("session search budget blocks after MAX_SEARCHES_PER_SESSION varied queries delete process.env.OLLAMA_API_KEY; const restoreFetch = mockFetch(makeBraveResponse()); - t.after(() => { + afterEach(() => { restoreFetch(); restoreSearchEnv(); }); @@ -346,7 +346,7 @@ test("session search budget resets via resetSearchLoopGuardState", async (t) => delete process.env.OLLAMA_API_KEY; const restoreFetch = mockFetch(makeBraveResponse()); - t.after(() => { + afterEach(() => { restoreFetch(); restoreSearchEnv(); }); diff --git a/src/tests/search-provider-command.test.ts b/src/tests/search-provider-command.test.ts index 2deee8f7a..44e377863 100644 --- a/src/tests/search-provider-command.test.ts +++ b/src/tests/search-provider-command.test.ts @@ -14,7 +14,7 @@ import assert from "node:assert/strict"; import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; // ─── Helpers (reused from provider.test.ts pattern) ──────────────────────── @@ -144,7 +144,7 @@ test('direct arg "tavily" sets preference and notifies', async (t) => { const cmd = await loadCommand(); const { authPath, cleanup } = makeTmpAuth(); - t.after(() => { + afterEach(() => { cleanup(); }); @@ -188,7 +188,7 @@ test('direct arg "brave" sets preference and notifies', async (t) => { const cmd = await loadCommand(); const { cleanup } = makeTmpAuth(); - t.after(() => { + afterEach(() => { cleanup(); }); @@ -217,7 +217,7 @@ test('direct arg "auto" sets preference and notifies', async (t) => { const cmd = await loadCommand(); const { cleanup } = makeTmpAuth(); - t.after(() => { + afterEach(() => { cleanup(); }); @@ -289,7 +289,7 @@ test("cancel (select returns undefined) produces no side effects", async (t) => const cmd = await loadCommand(); const { authPath, cleanup } = makeTmpAuth(); - t.after(() => { + afterEach(() => { cleanup(); }); diff --git a/src/tests/search-tavily.test.ts b/src/tests/search-tavily.test.ts index 736d725a4..382bc32c6 100644 --- a/src/tests/search-tavily.test.ts +++ b/src/tests/search-tavily.test.ts @@ -12,7 +12,7 @@ */ import assert from "node:assert/strict"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { resolveSearchProvider } from "../resources/extensions/search-the-web/provider.ts"; import { mapFreshnessToTavily } from "../resources/extensions/search-the-web/tavily.ts"; @@ -100,7 +100,7 @@ test("executeTavilySearch sends POST to Tavily API and produces CachedSearchResu const { captured, restore } = mockFetch(makeTavilyResponse()); - t.after(() => { + afterEach(() => { restore(); if (origKey !== undefined) process.env.TAVILY_API_KEY = origKey; else delete process.env.TAVILY_API_KEY; @@ -180,7 +180,7 @@ test("resolveSearchProvider returns 'tavily' when TAVILY_API_KEY is set and BRAV process.env.TAVILY_API_KEY = "tvly-test-key"; delete process.env.BRAVE_API_KEY; - t.after(() => { + afterEach(() => { if (origTavily !== undefined) process.env.TAVILY_API_KEY = origTavily; else delete process.env.TAVILY_API_KEY; if (origBrave !== undefined) process.env.BRAVE_API_KEY = origBrave; @@ -198,7 +198,7 @@ test("resolveSearchProvider returns 'brave' when only BRAVE_API_KEY is set", (t) delete process.env.TAVILY_API_KEY; process.env.BRAVE_API_KEY = "BSA-test-key"; - t.after(() => { + afterEach(() => { if (origTavily !== undefined) process.env.TAVILY_API_KEY = origTavily; else delete process.env.TAVILY_API_KEY; if (origBrave !== undefined) process.env.BRAVE_API_KEY = origBrave; @@ -216,7 +216,7 @@ test("resolveSearchProvider returns null when neither key is set", (t) => { delete process.env.TAVILY_API_KEY; delete process.env.BRAVE_API_KEY; - t.after(() => { + afterEach(() => { if (origTavily !== undefined) process.env.TAVILY_API_KEY = origTavily; else delete process.env.BRAVE_API_KEY; if (origBrave !== undefined) process.env.BRAVE_API_KEY = origBrave; @@ -313,7 +313,7 @@ test("Tavily answer field maps to summaryText in CachedSearchResult", async (t) const { captured, restore } = mockFetch(responseWithAnswer); - t.after(() => { + afterEach(() => { restore(); if (origKey !== undefined) process.env.TAVILY_API_KEY = origKey; else delete process.env.TAVILY_API_KEY; @@ -387,7 +387,7 @@ test("Tavily domain filter uses include_domains, not site: prefix in query", asy const { captured, restore } = mockFetch(makeTavilyResponse()); - t.after(() => { + afterEach(() => { restore(); if (origKey !== undefined) process.env.TAVILY_API_KEY = origKey; else delete process.env.TAVILY_API_KEY; diff --git a/src/tests/secret-scan.test.ts b/src/tests/secret-scan.test.ts index ea86e6d7f..7e253f7f4 100644 --- a/src/tests/secret-scan.test.ts +++ b/src/tests/secret-scan.test.ts @@ -3,7 +3,7 @@ import { spawnSync } from "node:child_process"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { platform, tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; // Secret scanner requires bash + POSIX grep — skip on Windows const isWindows = platform() === "win32"; @@ -160,7 +160,7 @@ test("skips package-lock.json", { skip: isWindows }, () => { test("reports no files cleanly", { skip: isWindows }, (t) => { const dir = mkdtempSync(join(tmpdir(), "secret-scan-empty-")); - t.after(() => { + afterEach(() => { rmSync(dir, { recursive: true, force: true }); }); @@ -193,7 +193,7 @@ test("reports multiple secrets in one file", { skip: isWindows }, () => { test("CI mode scans diff against ref", { skip: isWindows }, (t) => { const dir = mkdtempSync(join(tmpdir(), "secret-scan-ci-")); - t.after(() => { + afterEach(() => { rmSync(dir, { recursive: true, force: true }); }); diff --git a/src/tests/terminal-cmux.test.ts b/src/tests/terminal-cmux.test.ts index 1e5ae9e54..dbaeeeb69 100644 --- a/src/tests/terminal-cmux.test.ts +++ b/src/tests/terminal-cmux.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { detectCapabilities, resetCapabilitiesCache, @@ -28,7 +28,7 @@ test("detectCapabilities treats cmux as kitty-capable", (t) => { CMUX_SURFACE_ID: "surface:2", TERM_PROGRAM: "ghostty", }; - t.after(() => { + afterEach(() => { process.env = originalEnv; resetCapabilitiesCache(); }); diff --git a/src/tests/tool-bootstrap.test.ts b/src/tests/tool-bootstrap.test.ts index c275028b8..c1f1afcd8 100644 --- a/src/tests/tool-bootstrap.test.ts +++ b/src/tests/tool-bootstrap.test.ts @@ -12,7 +12,7 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { ensureManagedTools, resolveToolFromPath } from "../tool-bootstrap.js"; @@ -32,7 +32,7 @@ function makeExecutable( test("resolveToolFromPath finds fd via fdfind fallback", (t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-tool-bootstrap-resolve-")); - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); @@ -49,7 +49,7 @@ test("ensureManagedTools provisions fd and rg into managed bin dir", (t) => { mkdirSync(sourceBin, { recursive: true }); mkdirSync(targetBin, { recursive: true }); - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); @@ -80,7 +80,7 @@ test("ensureManagedTools copies executable when symlink target already exists as mkdirSync(sourceBin, { recursive: true }); mkdirSync(targetBin, { recursive: true }); - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); diff --git a/src/tests/ttsr-rule-loader.test.ts b/src/tests/ttsr-rule-loader.test.ts index 0e7a70543..71aa6d628 100644 --- a/src/tests/ttsr-rule-loader.test.ts +++ b/src/tests/ttsr-rule-loader.test.ts @@ -7,7 +7,7 @@ import assert from "node:assert/strict"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { loadRules } from "../../src/resources/extensions/ttsr/index.js"; @@ -50,7 +50,7 @@ function writeRule( test("loads rule from project .sf/rules/", (t) => { const { cwd, projectDir, cleanup } = makeTmpProject(); - t.after(() => { + afterEach(() => { cleanup(); }); @@ -69,7 +69,7 @@ test("loads rule from project .sf/rules/", (t) => { test("parses scope and globs from frontmatter", (t) => { const { cwd, projectDir, cleanup } = makeTmpProject(); - t.after(() => { + afterEach(() => { cleanup(); }); @@ -88,7 +88,7 @@ test("parses scope and globs from frontmatter", (t) => { test("skips files without valid frontmatter", (t) => { const { cwd, projectDir, cleanup } = makeTmpProject(); - t.after(() => { + afterEach(() => { cleanup(); }); @@ -100,7 +100,7 @@ test("skips files without valid frontmatter", (t) => { test("skips rules with no condition", (t) => { const { cwd, projectDir, cleanup } = makeTmpProject(); - t.after(() => { + afterEach(() => { cleanup(); }); @@ -116,7 +116,7 @@ test("skips rules with no condition", (t) => { test("returns empty array when .sf/rules/ does not exist", (t) => { const { cwd, cleanup } = makeTmpProject(); - t.after(() => { + afterEach(() => { cleanup(); }); @@ -128,7 +128,7 @@ test("returns empty array when .sf/rules/ does not exist", (t) => { test("loads multiple rules from same directory", (t) => { const { cwd, projectDir, cleanup } = makeTmpProject(); - t.after(() => { + afterEach(() => { cleanup(); }); @@ -142,7 +142,7 @@ test("loads multiple rules from same directory", (t) => { test("handles quoted values in frontmatter", (t) => { const { cwd, projectDir, cleanup } = makeTmpProject(); - t.after(() => { + afterEach(() => { cleanup(); }); diff --git a/src/tests/update-check.test.ts b/src/tests/update-check.test.ts index cde9a2095..9ea33a3cc 100644 --- a/src/tests/update-check.test.ts +++ b/src/tests/update-check.test.ts @@ -3,7 +3,7 @@ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { createServer } from "node:http"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { checkForUpdates, @@ -49,7 +49,7 @@ test("compareSemver handles versions with different segment counts", () => { test("readUpdateCache returns null for nonexistent file", (t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-update-cache-")); - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); @@ -59,7 +59,7 @@ test("readUpdateCache returns null for nonexistent file", (t) => { test("readUpdateCache returns null for malformed JSON", (t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-update-cache-")); - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); @@ -71,7 +71,7 @@ test("readUpdateCache returns null for malformed JSON", (t) => { test("writeUpdateCache + readUpdateCache round-trips correctly", (t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-update-cache-")); - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); @@ -84,7 +84,7 @@ test("writeUpdateCache + readUpdateCache round-trips correctly", (t) => { test("writeUpdateCache creates parent directories", (t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-update-cache-")); - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); @@ -123,7 +123,7 @@ function startMockRegistry( test("checkForUpdates calls onUpdate when newer version is available", async (t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-update-")); const registry = await startMockRegistry({ version: "99.0.0" }); - t.after(async () => { + afterEach(async () => { await registry.close(); rmSync(tmp, { recursive: true, force: true }); }); @@ -153,7 +153,7 @@ test("checkForUpdates calls onUpdate when newer version is available", async (t) test("checkForUpdates does not call onUpdate when already on latest", async (t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-update-")); const registry = await startMockRegistry({ version: "1.0.0" }); - t.after(async () => { + afterEach(async () => { await registry.close(); rmSync(tmp, { recursive: true, force: true }); }); @@ -177,7 +177,7 @@ test("checkForUpdates does not call onUpdate when already on latest", async (t) test("checkForUpdates does not call onUpdate when current is ahead", async (t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-update-")); const registry = await startMockRegistry({ version: "1.0.0" }); - t.after(async () => { + afterEach(async () => { await registry.close(); rmSync(tmp, { recursive: true, force: true }); }); @@ -202,7 +202,7 @@ test("checkForUpdates writes cache after successful fetch", async (t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-update-")); const cachePath = join(tmp, ".update-check"); const registry = await startMockRegistry({ version: "5.0.0" }); - t.after(async () => { + afterEach(async () => { await registry.close(); rmSync(tmp, { recursive: true, force: true }); }); @@ -233,7 +233,7 @@ test("checkForUpdates uses cache and skips fetch when checked recently", async ( // Start server that would return a different version — should NOT be reached const registry = await startMockRegistry({ version: "20.0.0" }); - t.after(async () => { + afterEach(async () => { await registry.close(); rmSync(tmp, { recursive: true, force: true }); }); @@ -263,7 +263,7 @@ test("checkForUpdates skips notification when cache is fresh and versions match" cachePath, ); - t.after(() => { + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); @@ -288,7 +288,7 @@ test("checkForUpdates skips notification when cache is fresh and versions match" test("checkForUpdates handles server error gracefully", async (t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-update-")); const registry = await startMockRegistry({}, 500); - t.after(async () => { + afterEach(async () => { await registry.close(); rmSync(tmp, { recursive: true, force: true }); }); @@ -318,7 +318,7 @@ test("checkForUpdates handles network timeout gracefully", async (t) => { const addr = server.address() as { port: number }; const tmp = mkdtempSync(join(tmpdir(), "sf-update-")); - t.after(async () => { + afterEach(async () => { await new Promise((r) => server.close(() => r())); rmSync(tmp, { recursive: true, force: true }); }); @@ -342,7 +342,7 @@ test("checkForUpdates handles network timeout gracefully", async (t) => { test("checkForUpdates handles missing version field in response", async (t) => { const tmp = mkdtempSync(join(tmpdir(), "sf-update-")); const registry = await startMockRegistry({ name: "sf-run" }); // no version field - t.after(async () => { + afterEach(async () => { await registry.close(); rmSync(tmp, { recursive: true, force: true }); }); @@ -368,7 +368,7 @@ test("checkForUpdates handles missing version field in response", async (t) => { test("fetchLatestVersionFromRegistry returns the registry version string", async (t) => { const registry = await startMockRegistry({ version: "2.67.0" }); - t.after(async () => { + afterEach(async () => { await registry.close(); }); @@ -378,7 +378,7 @@ test("fetchLatestVersionFromRegistry returns the registry version string", async test("fetchLatestVersionFromRegistry returns null for blank version strings", async (t) => { const registry = await startMockRegistry({ version: "" }); - t.after(async () => { + afterEach(async () => { await registry.close(); }); diff --git a/src/tests/welcome-screen.test.ts b/src/tests/welcome-screen.test.ts index d6c37a5ab..a9d8caaf4 100644 --- a/src/tests/welcome-screen.test.ts +++ b/src/tests/welcome-screen.test.ts @@ -3,7 +3,7 @@ */ import assert from "node:assert/strict"; -import { test } from 'vitest'; +import { test, afterEach } from 'vitest'; import { printWelcomeScreen } from "../../dist/welcome-screen.js"; @@ -70,7 +70,7 @@ test("skips when not a TTY", (t) => { const origIsTTY = (process.stderr as any).isTTY; (process.stderr as any).isTTY = false; - t.after(() => { + afterEach(() => { (process.stderr as any).write = original; (process.stderr as any).isTTY = origIsTTY; }); @@ -102,7 +102,7 @@ test("omits remote channel when not provided", () => { test("separator lines extend to full terminal width on wide terminals", (t) => { const origColumns = process.stderr.columns; (process.stderr as any).columns = 250; - t.after(() => { + afterEach(() => { (process.stderr as any).columns = origColumns; }); diff --git a/vitest.config.ts b/vitest.config.ts index e4972e3a1..024433b92 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -42,12 +42,8 @@ export default defineConfig({ // ── Pool: forks = one Node process per test file (best for Node.js tests) ─ pool: "forks", - poolOptions: { - forks: { - // Single worker in CI; parallel in dev for speed - singleFork: process.env.CI === "true", - }, - }, + // Single worker in CI; parallel in dev for speed + singleFork: process.env.CI === "true", // ── Coverage ────────────────────────────────────────────────────────────── coverage: { diff --git a/web/lib/__tests__/dashboard-metrics-fallback.test.ts b/web/lib/__tests__/dashboard-metrics-fallback.test.ts index 4591ef059..bd6e2a176 100644 --- a/web/lib/__tests__/dashboard-metrics-fallback.test.ts +++ b/web/lib/__tests__/dashboard-metrics-fallback.test.ts @@ -1,4 +1,4 @@ -import { describe, test } from "node:test"; +import { describe } from 'vitest'; import assert from "node:assert/strict"; /** diff --git a/web/lib/__tests__/shutdown-gate.test.ts b/web/lib/__tests__/shutdown-gate.test.ts index fff141a4f..e2506b54c 100644 --- a/web/lib/__tests__/shutdown-gate.test.ts +++ b/web/lib/__tests__/shutdown-gate.test.ts @@ -1,4 +1,4 @@ -import { describe, test, beforeEach, afterEach } from "node:test"; +import { describe, beforeEach, afterEach } from 'vitest'; import assert from "node:assert/strict"; import {