refactor: remove schemaDefaults, use caller-provided dryRunMeta
Cursor completed removal of schemaDefaults. All dry-run paths now use explicit dryRunMeta from the caller.
This commit is contained in:
@@ -58,7 +58,7 @@ describe("createCommitterRole", () => {
|
|||||||
};
|
};
|
||||||
const role = createCommitterRole(
|
const role = createCommitterRole(
|
||||||
agent,
|
agent,
|
||||||
{ provider, dryRun: null },
|
{ provider, dryRun: null, dryRunMeta: { branch: "dry-run", message: "chore: dry run" } },
|
||||||
{ cwd: repo, remote: "origin", threadId: null },
|
{ cwd: repo, remote: "origin", threadId: null },
|
||||||
);
|
);
|
||||||
const out = await role(makeCtx());
|
const out = await role(makeCtx());
|
||||||
@@ -69,7 +69,11 @@ describe("createCommitterRole", () => {
|
|||||||
const agent: AgentFn = async () => {
|
const agent: AgentFn = async () => {
|
||||||
throw new Error("agent should not run");
|
throw new Error("agent should not run");
|
||||||
};
|
};
|
||||||
const role = createCommitterRole(agent, { provider, dryRun: true });
|
const role = createCommitterRole(agent, {
|
||||||
|
provider,
|
||||||
|
dryRun: true,
|
||||||
|
dryRunMeta: { branch: "dry-run", message: "chore: dry run" },
|
||||||
|
});
|
||||||
const out = await role(makeCtx());
|
const out = await role(makeCtx());
|
||||||
expect(out.content).toBe("[dry-run] committer skipped");
|
expect(out.content).toBe("[dry-run] committer skipped");
|
||||||
expect(out.meta).toEqual({ committed: true });
|
expect(out.meta).toEqual({ committed: true });
|
||||||
@@ -87,7 +91,7 @@ describe("createCommitterRole", () => {
|
|||||||
const agent: AgentFn = async () => "plan text";
|
const agent: AgentFn = async () => "plan text";
|
||||||
const role = createCommitterRole(
|
const role = createCommitterRole(
|
||||||
agent,
|
agent,
|
||||||
{ provider, dryRun: null },
|
{ provider, dryRun: null, dryRunMeta: { branch: "dry-run", message: "chore: dry run" } },
|
||||||
{ cwd: repo, remote: "origin", threadId: null },
|
{ cwd: repo, remote: "origin", threadId: null },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ const committerPlanSchema = z.object({
|
|||||||
message: z.string().describe("Single-line conventional commit subject"),
|
message: z.string().describe("Single-line conventional commit subject"),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export type CommitterPlanMeta = z.infer<typeof committerPlanSchema>;
|
||||||
|
|
||||||
export type CommitterGitConfig = {
|
export type CommitterGitConfig = {
|
||||||
cwd: string;
|
cwd: string;
|
||||||
remote: string;
|
remote: string;
|
||||||
@@ -90,7 +92,7 @@ Reply with enough detail that a maintainer understands the change; structured ex
|
|||||||
async function runCommitterPipeline(
|
async function runCommitterPipeline(
|
||||||
ctx: ThreadContext,
|
ctx: ThreadContext,
|
||||||
agent: AgentFn,
|
agent: AgentFn,
|
||||||
extract: { provider: LlmProvider; dryRun: boolean | null },
|
extract: { provider: LlmProvider; dryRun: boolean | null; dryRunMeta: CommitterPlanMeta },
|
||||||
gitConfig: CommitterGitConfig,
|
gitConfig: CommitterGitConfig,
|
||||||
): Promise<RoleResult<CommitterMeta>> {
|
): Promise<RoleResult<CommitterMeta>> {
|
||||||
const cwd = gitConfig.cwd;
|
const cwd = gitConfig.cwd;
|
||||||
@@ -107,6 +109,7 @@ async function runCommitterPipeline(
|
|||||||
const plan = await extractMetaOrThrow("committer-plan", raw, committerPlanSchema, {
|
const plan = await extractMetaOrThrow("committer-plan", raw, committerPlanSchema, {
|
||||||
provider: extract.provider,
|
provider: extract.provider,
|
||||||
dryRun: resolveExtractDryRun(extract.dryRun),
|
dryRun: resolveExtractDryRun(extract.dryRun),
|
||||||
|
dryRunMeta: extract.dryRunMeta,
|
||||||
});
|
});
|
||||||
|
|
||||||
const branch = sanitizeBranch(plan.branch);
|
const branch = sanitizeBranch(plan.branch);
|
||||||
@@ -129,7 +132,7 @@ async function runCommitterPipeline(
|
|||||||
*/
|
*/
|
||||||
export function createCommitterRole(
|
export function createCommitterRole(
|
||||||
adapter: AgentFn,
|
adapter: AgentFn,
|
||||||
extract: { provider: LlmProvider; dryRun: boolean | null },
|
extract: { provider: LlmProvider; dryRun: boolean | null; dryRunMeta: CommitterPlanMeta },
|
||||||
gitConfig: CommitterGitConfig = DEFAULT_COMMITTER_GIT_CONFIG,
|
gitConfig: CommitterGitConfig = DEFAULT_COMMITTER_GIT_CONFIG,
|
||||||
): Role<CommitterMeta> {
|
): Role<CommitterMeta> {
|
||||||
const inner: Role<CommitterMeta> = async (ctx) =>
|
const inner: Role<CommitterMeta> = async (ctx) =>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export {
|
export {
|
||||||
type CommitterGitConfig,
|
type CommitterGitConfig,
|
||||||
type CommitterMeta,
|
type CommitterMeta,
|
||||||
|
type CommitterPlanMeta,
|
||||||
committerMetaSchema,
|
committerMetaSchema,
|
||||||
createCommitterRole,
|
createCommitterRole,
|
||||||
DEFAULT_COMMITTER_GIT_CONFIG,
|
DEFAULT_COMMITTER_GIT_CONFIG,
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ describe("createRole", () => {
|
|||||||
schema,
|
schema,
|
||||||
systemPrompt: "hello",
|
systemPrompt: "hello",
|
||||||
agent,
|
agent,
|
||||||
extract: { provider, dryRun: null },
|
extract: { provider, dryRun: null, dryRunMeta: { n: 0 } },
|
||||||
});
|
});
|
||||||
|
|
||||||
const out = await role(makeCtx());
|
const out = await role(makeCtx());
|
||||||
@@ -85,7 +85,7 @@ describe("createRole", () => {
|
|||||||
schema: z.object({ n: z.number() }),
|
schema: z.object({ n: z.number() }),
|
||||||
systemPrompt: "p",
|
systemPrompt: "p",
|
||||||
agent,
|
agent,
|
||||||
extract: { provider, dryRun: null },
|
extract: { provider, dryRun: null, dryRunMeta: { n: 0 } },
|
||||||
});
|
});
|
||||||
await role(makeCtx());
|
await role(makeCtx());
|
||||||
|
|
||||||
@@ -103,7 +103,7 @@ describe("createRole", () => {
|
|||||||
schema,
|
schema,
|
||||||
systemPrompt: async (ctx) => `rounds=${ctx.steps.length}`,
|
systemPrompt: async (ctx) => `rounds=${ctx.steps.length}`,
|
||||||
agent,
|
agent,
|
||||||
extract: { provider, dryRun: null },
|
extract: { provider, dryRun: null, dryRunMeta: { n: 0 } },
|
||||||
});
|
});
|
||||||
|
|
||||||
const ctx = makeCtx();
|
const ctx = makeCtx();
|
||||||
@@ -121,7 +121,7 @@ describe("createRole", () => {
|
|||||||
schema: z.object({ n: z.number() }),
|
schema: z.object({ n: z.number() }),
|
||||||
systemPrompt: "p",
|
systemPrompt: "p",
|
||||||
agent,
|
agent,
|
||||||
extract: { provider, dryRun: null },
|
extract: { provider, dryRun: null, dryRunMeta: { n: 0 } },
|
||||||
});
|
});
|
||||||
await role(makeCtx());
|
await role(makeCtx());
|
||||||
|
|
||||||
@@ -129,7 +129,7 @@ describe("createRole", () => {
|
|||||||
"r1",
|
"r1",
|
||||||
"raw",
|
"raw",
|
||||||
expect.anything(),
|
expect.anything(),
|
||||||
expect.objectContaining({ provider, dryRun: false }),
|
expect.objectContaining({ provider, dryRun: false, dryRunMeta: { n: 0 } }),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -142,7 +142,7 @@ describe("createRole", () => {
|
|||||||
schema: z.object({ n: z.number() }),
|
schema: z.object({ n: z.number() }),
|
||||||
systemPrompt: "p",
|
systemPrompt: "p",
|
||||||
agent,
|
agent,
|
||||||
extract: { provider, dryRun: true },
|
extract: { provider, dryRun: true, dryRunMeta: { n: 0 } },
|
||||||
});
|
});
|
||||||
await role(makeCtx());
|
await role(makeCtx());
|
||||||
|
|
||||||
@@ -150,7 +150,7 @@ describe("createRole", () => {
|
|||||||
"r2",
|
"r2",
|
||||||
"raw",
|
"raw",
|
||||||
expect.anything(),
|
expect.anything(),
|
||||||
expect.objectContaining({ dryRun: true }),
|
expect.objectContaining({ dryRun: true, dryRunMeta: { n: 0 } }),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const provider = {
|
|||||||
describe("extractMetaOrThrow", () => {
|
describe("extractMetaOrThrow", () => {
|
||||||
const originalFetch = globalThis.fetch;
|
const originalFetch = globalThis.fetch;
|
||||||
|
|
||||||
test("dryRun returns schema-shaped defaults 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;
|
||||||
@@ -23,12 +23,13 @@ describe("extractMetaOrThrow", () => {
|
|||||||
const out = await extractMetaOrThrow("r", "raw", schema, {
|
const out = await extractMetaOrThrow("r", "raw", schema, {
|
||||||
provider,
|
provider,
|
||||||
dryRun: true,
|
dryRun: true,
|
||||||
|
dryRunMeta: { n: 7 },
|
||||||
});
|
});
|
||||||
|
|
||||||
globalThis.fetch = originalFetch;
|
globalThis.fetch = originalFetch;
|
||||||
|
|
||||||
expect(calls).toBe(0);
|
expect(calls).toBe(0);
|
||||||
expect(out).toEqual({ n: 0 });
|
expect(out).toEqual({ n: 7 });
|
||||||
});
|
});
|
||||||
|
|
||||||
test("throws when extraction fails after retry", async () => {
|
test("throws when extraction fails after retry", async () => {
|
||||||
@@ -53,7 +54,7 @@ describe("extractMetaOrThrow", () => {
|
|||||||
const schema = z.object({ n: z.number() });
|
const schema = z.object({ n: z.number() });
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
extractMetaOrThrow("plan", "text", schema, { provider, dryRun: false }),
|
extractMetaOrThrow("plan", "text", schema, { provider, dryRun: false, dryRunMeta: { n: 0 } }),
|
||||||
).rejects.toThrow(/structured extraction failed after retry/);
|
).rejects.toThrow(/structured extraction failed after retry/);
|
||||||
|
|
||||||
globalThis.fetch = originalFetch;
|
globalThis.fetch = originalFetch;
|
||||||
@@ -91,6 +92,7 @@ describe("extractMetaOrThrow", () => {
|
|||||||
const out = await extractMetaOrThrow("committer-plan", "plan text", schema, {
|
const out = await extractMetaOrThrow("committer-plan", "plan text", schema, {
|
||||||
provider,
|
provider,
|
||||||
dryRun: false,
|
dryRun: false,
|
||||||
|
dryRunMeta: { branch: "", message: "" },
|
||||||
});
|
});
|
||||||
|
|
||||||
globalThis.fetch = originalFetch;
|
globalThis.fetch = originalFetch;
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ describe("llmExtract", () => {
|
|||||||
model: "m",
|
model: "m",
|
||||||
},
|
},
|
||||||
dryRun: false,
|
dryRun: false,
|
||||||
|
dryRunMeta: { name: "", description: "" },
|
||||||
});
|
});
|
||||||
|
|
||||||
globalThis.fetch = originalFetch;
|
globalThis.fetch = originalFetch;
|
||||||
@@ -105,6 +106,7 @@ describe("llmExtract", () => {
|
|||||||
schema,
|
schema,
|
||||||
provider: { baseUrl: "https://example.com", apiKey: "k", model: "m" },
|
provider: { baseUrl: "https://example.com", apiKey: "k", model: "m" },
|
||||||
dryRun: false,
|
dryRun: false,
|
||||||
|
dryRunMeta: { n: 0 },
|
||||||
});
|
});
|
||||||
|
|
||||||
globalThis.fetch = originalFetch;
|
globalThis.fetch = originalFetch;
|
||||||
@@ -116,7 +118,7 @@ describe("llmExtract", () => {
|
|||||||
expect(result.error.kind).toBe("schema_validation_failed");
|
expect(result.error.kind).toBe("schema_validation_failed");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("dryRun skips fetch and returns schema-shaped stub values", async () => {
|
test("dryRun skips fetch and returns dryRunMeta", async () => {
|
||||||
let calls = 0;
|
let calls = 0;
|
||||||
globalThis.fetch = () => {
|
globalThis.fetch = () => {
|
||||||
calls += 1;
|
calls += 1;
|
||||||
@@ -129,6 +131,7 @@ describe("llmExtract", () => {
|
|||||||
schema,
|
schema,
|
||||||
provider: { baseUrl: "https://example.com", apiKey: "k", model: "m" },
|
provider: { baseUrl: "https://example.com", apiKey: "k", model: "m" },
|
||||||
dryRun: true,
|
dryRun: true,
|
||||||
|
dryRunMeta: { n: 42 },
|
||||||
});
|
});
|
||||||
|
|
||||||
globalThis.fetch = originalFetch;
|
globalThis.fetch = originalFetch;
|
||||||
@@ -138,6 +141,6 @@ describe("llmExtract", () => {
|
|||||||
if (!result.ok) {
|
if (!result.ok) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
expect(result.value).toEqual({ n: 0 });
|
expect(result.value).toEqual({ n: 42 });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,8 +10,9 @@ export type CreateRoleArgs<M extends Record<string, unknown>> = {
|
|||||||
agent: AgentFn;
|
agent: AgentFn;
|
||||||
extract: {
|
extract: {
|
||||||
provider: LlmProvider;
|
provider: LlmProvider;
|
||||||
/** When `true`, structured extract returns schema-shaped defaults. When `null`, live API extract. */
|
/** When `true`, structured extract returns `dryRunMeta`. When `null`, live API extract. */
|
||||||
dryRun: boolean | null;
|
dryRun: boolean | null;
|
||||||
|
dryRunMeta: M;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -28,6 +29,7 @@ export function createRole<M extends Record<string, unknown>>(args: CreateRoleAr
|
|||||||
const meta = await extractMetaOrThrow(args.name, raw, args.schema, {
|
const meta = await extractMetaOrThrow(args.name, raw, args.schema, {
|
||||||
provider: args.extract.provider,
|
provider: args.extract.provider,
|
||||||
dryRun: resolveExtractDryRun(args.extract.dryRun),
|
dryRun: resolveExtractDryRun(args.extract.dryRun),
|
||||||
|
dryRunMeta: args.extract.dryRunMeta,
|
||||||
});
|
});
|
||||||
return { content: raw, meta };
|
return { content: raw, meta };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,13 +6,14 @@ export async function extractMetaOrThrow<T extends Record<string, unknown>>(
|
|||||||
roleName: string,
|
roleName: string,
|
||||||
raw: string,
|
raw: string,
|
||||||
schema: z.ZodType<T>,
|
schema: z.ZodType<T>,
|
||||||
options: { provider: LlmProvider; dryRun: boolean },
|
options: { provider: LlmProvider; dryRun: boolean; dryRunMeta: T },
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
const result = await llmExtractWithRetry({
|
const result = await llmExtractWithRetry({
|
||||||
text: raw,
|
text: raw,
|
||||||
schema,
|
schema,
|
||||||
provider: options.provider,
|
provider: options.provider,
|
||||||
dryRun: options.dryRun,
|
dryRun: options.dryRun,
|
||||||
|
dryRunMeta: options.dryRunMeta,
|
||||||
});
|
});
|
||||||
if (!result.ok) {
|
if (!result.ok) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ export {
|
|||||||
type OnFailOptions,
|
type OnFailOptions,
|
||||||
onFail,
|
onFail,
|
||||||
type RoleDecorator,
|
type RoleDecorator,
|
||||||
schemaDefaults,
|
|
||||||
type WithDryRunOptions,
|
type WithDryRunOptions,
|
||||||
withDryRun,
|
withDryRun,
|
||||||
} from "@uncaged/workflow-util-role";
|
} from "@uncaged/workflow-util-role";
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { err, ok, type Result } from "@uncaged/workflow";
|
import { err, ok, type Result } from "@uncaged/workflow";
|
||||||
import { schemaDefaults } from "@uncaged/workflow-util-role";
|
|
||||||
import * as z from "zod/v4";
|
import * as z from "zod/v4";
|
||||||
import type { LlmProvider } from "./types.js";
|
import type { LlmProvider } from "./types.js";
|
||||||
|
|
||||||
@@ -10,6 +9,8 @@ export type LlmExtractArgs<T> = {
|
|||||||
schema: z.ZodType<T>;
|
schema: z.ZodType<T>;
|
||||||
provider: LlmProvider;
|
provider: LlmProvider;
|
||||||
dryRun: boolean;
|
dryRun: boolean;
|
||||||
|
/** Returned when `dryRun` is true (ignored for live extract). */
|
||||||
|
dryRunMeta: T;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LlmError =
|
export type LlmError =
|
||||||
@@ -128,7 +129,7 @@ async function performLlmExtract<T>(
|
|||||||
options: LlmExtractArgs<T> & { userContent: string },
|
options: LlmExtractArgs<T> & { userContent: string },
|
||||||
): Promise<Result<T, LlmError>> {
|
): Promise<Result<T, LlmError>> {
|
||||||
if (options.dryRun) {
|
if (options.dryRun) {
|
||||||
return ok(schemaDefaults(options.schema) as T);
|
return ok(options.dryRunMeta);
|
||||||
}
|
}
|
||||||
|
|
||||||
const rawJsonSchema = z.toJSONSchema(options.schema) as Record<string, unknown>;
|
const rawJsonSchema = z.toJSONSchema(options.schema) as Record<string, unknown>;
|
||||||
|
|||||||
@@ -58,7 +58,11 @@ describe("createReviewerRole", () => {
|
|||||||
return "review done";
|
return "review done";
|
||||||
};
|
};
|
||||||
|
|
||||||
const role = createReviewerRole(agent, { provider, dryRun: null });
|
const role = createReviewerRole(agent, {
|
||||||
|
provider,
|
||||||
|
dryRun: null,
|
||||||
|
dryRunMeta: { approved: true },
|
||||||
|
});
|
||||||
const out = await role(makeCtx());
|
const out = await role(makeCtx());
|
||||||
expect(out.meta).toEqual({ approved: true });
|
expect(out.meta).toEqual({ approved: true });
|
||||||
});
|
});
|
||||||
@@ -74,7 +78,7 @@ describe("createReviewerRole", () => {
|
|||||||
|
|
||||||
const role = createReviewerRole(
|
const role = createReviewerRole(
|
||||||
agent,
|
agent,
|
||||||
{ provider, dryRun: null },
|
{ provider, dryRun: null, dryRunMeta: { approved: false } },
|
||||||
{
|
{
|
||||||
cwd: "/proj",
|
cwd: "/proj",
|
||||||
conventionsPath: null,
|
conventionsPath: null,
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ or
|
|||||||
*/
|
*/
|
||||||
export function createReviewerRole(
|
export function createReviewerRole(
|
||||||
adapter: AgentFn,
|
adapter: AgentFn,
|
||||||
extract: { provider: LlmProvider; dryRun: boolean | null },
|
extract: { provider: LlmProvider; dryRun: boolean | null; dryRunMeta: ReviewerMeta },
|
||||||
config: ReviewerConfig = DEFAULT_REVIEWER_CONFIG,
|
config: ReviewerConfig = DEFAULT_REVIEWER_CONFIG,
|
||||||
): Role<ReviewerMeta> {
|
): Role<ReviewerMeta> {
|
||||||
return createRole({
|
return createRole({
|
||||||
@@ -98,6 +98,10 @@ export function createReviewerRole(
|
|||||||
schema: reviewerMetaSchema,
|
schema: reviewerMetaSchema,
|
||||||
systemPrompt: async (ctx) => reviewerPrompt(config, ctx),
|
systemPrompt: async (ctx) => reviewerPrompt(config, ctx),
|
||||||
agent: adapter,
|
agent: adapter,
|
||||||
extract: { provider: extract.provider, dryRun: extract.dryRun },
|
extract: {
|
||||||
|
provider: extract.provider,
|
||||||
|
dryRun: extract.dryRun,
|
||||||
|
dryRunMeta: extract.dryRunMeta,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import type { AgentFn, Role } from "@uncaged/workflow";
|
import type { AgentFn, Role } from "@uncaged/workflow";
|
||||||
import { type CommitterMeta, createCommitterRole } from "@uncaged/workflow-role-committer";
|
import {
|
||||||
|
type CommitterMeta,
|
||||||
|
type CommitterPlanMeta,
|
||||||
|
createCommitterRole,
|
||||||
|
} from "@uncaged/workflow-role-committer";
|
||||||
import { createRole, type LlmProvider } from "@uncaged/workflow-role-llm";
|
import { createRole, type LlmProvider } from "@uncaged/workflow-role-llm";
|
||||||
import { createReviewerRole, type ReviewerMeta } from "@uncaged/workflow-role-reviewer";
|
import { createReviewerRole, type ReviewerMeta } from "@uncaged/workflow-role-reviewer";
|
||||||
import * as z from "zod/v4";
|
import * as z from "zod/v4";
|
||||||
@@ -33,6 +37,26 @@ export type PlannerMeta = z.infer<typeof plannerMetaSchema>;
|
|||||||
|
|
||||||
export type CoderMeta = z.infer<typeof coderMetaSchema>;
|
export type CoderMeta = z.infer<typeof coderMetaSchema>;
|
||||||
|
|
||||||
|
const PLANNER_DRY_RUN_META: PlannerMeta = {
|
||||||
|
plan: "",
|
||||||
|
files: [],
|
||||||
|
approach: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const CODER_DRY_RUN_META: CoderMeta = {
|
||||||
|
filesChanged: [],
|
||||||
|
summary: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const REVIEWER_DRY_RUN_META: ReviewerMeta = {
|
||||||
|
approved: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const COMMITTER_PLAN_DRY_RUN_META: CommitterPlanMeta = {
|
||||||
|
branch: "dry-run",
|
||||||
|
message: "chore: dry run",
|
||||||
|
};
|
||||||
|
|
||||||
export type SolveIssueMeta = {
|
export type SolveIssueMeta = {
|
||||||
planner: PlannerMeta;
|
planner: PlannerMeta;
|
||||||
coder: CoderMeta;
|
coder: CoderMeta;
|
||||||
@@ -40,7 +64,7 @@ export type SolveIssueMeta = {
|
|||||||
committer: CommitterMeta;
|
committer: CommitterMeta;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Wiring for workflow-role LLM structured extraction. Use null for schema-default dry runs (tests / stubs). */
|
/** Wiring for workflow-role LLM structured extraction. Use `null` for stub extract (dry-run meta from built-in placeholders). */
|
||||||
export type SolveIssueRolesConfig = {
|
export type SolveIssueRolesConfig = {
|
||||||
agent: AgentFn;
|
agent: AgentFn;
|
||||||
workdir: string;
|
workdir: string;
|
||||||
@@ -83,7 +107,11 @@ export function createSolveIssueRoles(config: SolveIssueRolesConfig): SolveIssue
|
|||||||
schema: plannerMetaSchema,
|
schema: plannerMetaSchema,
|
||||||
systemPrompt: PLANNER_SYSTEM,
|
systemPrompt: PLANNER_SYSTEM,
|
||||||
agent: config.agent,
|
agent: config.agent,
|
||||||
extract: { provider: extract.provider, dryRun: extract.dryRun },
|
extract: {
|
||||||
|
provider: extract.provider,
|
||||||
|
dryRun: extract.dryRun,
|
||||||
|
dryRunMeta: PLANNER_DRY_RUN_META,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const coder: Role<CoderMeta> = createRole({
|
const coder: Role<CoderMeta> = createRole({
|
||||||
@@ -91,18 +119,30 @@ export function createSolveIssueRoles(config: SolveIssueRolesConfig): SolveIssue
|
|||||||
schema: coderMetaSchema,
|
schema: coderMetaSchema,
|
||||||
systemPrompt: CODER_SYSTEM,
|
systemPrompt: CODER_SYSTEM,
|
||||||
agent: config.agent,
|
agent: config.agent,
|
||||||
extract: { provider: extract.provider, dryRun: extract.dryRun },
|
extract: {
|
||||||
|
provider: extract.provider,
|
||||||
|
dryRun: extract.dryRun,
|
||||||
|
dryRunMeta: CODER_DRY_RUN_META,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const reviewer: Role<ReviewerMeta> = createReviewerRole(
|
const reviewer: Role<ReviewerMeta> = createReviewerRole(
|
||||||
config.agent,
|
config.agent,
|
||||||
{ provider: extract.provider, dryRun: extract.dryRun },
|
{
|
||||||
|
provider: extract.provider,
|
||||||
|
dryRun: extract.dryRun,
|
||||||
|
dryRunMeta: REVIEWER_DRY_RUN_META,
|
||||||
|
},
|
||||||
reviewerGit,
|
reviewerGit,
|
||||||
);
|
);
|
||||||
|
|
||||||
const committer: Role<CommitterMeta> = createCommitterRole(
|
const committer: Role<CommitterMeta> = createCommitterRole(
|
||||||
config.agent,
|
config.agent,
|
||||||
{ provider: extract.provider, dryRun: extract.dryRun },
|
{
|
||||||
|
provider: extract.provider,
|
||||||
|
dryRun: extract.dryRun,
|
||||||
|
dryRunMeta: COMMITTER_PLAN_DRY_RUN_META,
|
||||||
|
},
|
||||||
committerGit,
|
committerGit,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -6,4 +6,3 @@ export {
|
|||||||
type WithDryRunOptions,
|
type WithDryRunOptions,
|
||||||
withDryRun,
|
withDryRun,
|
||||||
} from "./decorators.js";
|
} from "./decorators.js";
|
||||||
export { schemaDefaults } from "./schema-defaults.js";
|
|
||||||
|
|||||||
@@ -1,190 +0,0 @@
|
|||||||
import type * as z from "zod/v4";
|
|
||||||
|
|
||||||
type ZodTypeAny = z.ZodType;
|
|
||||||
|
|
||||||
type Def = Record<string, unknown> & { type: string };
|
|
||||||
type TypeHandler = (schema: ZodTypeAny, def: Def) => unknown;
|
|
||||||
|
|
||||||
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
||||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isZodExactOptional(s: ZodTypeAny): boolean {
|
|
||||||
return s.constructor.name === "ZodExactOptional";
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveDefaultValue(defaultValue: unknown | (() => unknown)): unknown {
|
|
||||||
if (typeof defaultValue === "function") {
|
|
||||||
return (defaultValue as () => unknown)();
|
|
||||||
}
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
function mergeIntersection(left: unknown, right: unknown): unknown {
|
|
||||||
if (isPlainObject(left) && isPlainObject(right)) {
|
|
||||||
return { ...left, ...right };
|
|
||||||
}
|
|
||||||
return right;
|
|
||||||
}
|
|
||||||
|
|
||||||
function defaultsForObject(_schema: ZodTypeAny, def: Def): unknown {
|
|
||||||
const shape = def.shape as Record<string, ZodTypeAny> | undefined;
|
|
||||||
if (shape === undefined) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
const out: Record<string, unknown> = {};
|
|
||||||
for (const key of Object.keys(shape)) {
|
|
||||||
const child = shape[key];
|
|
||||||
const cdef = child.def as { type: string };
|
|
||||||
if (cdef.type === "optional") {
|
|
||||||
if (isZodExactOptional(child)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
out[key] = undefined;
|
|
||||||
} else {
|
|
||||||
out[key] = schemaDefaultsInner(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
function firstUnionOption(_schema: ZodTypeAny, def: Def): unknown {
|
|
||||||
const options = def.options as readonly ZodTypeAny[] | undefined;
|
|
||||||
if (options === undefined || options.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return schemaDefaultsInner(options[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function defaultsFromNullable(_schema: ZodTypeAny, _def: Def): unknown {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function defaultsFromInner(_schema: ZodTypeAny, def: Def): unknown {
|
|
||||||
const inner = def.innerType as ZodTypeAny | undefined;
|
|
||||||
if (inner === undefined) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return schemaDefaultsInner(inner);
|
|
||||||
}
|
|
||||||
|
|
||||||
function defaultsForPipe(_schema: ZodTypeAny, def: Def): unknown {
|
|
||||||
const out = def.out as ZodTypeAny | undefined;
|
|
||||||
if (out === undefined) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return schemaDefaultsInner(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
function defaultsForIntersection(_schema: ZodTypeAny, def: Def): unknown {
|
|
||||||
const left = def.left as ZodTypeAny | undefined;
|
|
||||||
const right = def.right as ZodTypeAny | undefined;
|
|
||||||
if (left === undefined || right === undefined) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return mergeIntersection(schemaDefaultsInner(left), schemaDefaultsInner(right));
|
|
||||||
}
|
|
||||||
|
|
||||||
function defaultsForTuple(_schema: ZodTypeAny, def: Def): unknown {
|
|
||||||
const items = def.items as readonly ZodTypeAny[] | undefined;
|
|
||||||
if (items === undefined) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return items.map((item) => schemaDefaultsInner(item));
|
|
||||||
}
|
|
||||||
|
|
||||||
function defaultsForLazy(schema: ZodTypeAny, def: Def): unknown {
|
|
||||||
const inner =
|
|
||||||
(schema as { _zod?: { innerType?: ZodTypeAny } })._zod?.innerType ??
|
|
||||||
(def.getter as (() => ZodTypeAny) | undefined)?.();
|
|
||||||
if (inner === undefined) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return schemaDefaultsInner(inner);
|
|
||||||
}
|
|
||||||
|
|
||||||
function defaultsForPromise(_schema: ZodTypeAny, def: Def): unknown {
|
|
||||||
const inner = def.innerType as ZodTypeAny | undefined;
|
|
||||||
if (inner === undefined) {
|
|
||||||
return Promise.resolve(null);
|
|
||||||
}
|
|
||||||
return Promise.resolve(schemaDefaultsInner(inner));
|
|
||||||
}
|
|
||||||
|
|
||||||
function firstEnumValue(_schema: ZodTypeAny, def: Def): unknown {
|
|
||||||
const entries = def.entries as Record<string, string | number> | undefined;
|
|
||||||
if (entries === undefined) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const values = Object.values(entries);
|
|
||||||
return values[0] ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function firstLiteralValue(_schema: ZodTypeAny, def: Def): unknown {
|
|
||||||
const values = def.values as unknown[] | undefined;
|
|
||||||
if (values === undefined || values.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return values[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
const TYPE_HANDLERS: Record<string, TypeHandler> = {
|
|
||||||
string: () => "",
|
|
||||||
number: () => 0,
|
|
||||||
boolean: () => false,
|
|
||||||
bigint: () => 0n,
|
|
||||||
date: () => new Date(0),
|
|
||||||
symbol: () => Symbol(),
|
|
||||||
undefined: () => undefined,
|
|
||||||
null: () => null,
|
|
||||||
void: () => undefined,
|
|
||||||
any: () => null,
|
|
||||||
unknown: () => null,
|
|
||||||
never: () => undefined,
|
|
||||||
nan: () => Number.NaN,
|
|
||||||
array: () => [],
|
|
||||||
object: defaultsForObject,
|
|
||||||
record: () => ({}),
|
|
||||||
map: () => new Map(),
|
|
||||||
set: () => new Set(),
|
|
||||||
enum: firstEnumValue,
|
|
||||||
literal: firstLiteralValue,
|
|
||||||
optional: () => undefined,
|
|
||||||
nullable: defaultsFromNullable,
|
|
||||||
default: (_s, def) => resolveDefaultValue(def.defaultValue as unknown | (() => unknown)),
|
|
||||||
prefault: (_s, def) => resolveDefaultValue(def.defaultValue as unknown | (() => unknown)),
|
|
||||||
nonoptional: defaultsFromInner,
|
|
||||||
catch: defaultsFromInner,
|
|
||||||
success: () => false,
|
|
||||||
readonly: defaultsFromInner,
|
|
||||||
union: firstUnionOption,
|
|
||||||
xor: firstUnionOption,
|
|
||||||
intersection: defaultsForIntersection,
|
|
||||||
pipe: defaultsForPipe,
|
|
||||||
transform: () => null,
|
|
||||||
tuple: defaultsForTuple,
|
|
||||||
lazy: defaultsForLazy,
|
|
||||||
promise: defaultsForPromise,
|
|
||||||
file: () => new File([], ""),
|
|
||||||
function: () => null,
|
|
||||||
custom: () => null,
|
|
||||||
template_literal: () => "",
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Produces a structurally valid placeholder that mirrors primitive/array/object
|
|
||||||
* shape for a Zod schema. Used for `llmExtract` dry runs so downstream code
|
|
||||||
* does not throw on `undefined` fields.
|
|
||||||
*/
|
|
||||||
export function schemaDefaults(schema: z.ZodType): unknown {
|
|
||||||
return schemaDefaultsInner(schema as ZodTypeAny);
|
|
||||||
}
|
|
||||||
|
|
||||||
function schemaDefaultsInner(schema: ZodTypeAny): unknown {
|
|
||||||
const def = schema.def as Def;
|
|
||||||
const run = TYPE_HANDLERS[def.type];
|
|
||||||
if (run === undefined) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return run(schema, def);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user