Compare commits

...

1 Commits

Author SHA1 Message Date
xingyue 9407a26806 fix(workflow): resolve type errors across all packages and remove tsbuildinfo from tracking
- Add bun-types and @types/xxhashjs to root devDependencies, remove @types/node
- Add types: ['bun-types'] to root and package tsconfigs
- Fix AST Node type narrowing in bundle-validator.ts with AcornNode type and narrowNode helper
- Fix generic ThreadContext variance in create-role-moderator.ts
- Add explicit parameter types in worker.ts
- Fix ChildProcess type in worker-spawn.ts to match spawn() stdio config
- Fix mock fetch type 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
- Add typecheck script (bunx tsc --build) to package.json
- Update check script to run typecheck before biome
- Remove all tsconfig.tsbuildinfo from git tracking
- Add tsconfig.tsbuildinfo to .gitignore
2026-05-06 17:51:52 +08:00
26 changed files with 80 additions and 57 deletions
+1
View File
@@ -2,3 +2,4 @@ node_modules/
dist/
bun.lock
*.tgz
tsconfig.tsbuildinfo
+5 -2
View File
@@ -7,11 +7,14 @@
],
"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"
},
"devDependencies": {
"@biomejs/biome": "^2.4.14"
"@biomejs/biome": "^2.4.14",
"@types/xxhashjs": "^0.2.4",
"bun-types": "^1.3.13"
}
}
+2 -2
View File
@@ -1,4 +1,4 @@
import { type ChildProcessWithoutNullStreams, spawn } from "node:child_process";
import { type ChildProcess, spawn } from "node:child_process";
import { mkdir, readdir, unlink, writeFile } from "node:fs/promises";
import { createConnection } from "node:net";
import { join } from "node:path";
@@ -23,7 +23,7 @@ function isProcessAlive(pid: number): boolean {
async function waitForReadyLine(
childStdout: NodeJS.ReadableStream,
child: ChildProcessWithoutNullStreams,
child: ChildProcess,
): Promise<Result<number, string>> {
return await new Promise((resolve) => {
let buf = "";
+2 -1
View File
@@ -14,7 +14,8 @@
"sourceMap": true,
"composite": true,
"outDir": "dist",
"rootDir": "src"
"rootDir": "src",
"types": ["bun-types"]
},
"references": [{ "path": "../workflow" }],
"include": ["src/**/*.ts"]
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -12,5 +12,8 @@
"@uncaged/workflow": "workspace:*",
"@uncaged/workflow-util-role": "workspace:*",
"zod": "^4.0.0"
},
"devDependencies": {
"@types/node": "^25.6.0"
}
}
@@ -3,7 +3,8 @@
"compilerOptions": {
"rootDir": "src",
"outDir": "dist",
"composite": true
"composite": true,
"types": ["bun-types"]
},
"include": ["src/**/*.ts"],
"references": [{ "path": "../workflow" }, { "path": "../workflow-util-role" }]
File diff suppressed because one or more lines are too long
@@ -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);
@@ -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;
File diff suppressed because one or more lines are too long
@@ -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) => {
File diff suppressed because one or more lines are too long
@@ -1,4 +1,5 @@
import { describe, expect, test } from "bun:test";
import type { RoleMeta } from "@uncaged/workflow";
import {
END,
type RoleStep,
@@ -6,7 +7,6 @@ import {
type ThreadContext,
validateWorkflowDescriptor,
} from "@uncaged/workflow";
import { buildSolveIssueDescriptor } from "../src/descriptor.js";
import { solveIssueModerator } from "../src/moderator.js";
import { createSolveIssueRoles, type SolveIssueMeta } from "../src/roles.js";
@@ -114,7 +114,7 @@ describe("createSolveIssueRoles", () => {
expect(typeof roles.reviewer).toBe("function");
expect(typeof roles.committer).toBe("function");
const ctx = makeCtx(10, []);
const ctx = makeCtx(10, []) as unknown as ThreadContext<RoleMeta>;
const plannerOut = await roles.planner(ctx);
expect(plannerOut.meta.plan).toBe("");
expect(Array.isArray(plannerOut.meta.files)).toBe(true);
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -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(),
@@ -14,12 +14,12 @@ describe("llmExtract", () => {
})
.describe("Extract sense metadata from plan");
let capturedUrl: string | null = null;
let capturedInit: RequestInit | null = null;
let capturedUrl = "";
let capturedInit: RequestInit = {};
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;
capturedInit = init ?? {};
return Promise.resolve(
new Response(
JSON.stringify({
@@ -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",
@@ -67,12 +67,12 @@ 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(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({
+19 -7
View File
@@ -10,6 +10,18 @@ import type {
Program,
VariableDeclaration,
} from "acorn";
/** Acorn Node with index-access for property traversal. */
type AcornNode = Node & { [key: string]: unknown };
/**
* Narrow an Acorn Node to a specific AST subtype after a `.type` guard.
* Avoids double-cast (`as unknown as T`) by going through AcornNode.
*/
function narrowNode<T extends Node>(node: Node): T {
return node as unknown as T;
}
import * as acorn from "acorn";
import { err, ok, type Result } from "./result.js";
@@ -55,7 +67,7 @@ function pushNestedAstNodes(value: unknown, out: Node[]): void {
function collectChildNodes(node: Node): Node[] {
const children: Node[] = [];
for (const key of Object.keys(node)) {
const val = (node as Record<string, unknown>)[key];
const val = (node as AcornNode)[key];
pushNestedAstNodes(val, children);
}
return children;
@@ -273,10 +285,10 @@ function descriptorExportExists(program: Program): boolean {
}
function stringLiteralModuleSpecifier(src: Node): string | null {
if (src.type !== "Literal" || typeof src.value !== "string") {
if (src.type !== "Literal" || typeof (src as AcornNode).value !== "string") {
return null;
}
return src.value;
return (src as AcornNode).value as string;
}
function validateImportDeclaration(node: ImportDeclaration): string | null {
@@ -337,16 +349,16 @@ function bundleConstraintViolationForNode(node: Node): string | null {
return "dynamic import() is not allowed in workflow bundles";
}
if (node.type === "ImportDeclaration") {
return validateImportDeclaration(node);
return validateImportDeclaration(narrowNode<ImportDeclaration>(node));
}
if (node.type === "ExportNamedDeclaration") {
return validateExportNamedDeclaration(node);
return validateExportNamedDeclaration(narrowNode<ExportNamedDeclaration>(node));
}
if (node.type === "ExportAllDeclaration") {
return validateExportAllDeclaration(node);
return validateExportAllDeclaration(narrowNode<ExportAllDeclaration>(node));
}
if (node.type === "CallExpression") {
return validateRequireCall(node);
return validateRequireCall(narrowNode<CallExpression>(node));
}
return null;
}
@@ -69,7 +69,7 @@ export function createRoleModerator<M extends RoleMeta>(
return { returnCode: 1, summary: `unknown role: ${next}` };
}
const result = await roleFn(ctx);
const result = await roleFn(ctx as unknown as ThreadContext);
const ts = Date.now();
const step = {
role: next,
+2 -2
View File
@@ -423,7 +423,7 @@ async function main(): Promise<void> {
});
}
const server = createServer((socket) => {
const server = createServer((socket: Socket) => {
void (async () => {
const line = await readLineFromSocket(socket);
if (line === null) {
@@ -439,7 +439,7 @@ async function main(): Promise<void> {
})();
});
server.on("error", (errObj) => {
server.on("error", (errObj: Error) => {
bootLog("W8YK4NPX", `worker server error: ${errObj.message}`);
process.exit(1);
});
+2 -1
View File
@@ -14,7 +14,8 @@
"sourceMap": true,
"composite": true,
"outDir": "dist",
"rootDir": "src"
"rootDir": "src",
"types": ["bun-types"]
},
"include": ["src/**/*.ts", "xxhashjs.d.ts"]
}
File diff suppressed because one or more lines are too long
+2 -1
View File
@@ -13,7 +13,8 @@
"declarationMap": true,
"sourceMap": true,
"composite": true,
"outDir": "dist"
"outDir": "dist",
"types": ["bun-types"]
},
"references": [
{ "path": "packages/workflow" },