/** * Zod schemas for configuration validation * * Note: Uses zod v4 syntax for SDK compatibility. */ import { z } from 'zod/v4'; import { DEFAULT_LANGUAGE } from '../../shared/constants.js'; import { McpServersSchema } from './mcp-schemas.js'; export { McpServerConfigSchema, McpServersSchema } from './mcp-schemas.js'; /** Agent model schema (opus, sonnet, haiku) */ export const AgentModelSchema = z.enum(['opus', 'sonnet', 'haiku']).default('sonnet'); /** Agent configuration schema */ export const AgentConfigSchema = z.object({ name: z.string().min(1), description: z.string().optional(), model: AgentModelSchema, systemPrompt: z.string().optional(), allowedTools: z.array(z.string()).optional(), maxTurns: z.number().int().positive().optional(), }); /** Claude CLI configuration schema */ export const ClaudeConfigSchema = z.object({ command: z.string().default('claude'), timeout: z.number().int().positive().default(300000), }); /** TAKT global tool configuration schema */ export const TaktConfigSchema = z.object({ defaultModel: AgentModelSchema, defaultPiece: z.string().default('default'), agentDirs: z.array(z.string()).default([]), pieceDirs: z.array(z.string()).default([]), sessionDir: z.string().optional(), claude: ClaudeConfigSchema.default({ command: 'claude', timeout: 300000 }), }); /** Agent type schema */ export const AgentTypeSchema = z.enum(['coder', 'architect', 'supervisor', 'custom']); /** Status schema */ export const StatusSchema = z.enum([ 'pending', 'done', 'blocked', 'approved', 'rejected', 'improve', 'cancelled', 'interrupted', 'answer', ]); /** Permission mode schema for tool execution */ export const PermissionModeSchema = z.enum(['readonly', 'edit', 'full']); /** * Output contract item schema (new structured format). * * YAML format: * output_contracts: * - name: 00-plan.md * order: | * **レポート出力:** {report:00-plan.md} に出力してください。 * format: | * **出力契約:** * ```markdown * ... * ``` */ export const OutputContractItemSchema = z.object({ /** Report file name */ name: z.string().min(1), /** Instruction prepended before instruction_template (e.g., output destination) */ order: z.string().optional(), /** Instruction appended after instruction_template (e.g., output format) */ format: z.string().optional(), }); /** * Raw output contract entry — array item in output_contracts.report * * Supports: * - Label:path format: { Scope: "01-scope.md" } * - Item format: { name, order?, format? } */ export const OutputContractEntrySchema = z.union([ z.record(z.string(), z.string()), // {Scope: "01-scope.md"} format OutputContractItemSchema, // {name, order?, format?} format ]); /** * Output contracts field schema for movement-level definition. * * YAML format: * output_contracts: * report: # report array (required if output_contracts is specified) * - Scope: 01-scope.md # label:path format * - Decisions: 02-decisions.md * output_contracts: * report: * - name: 00-plan.md # name + order + format format * order: ... * format: plan */ export const OutputContractsFieldSchema = z.object({ report: z.array(OutputContractEntrySchema).optional(), }).optional(); /** Quality gates schema - AI directives for movement completion (string array) */ export const QualityGatesSchema = z.array(z.string()).optional(); /** Rule-based transition schema (new unified format) */ export const PieceRuleSchema = z.object({ /** Human-readable condition text */ condition: z.string().min(1), /** Next movement name (e.g., implement, COMPLETE, ABORT). Optional for parallel sub-movements (parent handles routing). */ next: z.string().min(1).optional(), /** Template for additional AI output */ appendix: z.string().optional(), /** Require user input before continuing (interactive mode only) */ requires_user_input: z.boolean().optional(), /** Rule applies only in interactive mode */ interactive_only: z.boolean().optional(), }); /** Sub-movement schema for parallel execution */ export const ParallelSubMovementRawSchema = z.object({ name: z.string().min(1), /** Persona reference — key name from piece-level personas map, or file path */ persona: z.string().optional(), /** Display name for the persona (shown in output) */ persona_name: z.string().optional(), /** Policy reference(s) — key name(s) from piece-level policies map */ policy: z.union([z.string(), z.array(z.string())]).optional(), /** Knowledge reference(s) — key name(s) from piece-level knowledge map */ knowledge: z.union([z.string(), z.array(z.string())]).optional(), allowed_tools: z.array(z.string()).optional(), mcp_servers: McpServersSchema, provider: z.enum(['claude', 'codex', 'mock']).optional(), model: z.string().optional(), permission_mode: PermissionModeSchema.optional(), edit: z.boolean().optional(), instruction: z.string().optional(), instruction_template: z.string().optional(), rules: z.array(PieceRuleSchema).optional(), /** Output contracts for this movement (report definitions) */ output_contracts: OutputContractsFieldSchema, /** Quality gates for this movement (AI directives) */ quality_gates: QualityGatesSchema, pass_previous_response: z.boolean().optional().default(true), }); /** Piece movement schema - raw YAML format */ export const PieceMovementRawSchema = z.object({ name: z.string().min(1), description: z.string().optional(), /** Session handling for this movement */ session: z.enum(['continue', 'refresh']).optional(), /** Persona reference — key name from piece-level personas map, or file path */ persona: z.string().optional(), /** Display name for the persona (shown in output) */ persona_name: z.string().optional(), /** Policy reference(s) — key name(s) from piece-level policies map */ policy: z.union([z.string(), z.array(z.string())]).optional(), /** Knowledge reference(s) — key name(s) from piece-level knowledge map */ knowledge: z.union([z.string(), z.array(z.string())]).optional(), allowed_tools: z.array(z.string()).optional(), mcp_servers: McpServersSchema, provider: z.enum(['claude', 'codex', 'mock']).optional(), model: z.string().optional(), /** Permission mode for tool execution in this movement */ permission_mode: PermissionModeSchema.optional(), /** Whether this movement is allowed to edit project files */ edit: z.boolean().optional(), instruction: z.string().optional(), instruction_template: z.string().optional(), /** Rules for movement routing */ rules: z.array(PieceRuleSchema).optional(), /** Output contracts for this movement (report definitions) */ output_contracts: OutputContractsFieldSchema, /** Quality gates for this movement (AI directives) */ quality_gates: QualityGatesSchema, pass_previous_response: z.boolean().optional().default(true), /** Sub-movements to execute in parallel */ parallel: z.array(ParallelSubMovementRawSchema).optional(), }); /** Loop monitor rule schema */ export const LoopMonitorRuleSchema = z.object({ /** Human-readable condition text */ condition: z.string().min(1), /** Next movement name to transition to */ next: z.string().min(1), }); /** Loop monitor judge schema */ export const LoopMonitorJudgeSchema = z.object({ /** Persona reference — key name from piece-level personas map, or file path */ persona: z.string().optional(), /** Custom instruction template for the judge */ instruction_template: z.string().optional(), /** Rules for the judge's decision */ rules: z.array(LoopMonitorRuleSchema).min(1), }); /** Loop monitor configuration schema */ export const LoopMonitorSchema = z.object({ /** Ordered list of movement names forming the cycle to detect */ cycle: z.array(z.string().min(1)).min(2), /** Number of complete cycles before triggering the judge (default: 3) */ threshold: z.number().int().positive().optional().default(3), /** Judge configuration */ judge: LoopMonitorJudgeSchema, }); /** Piece configuration schema - raw YAML format */ export const PieceConfigRawSchema = z.object({ name: z.string().min(1), description: z.string().optional(), /** Piece-level persona definitions — map of name to .md file path or inline content */ personas: z.record(z.string(), z.string()).optional(), /** Piece-level policy definitions — map of name to .md file path or inline content */ policies: z.record(z.string(), z.string()).optional(), /** Piece-level knowledge definitions — map of name to .md file path or inline content */ knowledge: z.record(z.string(), z.string()).optional(), /** Piece-level instruction definitions — map of name to .md file path or inline content */ instructions: z.record(z.string(), z.string()).optional(), /** Piece-level report format definitions — map of name to .md file path or inline content */ report_formats: z.record(z.string(), z.string()).optional(), movements: z.array(PieceMovementRawSchema).min(1), initial_movement: z.string().optional(), max_iterations: z.number().int().positive().optional().default(10), loop_monitors: z.array(LoopMonitorSchema).optional(), answer_agent: z.string().optional(), }); /** Custom agent configuration schema */ export const CustomAgentConfigSchema = z.object({ name: z.string().min(1), prompt_file: z.string().optional(), prompt: z.string().optional(), allowed_tools: z.array(z.string()).optional(), claude_agent: z.string().optional(), claude_skill: z.string().optional(), provider: z.enum(['claude', 'codex', 'mock']).optional(), model: z.string().optional(), }).refine( (data) => data.prompt_file || data.prompt || data.claude_agent || data.claude_skill, { message: 'Agent must have prompt_file, prompt, claude_agent, or claude_skill' } ); /** Debug config schema */ export const DebugConfigSchema = z.object({ enabled: z.boolean().optional().default(false), log_file: z.string().optional(), }); /** Language setting schema */ export const LanguageSchema = z.enum(['en', 'ja']); /** Pipeline execution config schema */ export const PipelineConfigSchema = z.object({ default_branch_prefix: z.string().optional(), commit_message_template: z.string().optional(), pr_body_template: z.string().optional(), }); /** Piece category config schema (recursive) */ export type PieceCategoryConfigNode = { pieces?: string[]; [key: string]: PieceCategoryConfigNode | string[] | undefined; }; export const PieceCategoryConfigNodeSchema: z.ZodType = z.lazy(() => z.object({ pieces: z.array(z.string()).optional(), }).catchall(PieceCategoryConfigNodeSchema) ); export const PieceCategoryConfigSchema = z.record(z.string(), PieceCategoryConfigNodeSchema); /** Global config schema */ export const GlobalConfigSchema = z.object({ language: LanguageSchema.optional().default(DEFAULT_LANGUAGE), default_piece: z.string().optional().default('default'), log_level: z.enum(['debug', 'info', 'warn', 'error']).optional().default('info'), provider: z.enum(['claude', 'codex', 'mock']).optional().default('claude'), model: z.string().optional(), debug: DebugConfigSchema.optional(), /** Directory for shared clones (worktree_dir in config). If empty, uses ../{clone-name} relative to project */ worktree_dir: z.string().optional(), /** Auto-create PR after worktree execution (default: prompt in interactive mode) */ auto_pr: z.boolean().optional(), /** List of builtin piece/agent names to exclude from fallback loading */ disabled_builtins: z.array(z.string()).optional().default([]), /** Enable builtin pieces from builtins/{lang}/pieces */ enable_builtin_pieces: z.boolean().optional(), /** Anthropic API key for Claude Code SDK (overridden by TAKT_ANTHROPIC_API_KEY env var) */ anthropic_api_key: z.string().optional(), /** OpenAI API key for Codex SDK (overridden by TAKT_OPENAI_API_KEY env var) */ openai_api_key: z.string().optional(), /** Pipeline execution settings */ pipeline: PipelineConfigSchema.optional(), /** Minimal output mode for CI - suppress AI output to prevent sensitive information leaks */ minimal_output: z.boolean().optional().default(false), /** Path to bookmarks file (default: ~/.takt/preferences/bookmarks.yaml) */ bookmarks_file: z.string().optional(), /** Path to piece categories file (default: ~/.takt/preferences/piece-categories.yaml) */ piece_categories_file: z.string().optional(), /** Branch name generation strategy: 'romaji' (fast, default) or 'ai' (slow) */ branch_name_strategy: z.enum(['romaji', 'ai']).optional(), /** Prevent macOS idle sleep during takt execution using caffeinate (default: false) */ prevent_sleep: z.boolean().optional(), /** Enable notification sounds (default: true when undefined) */ notification_sound: z.boolean().optional(), /** Number of movement previews to inject into interactive mode (0 to disable, max 10) */ interactive_preview_movements: z.number().int().min(0).max(10).optional().default(3), /** Number of tasks to run concurrently in takt run (default: 1 = sequential, max: 10) */ concurrency: z.number().int().min(1).max(10).optional().default(1), }); /** Project config schema */ export const ProjectConfigSchema = z.object({ piece: z.string().optional(), agents: z.array(CustomAgentConfigSchema).optional(), provider: z.enum(['claude', 'codex', 'mock']).optional(), });