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