From 63454a4cfda1f3208596f1db5458b64b96ab4e88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=A9=98?= Date: Thu, 4 Jun 2026 10:59:53 +0000 Subject: [PATCH 1/6] =?UTF-8?q?fix:=20OCAS=5FDIR=20=E2=86=92=20OCAS=5FHOME?= =?UTF-8?q?=20in=20test=20helpers=20+=20exclude=20integration=20tests=20fr?= =?UTF-8?q?om=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remaining OCAS_DIR references caused test isolation failures - agent-hermes integration tests need 'hermes' CLI, skip in CI Fixes #58 --- packages/agent-hermes/package.json | 2 +- packages/cli/src/__tests__/store-unified-threads.test.ts | 2 +- packages/cli/src/__tests__/thread-resume.test.ts | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/agent-hermes/package.json b/packages/agent-hermes/package.json index b27f390..1456668 100644 --- a/packages/agent-hermes/package.json +++ b/packages/agent-hermes/package.json @@ -19,7 +19,7 @@ "scripts": { "prepublishOnly": "echo 'Use pnpm run release from repo root' && exit 1", "test": "vitest run __tests__/", - "test:ci": "vitest run __tests__/" + "test:ci": "vitest run __tests__/ --exclude __tests__/integration/" }, "dependencies": { "@ocas/core": "^0.3.0", diff --git a/packages/cli/src/__tests__/store-unified-threads.test.ts b/packages/cli/src/__tests__/store-unified-threads.test.ts index 247db5d..be1647a 100644 --- a/packages/cli/src/__tests__/store-unified-threads.test.ts +++ b/packages/cli/src/__tests__/store-unified-threads.test.ts @@ -15,7 +15,7 @@ import { async function makeUwfStore(storageRoot: string) { const casDir = join(storageRoot, "cas"); await mkdir(casDir, { recursive: true }); - process.env.OCAS_DIR = casDir; + process.env.OCAS_HOME = casDir; return createUwfStore(storageRoot); } diff --git a/packages/cli/src/__tests__/thread-resume.test.ts b/packages/cli/src/__tests__/thread-resume.test.ts index 3b8aa9e..d53e4b6 100644 --- a/packages/cli/src/__tests__/thread-resume.test.ts +++ b/packages/cli/src/__tests__/thread-resume.test.ts @@ -491,7 +491,7 @@ describe("uwf thread resume - completed threads", () => { cwd: tmpDir, }); - process.env.OCAS_DIR = casDir; + process.env.OCAS_HOME = casDir; const workerOutputHash = await store.cas.put(outputSchemaHash, { $status: "_" }); const reviewerOutputHash = await store.cas.put(outputSchemaHash, { $status: "_" }); @@ -654,7 +654,7 @@ echo '${adapterJson}' cwd: tmpDir, }); - process.env.OCAS_DIR = casDir; + process.env.OCAS_HOME = casDir; await seedThreads(tmpDir, { [THREAD_ID]: { head: startHash, @@ -702,7 +702,7 @@ echo '${adapterJson}' cwd: tmpDir, }); - process.env.OCAS_DIR = casDir; + process.env.OCAS_HOME = casDir; await seedThreads(tmpDir, { [THREAD_ID]: startHash }); const result = runUwf(["thread", "resume", THREAD_ID], casDir); From 883bd79bcb1e76dce7441e012281c5102da69e88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=A9=98?= Date: Thu, 4 Jun 2026 11:18:49 +0000 Subject: [PATCH 2/6] fix: add timeout to CI-slow tests + check stderr for help output --- packages/cli/src/__tests__/e2e-mock-agent.test.ts | 12 ++++++------ packages/cli/src/__tests__/prompt.test.ts | 2 +- packages/cli/src/__tests__/thread-step-count.test.ts | 12 +++++++----- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/cli/src/__tests__/e2e-mock-agent.test.ts b/packages/cli/src/__tests__/e2e-mock-agent.test.ts index aac5687..162e195 100644 --- a/packages/cli/src/__tests__/e2e-mock-agent.test.ts +++ b/packages/cli/src/__tests__/e2e-mock-agent.test.ts @@ -241,7 +241,7 @@ describe("E2E mock-agent: full uwf pipeline", () => { expect(finalEntry!.head).toBe(step2.head); }); - test("2. branching workflow loops developer→reviewer→developer→reviewer→$END", async () => { + test("2. branching workflow loops developer→reviewer→developer→reviewer→$END", { timeout: 30_000 }, async () => { await writeMockConfig("e2e-loop.mock.yaml"); const workflowHash = await addWorkflow("e2e-loop.workflow.yaml", "test-loop"); @@ -299,7 +299,7 @@ describe("E2E mock-agent: full uwf pipeline", () => { expect(finalEntry!.status).toBe("completed"); }); - test("3. role mismatch in mock data makes the agent exit with an error", async () => { + test("3. role mismatch in mock data makes the agent exit with an error", { timeout: 30_000 }, async () => { // Reuses the linear workflow but with a mock whose step[1].role is wrong. await writeMockConfig("e2e-mismatch.mock.yaml"); const workflowHash = await addWorkflow("e2e-linear.workflow.yaml", "test-linear"); @@ -325,7 +325,7 @@ describe("E2E mock-agent: full uwf pipeline", () => { expect(entry!.head).toBe(step1.head); }); - test("4. planner $SUSPEND then resume re-runs planner and reaches $END", async () => { + test("4. planner $SUSPEND then resume re-runs planner and reaches $END", { timeout: 30_000 }, async () => { await writeMockConfig("e2e-suspend.mock.yaml"); const workflowHash = await addWorkflow("e2e-suspend.workflow.yaml", "test-suspend"); @@ -372,7 +372,7 @@ describe("E2E mock-agent: full uwf pipeline", () => { expect(finalEntry!.head).toBe(resumeOut.head); }); - test("5. --count 3 runs the whole linear pipeline in one invocation", async () => { + test("5. --count 3 runs the whole linear pipeline in one invocation", { timeout: 30_000 }, async () => { await writeMockConfig("e2e-count.mock.yaml"); const workflowHash = await addWorkflow("e2e-count.workflow.yaml", "test-count"); @@ -412,7 +412,7 @@ describe("E2E mock-agent: full uwf pipeline", () => { expect(finalEntry!.head).toBe(results[2].head); }); - test("6. mustache edge prompt renders planner variables into the worker step", async () => { + test("6. mustache edge prompt renders planner variables into the worker step", { timeout: 30_000 }, async () => { await writeMockConfig("e2e-mustache.mock.yaml"); const workflowHash = await addWorkflow("e2e-mustache.workflow.yaml", "test-mustache"); @@ -441,7 +441,7 @@ describe("E2E mock-agent: full uwf pipeline", () => { expect(workerStep.edgePrompt).toBe("Work on branch fix/42-auth in /tmp/my-repo"); }); - test("7. completed thread can be resumed (衔尾蛇: end → start)", async () => { + test("7. completed thread can be resumed (衔尾蛇: end → start)", { timeout: 30_000 }, async () => { // Reuse the suspend workflow (planner with ready → $END), but mock data // goes straight to ready on first run, then ready again after resume. await writeMockConfig("e2e-completed-resume.mock.yaml"); diff --git a/packages/cli/src/__tests__/prompt.test.ts b/packages/cli/src/__tests__/prompt.test.ts index 5c90503..019646d 100644 --- a/packages/cli/src/__tests__/prompt.test.ts +++ b/packages/cli/src/__tests__/prompt.test.ts @@ -88,7 +88,7 @@ describe("prompt commands", () => { expect(result).toContain("version"); }); - test("prompt help subcommand is suppressed", () => { + test("prompt help subcommand is suppressed", { timeout: 30_000 }, () => { const output = execFileSync("npx", ["tsx", "src/cli.ts", "prompt", "--help"], { cwd: join(__dirname, "..", ".."), encoding: "utf-8", diff --git a/packages/cli/src/__tests__/thread-step-count.test.ts b/packages/cli/src/__tests__/thread-step-count.test.ts index 4d2ca4b..6a60866 100644 --- a/packages/cli/src/__tests__/thread-step-count.test.ts +++ b/packages/cli/src/__tests__/thread-step-count.test.ts @@ -24,15 +24,17 @@ function runCli(args: string[]): { stdout: string; stderr: string; exitCode: num } describe("thread exec --count CLI parsing", () => { - test("--help shows -c/--count option", () => { + test("--help shows -c/--count option", { timeout: 30_000 }, () => { const result = runCli(["thread", "exec", "--help"]); - expect(result.stdout).toContain("--count"); - expect(result.stdout).toContain("-c"); + const combined = result.stdout + result.stderr; + expect(combined).toContain("--count"); + expect(combined).toContain("-c"); }); - test("description says 'one or more steps'", () => { + test("description says 'one or more steps'", { timeout: 30_000 }, () => { const result = runCli(["thread", "exec", "--help"]); - expect(result.stdout).toContain("one or more steps"); + const combined = result.stdout + result.stderr; + expect(combined).toContain("one or more steps"); }); }); From d26f54e8eac617e81bc5ab1b66f0333256506e8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=A9=98?= Date: Thu, 4 Jun 2026 11:22:46 +0000 Subject: [PATCH 3/6] fix: biome format + remove unused noConsole suppressions --- .../cli/src/__tests__/e2e-mock-agent.test.ts | 24 ++++++++++++++----- .../cli/src/__tests__/thread-resume.test.ts | 3 --- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/cli/src/__tests__/e2e-mock-agent.test.ts b/packages/cli/src/__tests__/e2e-mock-agent.test.ts index 162e195..3d6001e 100644 --- a/packages/cli/src/__tests__/e2e-mock-agent.test.ts +++ b/packages/cli/src/__tests__/e2e-mock-agent.test.ts @@ -241,7 +241,9 @@ describe("E2E mock-agent: full uwf pipeline", () => { expect(finalEntry!.head).toBe(step2.head); }); - test("2. branching workflow loops developer→reviewer→developer→reviewer→$END", { timeout: 30_000 }, async () => { + test("2. branching workflow loops developer→reviewer→developer→reviewer→$END", { + timeout: 30_000, + }, async () => { await writeMockConfig("e2e-loop.mock.yaml"); const workflowHash = await addWorkflow("e2e-loop.workflow.yaml", "test-loop"); @@ -299,7 +301,9 @@ describe("E2E mock-agent: full uwf pipeline", () => { expect(finalEntry!.status).toBe("completed"); }); - test("3. role mismatch in mock data makes the agent exit with an error", { timeout: 30_000 }, async () => { + test("3. role mismatch in mock data makes the agent exit with an error", { + timeout: 30_000, + }, async () => { // Reuses the linear workflow but with a mock whose step[1].role is wrong. await writeMockConfig("e2e-mismatch.mock.yaml"); const workflowHash = await addWorkflow("e2e-linear.workflow.yaml", "test-linear"); @@ -325,7 +329,9 @@ describe("E2E mock-agent: full uwf pipeline", () => { expect(entry!.head).toBe(step1.head); }); - test("4. planner $SUSPEND then resume re-runs planner and reaches $END", { timeout: 30_000 }, async () => { + test("4. planner $SUSPEND then resume re-runs planner and reaches $END", { + timeout: 30_000, + }, async () => { await writeMockConfig("e2e-suspend.mock.yaml"); const workflowHash = await addWorkflow("e2e-suspend.workflow.yaml", "test-suspend"); @@ -372,7 +378,9 @@ describe("E2E mock-agent: full uwf pipeline", () => { expect(finalEntry!.head).toBe(resumeOut.head); }); - test("5. --count 3 runs the whole linear pipeline in one invocation", { timeout: 30_000 }, async () => { + test("5. --count 3 runs the whole linear pipeline in one invocation", { + timeout: 30_000, + }, async () => { await writeMockConfig("e2e-count.mock.yaml"); const workflowHash = await addWorkflow("e2e-count.workflow.yaml", "test-count"); @@ -412,7 +420,9 @@ describe("E2E mock-agent: full uwf pipeline", () => { expect(finalEntry!.head).toBe(results[2].head); }); - test("6. mustache edge prompt renders planner variables into the worker step", { timeout: 30_000 }, async () => { + test("6. mustache edge prompt renders planner variables into the worker step", { + timeout: 30_000, + }, async () => { await writeMockConfig("e2e-mustache.mock.yaml"); const workflowHash = await addWorkflow("e2e-mustache.workflow.yaml", "test-mustache"); @@ -441,7 +451,9 @@ describe("E2E mock-agent: full uwf pipeline", () => { expect(workerStep.edgePrompt).toBe("Work on branch fix/42-auth in /tmp/my-repo"); }); - test("7. completed thread can be resumed (衔尾蛇: end → start)", { timeout: 30_000 }, async () => { + test("7. completed thread can be resumed (衔尾蛇: end → start)", { + timeout: 30_000, + }, async () => { // Reuse the suspend workflow (planner with ready → $END), but mock data // goes straight to ready on first run, then ready again after resume. await writeMockConfig("e2e-completed-resume.mock.yaml"); diff --git a/packages/cli/src/__tests__/thread-resume.test.ts b/packages/cli/src/__tests__/thread-resume.test.ts index d53e4b6..f84eb10 100644 --- a/packages/cli/src/__tests__/thread-resume.test.ts +++ b/packages/cli/src/__tests__/thread-resume.test.ts @@ -539,9 +539,7 @@ describe("uwf thread resume - completed threads", () => { const { createUwfStore, getThread } = await import("../store.js"); const verifyUwf = await createUwfStore(tmpDir); const verifyEntry = getThread(verifyUwf.varStore, THREAD_ID); - // biome-ignore lint/suspicious/noConsole: test debugging console.log("Seeded entry status:", verifyEntry?.status); - // biome-ignore lint/suspicious/noConsole: test debugging console.log("Seeded entry:", JSON.stringify(verifyEntry, null, 2)); const promptCapturePath = join(tmpDir, "captured-prompt-completed.txt"); @@ -601,7 +599,6 @@ echo '${adapterJson}' ); if (result.status !== 0) { - // biome-ignore lint/suspicious/noConsole: test debugging console.error("Command failed:", result.stderr); } From 596c05bfccf7f9201ff6b7783f69489dd029cd3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=A9=98?= Date: Thu, 4 Jun 2026 11:32:09 +0000 Subject: [PATCH 4/6] fix: use node dist/cli.js instead of npx tsx in prompt help test npx tsx fails in CI (tsx not found, npm tries to install it) --- packages/cli/src/__tests__/prompt.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/__tests__/prompt.test.ts b/packages/cli/src/__tests__/prompt.test.ts index 019646d..7618721 100644 --- a/packages/cli/src/__tests__/prompt.test.ts +++ b/packages/cli/src/__tests__/prompt.test.ts @@ -89,10 +89,10 @@ describe("prompt commands", () => { }); test("prompt help subcommand is suppressed", { timeout: 30_000 }, () => { - const output = execFileSync("npx", ["tsx", "src/cli.ts", "prompt", "--help"], { - cwd: join(__dirname, "..", ".."), + const cliPath = join(__dirname, "..", "..", "dist", "cli.js"); + const output = execFileSync("node", [cliPath, "prompt", "--help"], { encoding: "utf-8", - env: { ...process.env, PATH: `/opt/homebrew/bin:${process.env.PATH}` }, + env: { ...process.env }, }); expect(output).not.toMatch(/help\s+\[command\]/i); expect(output).toContain("usage"); From 58b58d511e51019d9113d25670f56de6c2125cec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=A9=98?= Date: Thu, 4 Jun 2026 11:48:46 +0000 Subject: [PATCH 5/6] fix: add timeout to cmdThreadExec count logic tests --- packages/cli/src/__tests__/thread-step-count.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/__tests__/thread-step-count.test.ts b/packages/cli/src/__tests__/thread-step-count.test.ts index 6a60866..e9ae603 100644 --- a/packages/cli/src/__tests__/thread-step-count.test.ts +++ b/packages/cli/src/__tests__/thread-step-count.test.ts @@ -38,7 +38,7 @@ describe("thread exec --count CLI parsing", () => { }); }); -describe("cmdThreadExec count logic", () => { +describe("cmdThreadExec count logic", { timeout: 30_000 }, () => { test("count=0 fails with validation error", () => { const result = runCli(["thread", "exec", "FAKE_THREAD_ID", "-c", "0"]); expect(result.exitCode).not.toBe(0); From 66c2e2a79bd1f8caf534fda9955da4e13cd44bcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=A9=98?= Date: Thu, 4 Jun 2026 11:57:32 +0000 Subject: [PATCH 6/6] fix: use node dist/cli.js instead of npx tsx in thread-step-count tests npx tsx hangs in CI Docker (30s+ timeout). node dist/cli.js runs in <2s. --- packages/cli/src/__tests__/thread-step-count.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/__tests__/thread-step-count.test.ts b/packages/cli/src/__tests__/thread-step-count.test.ts index e9ae603..44fb978 100644 --- a/packages/cli/src/__tests__/thread-step-count.test.ts +++ b/packages/cli/src/__tests__/thread-step-count.test.ts @@ -3,11 +3,11 @@ import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import { describe, expect, test } from "vitest"; -const CLI_PATH = join(dirname(fileURLToPath(import.meta.url)), "..", "cli.js"); +const CLI_PATH = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "dist", "cli.js"); function runCli(args: string[]): { stdout: string; stderr: string; exitCode: number } { try { - const stdout = execFileSync("npx", ["tsx", CLI_PATH, ...args], { + const stdout = execFileSync("node", [CLI_PATH, ...args], { encoding: "utf8", env: { ...process.env, UWF_HOME: "/tmp/uwf-test-nonexistent" }, stdio: ["ignore", "pipe", "pipe"],