## 概要
仕様ドキュメント `/Users/m_naruse/work/git/takt/task_planning/output-contracts-and-quality-gates.md` に基づき、YAML構造の変更を実装する。
---
## タスク一覧
### 【高】トップレベル構造の変更
- 現在の `output_contracts` を `report_formats` にリネーム
- レポートテンプレート定義として機能させる
### 【高】Movement内の output_contracts 構造変更
- 各 movement の `output_contracts` が直接レポート配列を持つ構造に変更
- `output_contracts.report` の `report` キーを廃止
**変更後の構造:**
```yaml
report_formats: # トップレベル(テンプレート定義)
plan: ...
movements:
- name: plan
output_contracts: # 直接配列(reportキー不要)
- name: 00-plan.md
format: plan
```
### 【中】quality_gates の実装
- エージェントへの通達として機能させる(自動検証は将来実装)
- Movement完了時にエージェントが参照できる形式で定義
---
## 制約(ユーザー明示)
- 後方互換性は不要
---
## 確認方法
- 既存のピース定義YAMLが新構造でパースできること
- テストが通ること
313 lines
12 KiB
TypeScript
313 lines
12 KiB
TypeScript
/**
|
|
* 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';
|
|
|
|
/** 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(),
|
|
});
|
|
|
|
/**
|
|
* Output contracts field schema for movement-level definition.
|
|
*
|
|
* YAML formats:
|
|
* output_contracts: # array of label:path entries
|
|
* - Scope: 01-scope.md
|
|
* - Decisions: 02-decisions.md
|
|
* output_contracts: # array of objects (name + order + format)
|
|
* - name: 00-plan.md
|
|
* order: ...
|
|
* format: plan
|
|
*
|
|
* Array items can be single-key objects or full item objects.
|
|
*/
|
|
export const OutputContractsFieldSchema = z.array(
|
|
z.union([
|
|
z.record(z.string(), z.string()), // {Scope: "01-scope.md"} format
|
|
OutputContractItemSchema, // {name, order?, format?} format
|
|
])
|
|
).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(),
|
|
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(),
|
|
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<PieceCategoryConfigNode> = 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(),
|
|
});
|
|
|
|
/** Project config schema */
|
|
export const ProjectConfigSchema = z.object({
|
|
piece: z.string().optional(),
|
|
agents: z.array(CustomAgentConfigSchema).optional(),
|
|
provider: z.enum(['claude', 'codex', 'mock']).optional(),
|
|
});
|