diff --git a/.github/workflows/windows-advisory.yml b/.github/workflows/windows-advisory.yml index 8be78f1bf..ad9c5efb0 100644 --- a/.github/workflows/windows-advisory.yml +++ b/.github/workflows/windows-advisory.yml @@ -155,7 +155,7 @@ jobs: test/config test/project test/worktree - test/file + test/file/ test/github test/settings test/settings.test.ts @@ -176,11 +176,11 @@ jobs: test/question test/effect test/agent - test/git + test/git/ test/storage test/provider test/pty - test/share + test/share/ test/script test/memory test/lsp diff --git a/packages/opencode/test/github/bun-version-workflow.test.ts b/packages/opencode/test/github/bun-version-workflow.test.ts index 30e4362e9..6e2e24ad6 100644 --- a/packages/opencode/test/github/bun-version-workflow.test.ts +++ b/packages/opencode/test/github/bun-version-workflow.test.ts @@ -9,6 +9,14 @@ const workflowsRoot = path.join(repoRoot, ".github", "workflows") const expectedBunVersion = "1.3.14" const auditComment = "Load-bearing for `bun audit` exit semantics" +function repoRelativeWorkflowPath(workflowPath: string) { + return normalizeWorkflowPath(path.relative(repoRoot, workflowPath)) +} + +function normalizeWorkflowPath(relativePath: string) { + return relativePath.replaceAll("\\", "/") +} + function collectSetupBunPins(workflow: Workflow, relativePath: string) { const pins: string[] = [] @@ -25,6 +33,10 @@ function collectSetupBunPins(workflow: Workflow, relativePath: string) { } describe("GitHub workflow Bun version pin", () => { + test("normalizes workflow paths for cross-platform assertions", () => { + expect(normalizeWorkflowPath(".github\\workflows\\ci.yml")).toBe(".github/workflows/ci.yml") + }) + test("detects setup-bun steps that omit bun-version", () => { const workflow: Workflow = { jobs: { @@ -55,7 +67,7 @@ describe("GitHub workflow Bun version pin", () => { const missingComments: string[] = [] for (const workflowPath of workflowFiles) { - const relativePath = path.relative(repoRoot, workflowPath) + const relativePath = repoRelativeWorkflowPath(workflowPath) setupBunPins.push(...collectSetupBunPins(parseWorkflow(workflowPath), relativePath)) const lines = fs.readFileSync(workflowPath, "utf8").split(/\r?\n/) diff --git a/packages/opencode/test/github/ci-workflow.test.ts b/packages/opencode/test/github/ci-workflow.test.ts index e7771ca6f..9b15a595c 100644 --- a/packages/opencode/test/github/ci-workflow.test.ts +++ b/packages/opencode/test/github/ci-workflow.test.ts @@ -66,14 +66,14 @@ const windowsOpencodeShards = [ suffix: "opencode-config-project", usesTurbo: false, command: - "cd packages/opencode && bun test --timeout 30000 --reporter=junit --reporter-outfile=.artifacts/unit/junit-windows-config-project.xml test/config test/project test/worktree test/file test/github test/settings test/settings.test.ts", + "cd packages/opencode && bun test --timeout 30000 --reporter=junit --reporter-outfile=.artifacts/unit/junit-windows-config-project.xml test/config test/project test/worktree test/file/ test/github test/settings test/settings.test.ts", reportPath: "packages/opencode/.artifacts/unit/junit-windows-config-project.xml", }, { suffix: "opencode-server-tools", usesTurbo: false, command: - "cd packages/opencode && bun test --timeout 30000 --reporter=junit --reporter-outfile=.artifacts/unit/junit-windows-server-tools.xml test/server test/snapshot test/tool test/mcp test/question test/effect test/agent test/git test/storage test/provider test/pty test/share test/script test/memory test/lsp test/fixture test/acp test/bus test/cli test/global test/format test/account test/sync test/filesystem test/patch test/shell test/control-plane test/ide test/installation test/auth", + "cd packages/opencode && bun test --timeout 30000 --reporter=junit --reporter-outfile=.artifacts/unit/junit-windows-server-tools.xml test/server test/snapshot test/tool test/mcp test/question test/effect test/agent test/git/ test/storage test/provider test/pty test/share/ test/script test/memory test/lsp test/fixture test/acp test/bus test/cli test/global test/format test/account test/sync test/filesystem test/patch test/shell test/control-plane test/ide test/installation test/auth", reportPath: "packages/opencode/.artifacts/unit/junit-windows-server-tools.xml", }, ] as const @@ -149,6 +149,19 @@ function expandOpencodeTestPath(testPath: string): string[] { return [testPath] } +function ambiguousDirectoryShardArgs(testPaths: string[]) { + const testRootEntries = readdirSync(opencodeTestRoot, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .map((entry) => `test/${entry.name}`) + const testRootEntrySet = new Set(testRootEntries) + + return testPaths.filter((testPath) => { + if (testPath.endsWith("/")) return false + if (!testRootEntrySet.has(testPath)) return false + return testRootEntries.some((entry) => entry !== testPath && entry.startsWith(testPath)) + }) +} + function testPathArgs(command: string) { const testArgs = command.split(" bun test ")[1] if (!testArgs) { @@ -601,6 +614,15 @@ describe("ci workflow", () => { }) }) + test("keeps Windows opencode shard paths from prefix-matching sibling test directories", () => { + const parsed = parseWorkflow(windowsAdvisoryWorkflowPath) + const matrixIncludes = parsed.jobs?.[windowsUnitJobName]?.strategy?.matrix?.include ?? [] + const opencodeShards = matrixIncludes.filter(isWindowsOpencodeShard) + const shardArgs = opencodeShards.flatMap((item) => testPathArgs(item.command)) + + expect(ambiguousDirectoryShardArgs(shardArgs)).toEqual([]) + }) + test("keeps docs-only behavior and excludes Windows from the blocking aggregate", () => { const parsed = parseWorkflow(ciWorkflowPath) const check = parsed.jobs?.check diff --git a/packages/opencode/test/server/global-session-activity-list.test.ts b/packages/opencode/test/server/global-session-activity-list.test.ts index cf33ccb66..9065b58e4 100644 --- a/packages/opencode/test/server/global-session-activity-list.test.ts +++ b/packages/opencode/test/server/global-session-activity-list.test.ts @@ -281,7 +281,10 @@ describe("session.listGlobal activity order", () => { await Instance.provide({ directory: tmp.path, - fn: async () => userMessageWithSyntheticReminder(oldMixedUser.id, 4_000), + fn: async () => { + now = 4_000 + return userMessageWithSyntheticReminder(oldMixedUser.id, 4_000) + }, }) const sessions = [ diff --git a/packages/opencode/test/session/processor-effect.test.ts b/packages/opencode/test/session/processor-effect.test.ts index 8d81cc668..4395315bf 100644 --- a/packages/opencode/test/session/processor-effect.test.ts +++ b/packages/opencode/test/session/processor-effect.test.ts @@ -1645,7 +1645,7 @@ it.live("disabled unknown tools do not block safe connect-timeout auto retry", ( inputSchema: z.object({}), }), }, - connectTimeoutMs: 20, + connectTimeoutMs: 250, streamTimeoutMs: 1_000, }) diff --git a/packages/opencode/test/session/run-observability.test.ts b/packages/opencode/test/session/run-observability.test.ts index 8675ecaf0..8b9f969da 100644 --- a/packages/opencode/test/session/run-observability.test.ts +++ b/packages/opencode/test/session/run-observability.test.ts @@ -1,4 +1,5 @@ import { describe, expect, test } from "bun:test" +import { LLM } from "../../src/session/llm" import { MessageID, SessionID } from "../../src/session/schema" import { RunIncident } from "../../src/session/run-incident" import { RunObservability } from "../../src/session/run-observability" @@ -1281,6 +1282,36 @@ describe("RunObservability", () => { }) }) + test("disabled unknown tools are absent from before-progress retry boundary proof", () => { + const tools = LLM.resolveTools({ + agent: { permission: [] } as never, + permission: [], + user: { tools: { mcp_write: false } } as never, + tools: { + read: {}, + mcp_write: {}, + } as never, + }) + const snapshot = RunObservability.sideEffectBoundarySnapshot(tools) + const decision = recoveryForBeforeProgress({ + side_effect_facts_complete: true, + side_effect_boundary_snapshot: snapshot, + }) + + expect(Object.keys(tools)).toEqual(["read"]) + expect(snapshot).toMatchObject({ + exposed_tool_count: 1, + unknown_tool_count: 0, + unclassified_effect_count: 0, + proof_result: "complete", + proof_reason: "all_boundaries_classified", + }) + expect(decision).toMatchObject({ + recommendation: "auto_retry_once", + reason: "no_visible_output_or_tool_execution", + }) + }) + test("before-progress retry stays conservative when the boundary snapshot is missing", () => { const decision = recoveryForBeforeProgress({ side_effect_boundary_snapshot: undefined })