scripts/worktree-feature/: Zod Output Schemas
Ghost doc. Real schema files live in .worktrees/*/scripts/worktree-feature/components/ (per-worktree); the scripts/worktree-feature/ directory does not exist on main.
Discover Schema
// components/Discover.schema.ts
import { z } from "zod";
export const Ticket = z.object({
id: z.string().describe("Unique slug identifier (lowercase kebab-case, e.g. 'vcs-jj-rewrite')"),
title: z.string().describe("Short imperative title"),
description: z.string().describe("Detailed description of what needs to be implemented"),
acceptanceCriteria: z.array(z.string()).describe("List of acceptance criteria"),
filesToModify: z.array(z.string()).describe("Files to modify"),
filesToCreate: z.array(z.string()).describe("Files to create"),
dependencies: z.array(z.string()).nullable().describe("IDs of tickets this depends on"),
});
export type Ticket = z.infer<typeof Ticket>;
export const DiscoverOutput = z.object({
tickets: z.array(Ticket).describe("All tickets to implement, ordered by dependency"),
reasoning: z.string().describe("Why these tickets were chosen and in this order"),
});
export type DiscoverOutput = z.infer<typeof DiscoverOutput>;
Implement Schema
// components/Implement.schema.ts
import { z } from "zod";
export const ImplementOutput = z.object({
filesCreated: z.array(z.string()).nullable().describe("Files created"),
filesModified: z.array(z.string()).nullable().describe("Files modified"),
commitMessages: z.array(z.string()).describe("Git commit messages made"),
whatWasDone: z.string().describe("Detailed description of what was implemented"),
testsWritten: z.array(z.string()).describe("Test files written"),
docsUpdated: z.array(z.string()).describe("Documentation files updated"),
allTestsPassing: z.boolean().describe("Whether all tests pass after implementation"),
testOutput: z.string().describe("Output from running tests"),
});
export type ImplementOutput = z.infer<typeof ImplementOutput>;
Validate Schema
// components/Validate.schema.ts
import { z } from "zod";
export const ValidateOutput = z.object({
allPassed: z.boolean().describe("Whether `bun test` exited with status 0"),
failingSummary: z.string().nullable().describe("Summary of what failed and why (null if all passed)"),
fullOutput: z.string().describe("Full output from `bun test`"),
});
export type ValidateOutput = z.infer<typeof ValidateOutput>;
Review Schema
// components/Review.schema.ts
import { z } from "zod";
export const ReviewOutput = z.object({
reviewer: z.string().default("unknown").describe("Which agent reviewed (claude, codex)"),
approved: z.boolean().describe("Whether the reviewer approves (LGTM)"),
issues: z.array(z.object({
severity: z.enum(["critical", "major", "minor", "nit"]),
file: z.string(),
line: z.number().nullable(),
description: z.string(),
suggestion: z.string().nullable(),
})).describe("Issues found during review"),
testCoverage: z.enum(["excellent", "good", "insufficient", "missing"]).describe("Test coverage assessment"),
codeQuality: z.enum(["excellent", "good", "needs-work", "poor"]).describe("Code quality assessment"),
feedback: z.string().describe("Overall review feedback"),
});
export type ReviewOutput = z.infer<typeof ReviewOutput>;
ReviewFix Schema
// components/ReviewFix.schema.ts
import { z } from "zod";
export const ReviewFixOutput = z.object({
fixesMade: z.array(z.object({
issue: z.string(),
fix: z.string(),
file: z.string(),
})).describe("Fixes applied"),
falsePositiveComments: z.array(z.object({
file: z.string(),
line: z.number(),
issue: z.string().describe("The review issue that was a false positive"),
rationale: z.string().describe("Why this is a false positive"),
})).nullable().describe("False positives to suppress in future reviews"),
commitMessages: z.array(z.string()).describe("Commit messages for fixes"),
allIssuesResolved: z.boolean().describe("Whether all review issues were resolved"),
summary: z.string().describe("Summary of fixes"),
});
export type ReviewFixOutput = z.infer<typeof ReviewFixOutput>;
Report Schema
// components/Report.schema.ts
import { z } from "zod";
export const ReportOutput = z.object({
ticketTitle: z.string().describe("Title of the ticket"),
status: z.enum(["completed", "partial", "failed"]).describe("Final status"),
summary: z.string().describe("Concise summary of what was implemented"),
filesChanged: z.number().describe("Number of files changed"),
testsAdded: z.number().describe("Number of tests added"),
reviewRounds: z.number().describe("How many review rounds it took"),
struggles: z.array(z.string()).nullable().describe("Any struggles or issues encountered"),
lessonsLearned: z.array(z.string()).nullable().describe("Lessons for future tickets"),
});
export type ReportOutput = z.infer<typeof ReportOutput>;
Registration
All schemas register in one createSmithers call:
// smithers.ts
import { createSmithers } from "smithers";
import { DiscoverOutput } from "./components/Discover.schema";
import { ImplementOutput } from "./components/Implement.schema";
import { ValidateOutput } from "./components/Validate.schema";
import { ReviewOutput } from "./components/Review.schema";
import { ReviewFixOutput } from "./components/ReviewFix.schema";
import { ReportOutput } from "./components/Report.schema";
export const { Workflow, Task, useCtx, smithers, tables } = createSmithers({
discover: DiscoverOutput,
implement: ImplementOutput,
validate: ValidateOutput,
review: ReviewOutput,
reviewFix: ReviewFixOutput,
report: ReportOutput,
}, { dbPath: `${process.env.HOME}/.cache/smithers/worktree-feature.db`, journalMode: "DELETE" });
Use tables.discover, tables.review, etc. to access the auto-generated SQLite tables for each stage.
Key Patterns
- Dual export — each file exports the Zod schema and its inferred
type, giving runtime validation and compile-time types from one source.
.describe() annotations — Smithers passes these to the LLM as field-level instructions in the structured output schema.
.nullable() — ensures optional fields are always present in output JSON (e.g., dependencies, failingSummary).
z.enum() — constrains agent output to valid values (severity levels, status codes, quality ratings).
- Schema composition —
Ticket defined once, reused in DiscoverOutput.tickets and as a prop type throughout the pipeline.
- Single registration —
createSmithers auto-generates SQLite tables and accepts an optional second argument for dbPath and journalMode; tables provides typed references to each stage’s table.