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:
+2
-1
@@ -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({
|
||||||
|
|||||||
Reference in New Issue
Block a user