fix(workflow): add typecheck script and fix remaining type errors

- Add typecheck script (bunx tsc --build) to package.json
- Update check script to run typecheck before biome
- Fix mock fetch casts in test files (bun-types preconnect)
- Fix RequestInfo → Request | string | URL in llm-extract test
- Fix ThreadContext generic cast in solve-issue-template test
- Fix git-exec.ts missing return and module resolution
- Remove @types/node from workflow-role-committer
- Add exports field to workflow-util-agent/package.json
This commit is contained in:
2026-05-06 18:08:32 +08:00
parent 2c642b1a53
commit 94fa964b84
9 changed files with 44 additions and 31 deletions
+2 -1
View File
@@ -7,7 +7,8 @@
],
"scripts": {
"build": "bun run --filter '*' build",
"check": "biome check .",
"check": "bunx tsc --build && biome check .",
"typecheck": "bunx tsc --build",
"format": "biome format --write .",
"test": "bun run --filter '*' test"
},
@@ -12,8 +12,5 @@
"@uncaged/workflow": "workspace:*",
"@uncaged/workflow-util-role": "workspace:*",
"zod": "^4.0.0"
},
"devDependencies": {
"@types/node": "^25.6.0"
}
}
@@ -19,13 +19,13 @@ describe("createLlmAdapter", () => {
const originalFetch = globalThis.fetch;
test("posts system + user (start.content) and returns assistant text", async () => {
globalThis.fetch = () =>
globalThis.fetch = (() =>
Promise.resolve(
new Response(JSON.stringify({ choices: [{ message: { content: "model reply" } }] }), {
status: 200,
headers: { "Content-Type": "application/json" },
}),
);
)) as unknown as typeof fetch;
const provider = { baseUrl: "https://api.example/v1", apiKey: "k", model: "m" };
const adapter = createLlmAdapter(provider);
@@ -37,13 +37,13 @@ describe("createLlmAdapter", () => {
});
test("throws on non-ok fetch response", async () => {
globalThis.fetch = () =>
globalThis.fetch = (() =>
Promise.resolve(
new Response("Internal Server Error", {
status: 500,
headers: { "Content-Type": "text/plain" },
}),
);
)) as unknown as typeof fetch;
const provider = { baseUrl: "https://api.example/v1", apiKey: "k", model: "m" };
const adapter = createLlmAdapter(provider);
@@ -53,7 +53,7 @@ describe("createLlmAdapter", () => {
});
test("throws on fetch network failure", async () => {
globalThis.fetch = () => Promise.reject(new Error("ECONNREFUSED"));
globalThis.fetch = (() => Promise.reject(new Error("ECONNREFUSED"))) as unknown as typeof fetch;
const provider = { baseUrl: "https://api.example/v1", apiKey: "k", model: "m" };
const adapter = createLlmAdapter(provider);
@@ -50,7 +50,10 @@ describe("createReviewerRole", () => {
});
test("runs reviewer extract", async () => {
globalThis.fetch = () => Promise.resolve(toolCallResponse(JSON.stringify({ approved: true })));
globalThis.fetch = (() =>
Promise.resolve(
toolCallResponse(JSON.stringify({ approved: true })),
)) as unknown as typeof fetch;
const agent: AgentFn = async (_ctx, prompt) => {
expect(prompt).toContain("git diff");
@@ -68,7 +71,10 @@ describe("createReviewerRole", () => {
});
test("includes uncaged-workflow thread hint when threadId set", async () => {
globalThis.fetch = () => Promise.resolve(toolCallResponse(JSON.stringify({ approved: false })));
globalThis.fetch = (() =>
Promise.resolve(
toolCallResponse(JSON.stringify({ approved: false })),
)) as unknown as typeof fetch;
let seen = "";
const agent: AgentFn = async (_ctx, prompt) => {
@@ -115,7 +115,7 @@ describe("createSolveIssueRoles", () => {
expect(typeof roles.committer).toBe("function");
const ctx = makeCtx(10, []);
const plannerOut = await roles.planner(ctx);
const plannerOut = await roles.planner(ctx as unknown as ThreadContext);
expect(plannerOut.meta.plan).toBe("");
expect(Array.isArray(plannerOut.meta.files)).toBe(true);
});
@@ -4,6 +4,12 @@
"type": "module",
"main": "src/index.ts",
"types": "src/index.ts",
"exports": {
".": {
"types": "./src/index.ts",
"default": "./src/index.ts"
}
},
"scripts": {
"build": "echo 'TODO'",
"test": "bun test"
@@ -55,7 +55,8 @@ describe("createRole", () => {
});
test("runs AgentFn then structured extract", async () => {
globalThis.fetch = () => Promise.resolve(toolCallResponse(JSON.stringify({ n: 3 })));
globalThis.fetch = (() =>
Promise.resolve(toolCallResponse(JSON.stringify({ n: 3 })))) as unknown as typeof fetch;
const schema = z.object({ n: z.number() });
const agent: AgentFn = async (_ctx, prompt) => prompt;
@@ -73,7 +74,8 @@ describe("createRole", () => {
});
test("passes ThreadContext to AgentFn", async () => {
globalThis.fetch = () => Promise.resolve(toolCallResponse(JSON.stringify({ n: 0 })));
globalThis.fetch = (() =>
Promise.resolve(toolCallResponse(JSON.stringify({ n: 0 })))) as unknown as typeof fetch;
const seen: ThreadContext[] = [];
const agent: AgentFn = async (ctx, _prompt) => {
@@ -94,7 +96,8 @@ describe("createRole", () => {
});
test("resolves dynamic systemPrompt functions before AgentFn", async () => {
globalThis.fetch = () => Promise.resolve(toolCallResponse(JSON.stringify({ n: 99 })));
globalThis.fetch = (() =>
Promise.resolve(toolCallResponse(JSON.stringify({ n: 99 })))) as unknown as typeof fetch;
const schema = z.object({ n: z.number() });
const agent: AgentFn = async (_ctx, prompt) => prompt;
@@ -14,10 +14,10 @@ describe("extractMetaOrThrow", () => {
test("dryRun returns dryRunMeta without calling fetch", async () => {
let calls = 0;
globalThis.fetch = () => {
globalThis.fetch = (() => {
calls += 1;
return Promise.resolve(new Response("{}", { status: 200 }));
};
}) as unknown as typeof fetch;
const schema = z.object({ n: z.number() });
const out = await extractMetaOrThrow("r", "raw", schema, {
@@ -33,7 +33,7 @@ describe("extractMetaOrThrow", () => {
});
test("throws when extraction fails after retry", async () => {
globalThis.fetch = () =>
globalThis.fetch = (() =>
Promise.resolve(
new Response(
JSON.stringify({
@@ -49,7 +49,7 @@ describe("extractMetaOrThrow", () => {
}),
{ status: 200, headers: { "Content-Type": "application/json" } },
),
);
)) as unknown as typeof fetch;
const schema = z.object({ n: z.number() });
@@ -61,7 +61,7 @@ describe("extractMetaOrThrow", () => {
});
test("returns validated meta on successful tool call", async () => {
globalThis.fetch = () =>
globalThis.fetch = (() =>
Promise.resolve(
new Response(
JSON.stringify({
@@ -82,7 +82,7 @@ describe("extractMetaOrThrow", () => {
}),
{ status: 200, headers: { "Content-Type": "application/json" } },
),
);
)) as unknown as typeof fetch;
const schema = z.object({
branch: z.string(),
@@ -17,7 +17,7 @@ describe("llmExtract", () => {
let capturedUrl: string | null = null;
let capturedInit: RequestInit | null = null;
globalThis.fetch = (input: RequestInfo | URL, init?: RequestInit) => {
globalThis.fetch = ((input: Request | string | URL, init?: RequestInit) => {
capturedUrl = typeof input === "string" ? input : input.toString();
capturedInit = init ?? null;
return Promise.resolve(
@@ -44,7 +44,7 @@ describe("llmExtract", () => {
{ status: 200, headers: { "Content-Type": "application/json" } },
),
);
};
}) as unknown as typeof fetch;
const result = await llmExtract({
text: "some plan",
@@ -66,13 +66,13 @@ describe("llmExtract", () => {
}
expect(result.value).toEqual({ name: "cpu-usage", description: "CPU load" });
expect(capturedUrl).toBe("https://example.com/v1/chat/completions");
expect(capturedInit?.method).toBe("POST");
expect(capturedInit?.headers).toMatchObject({
expect(capturedUrl!).toBe("https://example.com/v1/chat/completions");
expect(capturedInit!.method).toBe("POST");
expect(capturedInit!.headers).toMatchObject({
Authorization: "Bearer k",
"Content-Type": "application/json",
});
const body = JSON.parse(capturedInit?.body as string) as {
const body = JSON.parse(capturedInit!.body as string) as {
model: string;
tool_choice: { function: { name: string } };
};
@@ -83,7 +83,7 @@ describe("llmExtract", () => {
test("returns schema_validation_failed when arguments do not match the schema", async () => {
const schema = z.object({ n: z.number() });
globalThis.fetch = () =>
globalThis.fetch = (() =>
Promise.resolve(
new Response(
JSON.stringify({
@@ -99,7 +99,7 @@ describe("llmExtract", () => {
}),
{ status: 200, headers: { "Content-Type": "application/json" } },
),
);
)) as unknown as typeof fetch;
const result = await llmExtract({
text: "x",
@@ -120,10 +120,10 @@ describe("llmExtract", () => {
test("dryRun skips fetch and returns dryRunMeta", async () => {
let calls = 0;
globalThis.fetch = () => {
globalThis.fetch = (() => {
calls += 1;
return Promise.resolve(new Response("{}", { status: 200 }));
};
}) as unknown as typeof fetch;
const schema = z.object({ n: z.number() });
const result = await llmExtract({