fix: ピース再利用確認を task.data.piece から取得 & config テンプレート拡充
- retryFailedTask / instructBranch でピース名の取得元を runInfo?.piece から task.data?.piece に変更 (worktree 内に .takt/runs/ が存在しないため runInfo は常に null だった) - ~/.takt/config.yaml テンプレートに不足していた設定項目を追加 (provider, model, concurrency, analytics, pipeline, persona_providers 等)
This commit is contained in:
parent
a69e9f4fb3
commit
ebbd1a67a9
@ -2,17 +2,36 @@
|
||||
# Location: ~/.takt/config.yaml
|
||||
|
||||
# =====================================
|
||||
# General settings (piece-independent)
|
||||
# General settings
|
||||
# =====================================
|
||||
# Note: this template contains global-only settings for ~/.takt/config.yaml.
|
||||
language: en # UI language: en | ja
|
||||
|
||||
# Default provider and model
|
||||
# provider: claude # Default provider: claude | codex | opencode | cursor | copilot | mock
|
||||
# model: sonnet # Default model (passed directly to provider)
|
||||
|
||||
# Execution control
|
||||
# worktree_dir: ~/takt-worktrees # Base directory for shared clone execution
|
||||
# prevent_sleep: false # Prevent macOS idle sleep while running
|
||||
# auto_fetch: false # Fetch before clone to keep shared clones up-to-date
|
||||
# base_branch: main # Base branch to clone from (default: current branch)
|
||||
# concurrency: 1 # Number of tasks to run concurrently in takt run (1-10)
|
||||
# task_poll_interval_ms: 500 # Polling interval in ms for picking up new tasks (100-5000)
|
||||
|
||||
# PR / branch
|
||||
# auto_pr: false # Auto-create PR after worktree execution
|
||||
# draft_pr: false # Create PR as draft
|
||||
# branch_name_strategy: romaji # Branch name generation: romaji | ai
|
||||
|
||||
# Pipeline execution
|
||||
# pipeline:
|
||||
# default_branch_prefix: "takt/" # Branch prefix for pipeline-created branches
|
||||
# commit_message_template: "{title}" # Commit message template. Variables: {title}, {issue}
|
||||
# pr_body_template: "{report}" # PR body template. Variables: {issue_body}, {report}, {issue}
|
||||
|
||||
# Output / notifications
|
||||
# verbose: false # Shortcut: enable trace/debug and set logging.level=debug
|
||||
# minimal_output: false # Suppress detailed agent output
|
||||
# notification_sound: true # Master switch for sounds
|
||||
# notification_sound_events: # Per-event sound toggle (unset means true)
|
||||
# iteration_limit: true
|
||||
@ -20,12 +39,63 @@ language: en # UI language: en | ja
|
||||
# piece_abort: true
|
||||
# run_complete: true
|
||||
# run_abort: true
|
||||
# verbose: false # Shortcut: enable trace/debug and set logging.level=debug
|
||||
# logging:
|
||||
# level: info # Log level for console and file output
|
||||
# trace: true # Generate human-readable execution trace report (trace.md)
|
||||
# debug: false # Enable debug.log + prompts.jsonl
|
||||
# provider_events: false # Persist provider stream events
|
||||
# provider_events: false # Persist provider stream events
|
||||
# usage_events: false # Persist usage event logs
|
||||
|
||||
# Analytics
|
||||
# analytics:
|
||||
# enabled: true # Enable local analytics collection
|
||||
# events_path: ~/.takt/analytics/events # Custom events directory
|
||||
# retention_days: 30 # Retention period for event files
|
||||
|
||||
# Interactive mode
|
||||
# interactive_preview_movements: 3 # Number of movement previews in interactive mode (0-10)
|
||||
|
||||
# Per-persona provider/model overrides
|
||||
# persona_providers:
|
||||
# coder:
|
||||
# provider: claude
|
||||
# model: opus
|
||||
# reviewer:
|
||||
# provider: codex
|
||||
# model: gpt-5.2-codex
|
||||
|
||||
# Provider-specific options (lowest priority, overridden by piece/movement)
|
||||
# provider_options:
|
||||
# codex:
|
||||
# network_access: true
|
||||
# claude:
|
||||
# sandbox:
|
||||
# allow_unsandboxed_commands: true
|
||||
|
||||
# Provider permission profiles
|
||||
# provider_profiles:
|
||||
# claude:
|
||||
# default_permission_mode: edit
|
||||
# codex:
|
||||
# default_permission_mode: edit
|
||||
|
||||
# Runtime environment preparation
|
||||
# runtime:
|
||||
# prepare: [node, gradle, ./custom-script.sh]
|
||||
|
||||
# Piece-level overrides
|
||||
# piece_overrides:
|
||||
# quality_gates:
|
||||
# - "All tests pass"
|
||||
# quality_gates_edit_only: true
|
||||
# movements:
|
||||
# review:
|
||||
# quality_gates:
|
||||
# - "No security vulnerabilities"
|
||||
# personas:
|
||||
# coder:
|
||||
# quality_gates:
|
||||
# - "Code follows conventions"
|
||||
|
||||
# Credentials (environment variables take priority)
|
||||
# anthropic_api_key: "sk-ant-..." # Claude API key
|
||||
@ -35,7 +105,14 @@ language: en # UI language: en | ja
|
||||
# groq_api_key: "..." # Groq API key
|
||||
# openrouter_api_key: "..." # OpenRouter API key
|
||||
# opencode_api_key: "..." # OpenCode API key
|
||||
# codex_cli_path: "/absolute/path/to/codex" # Absolute path to Codex CLI
|
||||
# cursor_api_key: "..." # Cursor API key
|
||||
|
||||
# CLI paths
|
||||
# codex_cli_path: "/absolute/path/to/codex" # Absolute path to Codex CLI
|
||||
# claude_cli_path: "/absolute/path/to/claude" # Absolute path to Claude Code CLI
|
||||
# cursor_cli_path: "/absolute/path/to/cursor" # Absolute path to cursor-agent CLI
|
||||
# copilot_cli_path: "/absolute/path/to/copilot" # Absolute path to Copilot CLI
|
||||
# copilot_github_token: "ghp_..." # Copilot GitHub token
|
||||
|
||||
# Misc
|
||||
# bookmarks_file: ~/.takt/preferences/bookmarks.yaml # Bookmark file location
|
||||
|
||||
@ -2,17 +2,36 @@
|
||||
# 配置場所: ~/.takt/config.yaml
|
||||
|
||||
# =====================================
|
||||
# 通常設定(ピース非依存)
|
||||
# 通常設定
|
||||
# =====================================
|
||||
# 注意: このテンプレートは global 専用設定(~/.takt/config.yaml)だけを扱う
|
||||
language: ja # 表示言語: ja | en
|
||||
|
||||
# デフォルトプロバイダー・モデル
|
||||
# provider: claude # デフォルトプロバイダー: claude | codex | opencode | cursor | copilot | mock
|
||||
# model: sonnet # デフォルトモデル(プロバイダーにそのまま渡される)
|
||||
|
||||
# 実行制御
|
||||
# worktree_dir: ~/takt-worktrees # 共有clone作成先ディレクトリ
|
||||
# prevent_sleep: false # macOS実行中のスリープ防止(caffeinate)
|
||||
# auto_fetch: false # clone前にfetchして最新化するか
|
||||
# base_branch: main # cloneのベースブランチ(デフォルト: カレントブランチ)
|
||||
# concurrency: 1 # takt run の同時実行タスク数 (1-10)
|
||||
# task_poll_interval_ms: 500 # 新規タスク検出のポーリング間隔(ms, 100-5000)
|
||||
|
||||
# PR / ブランチ
|
||||
# auto_pr: false # worktree実行後にPR自動作成
|
||||
# draft_pr: false # ドラフトPRとして作成
|
||||
# branch_name_strategy: romaji # ブランチ名生成: romaji | ai
|
||||
|
||||
# パイプライン実行
|
||||
# pipeline:
|
||||
# default_branch_prefix: "takt/" # パイプラインで作成するブランチのプレフィックス
|
||||
# commit_message_template: "{title}" # コミットメッセージテンプレート。変数: {title}, {issue}
|
||||
# pr_body_template: "{report}" # PR本文テンプレート。変数: {issue_body}, {report}, {issue}
|
||||
|
||||
# 出力・通知
|
||||
# verbose: false # ショートカット: trace/debug有効化 + logging.level=debug
|
||||
# minimal_output: false # エージェント詳細出力を抑制
|
||||
# notification_sound: true # 通知音全体のON/OFF
|
||||
# notification_sound_events: # イベント別通知音(未指定はtrue扱い)
|
||||
# iteration_limit: true
|
||||
@ -20,12 +39,63 @@ language: ja # 表示言語: ja | en
|
||||
# piece_abort: true
|
||||
# run_complete: true
|
||||
# run_abort: true
|
||||
# verbose: false # ショートカット: trace/debug有効化 + logging.level=debug
|
||||
# logging:
|
||||
# level: info # ログレベル: debug | info | warn | error
|
||||
# trace: true # trace.md 実行レポート生成
|
||||
# debug: false # debug.log + prompts.jsonl を有効化
|
||||
# provider_events: false # providerイベントログを記録
|
||||
# usage_events: false # 使用量イベントログを記録
|
||||
|
||||
# アナリティクス
|
||||
# analytics:
|
||||
# enabled: true # ローカルアナリティクス収集を有効化
|
||||
# events_path: ~/.takt/analytics/events # イベントディレクトリのカスタムパス
|
||||
# retention_days: 30 # イベントファイルの保持期間(日)
|
||||
|
||||
# インタラクティブモード
|
||||
# interactive_preview_movements: 3 # インタラクティブモードでのムーブメントプレビュー数 (0-10)
|
||||
|
||||
# ペルソナ別プロバイダー・モデル指定
|
||||
# persona_providers:
|
||||
# coder:
|
||||
# provider: claude
|
||||
# model: opus
|
||||
# reviewer:
|
||||
# provider: codex
|
||||
# model: gpt-5.2-codex
|
||||
|
||||
# プロバイダー固有オプション(最低優先度、ピース/ムーブメントで上書き可能)
|
||||
# provider_options:
|
||||
# codex:
|
||||
# network_access: true
|
||||
# claude:
|
||||
# sandbox:
|
||||
# allow_unsandboxed_commands: true
|
||||
|
||||
# プロバイダー権限プロファイル
|
||||
# provider_profiles:
|
||||
# claude:
|
||||
# default_permission_mode: edit
|
||||
# codex:
|
||||
# default_permission_mode: edit
|
||||
|
||||
# ランタイム環境の準備
|
||||
# runtime:
|
||||
# prepare: [node, gradle, ./custom-script.sh]
|
||||
|
||||
# ピースレベルのオーバーライド
|
||||
# piece_overrides:
|
||||
# quality_gates:
|
||||
# - "All tests pass"
|
||||
# quality_gates_edit_only: true
|
||||
# movements:
|
||||
# review:
|
||||
# quality_gates:
|
||||
# - "No security vulnerabilities"
|
||||
# personas:
|
||||
# coder:
|
||||
# quality_gates:
|
||||
# - "Code follows conventions"
|
||||
|
||||
# 認証情報(環境変数優先)
|
||||
# anthropic_api_key: "sk-ant-..." # Claude APIキー
|
||||
@ -35,7 +105,14 @@ language: ja # 表示言語: ja | en
|
||||
# groq_api_key: "..." # Groq APIキー
|
||||
# openrouter_api_key: "..." # OpenRouter APIキー
|
||||
# opencode_api_key: "..." # OpenCode APIキー
|
||||
# codex_cli_path: "/absolute/path/to/codex" # Codex CLI絶対パス
|
||||
# cursor_api_key: "..." # Cursor APIキー
|
||||
|
||||
# CLIパス
|
||||
# codex_cli_path: "/absolute/path/to/codex" # Codex CLI絶対パス
|
||||
# claude_cli_path: "/absolute/path/to/claude" # Claude Code CLI絶対パス
|
||||
# cursor_cli_path: "/absolute/path/to/cursor" # cursor-agent CLI絶対パス
|
||||
# copilot_cli_path: "/absolute/path/to/copilot" # Copilot CLI絶対パス
|
||||
# copilot_github_token: "ghp_..." # Copilot GitHubトークン
|
||||
|
||||
# その他
|
||||
# bookmarks_file: ~/.takt/preferences/bookmarks.yaml # ブックマーク保存先
|
||||
|
||||
@ -210,15 +210,7 @@ describe('instructBranch direct execution flow', () => {
|
||||
expect(originalTaskInfo.data.piece).toBe('original-piece');
|
||||
});
|
||||
|
||||
it('should reuse previous piece when confirmed', async () => {
|
||||
mockFindRunForTask.mockReturnValue('run-previous');
|
||||
mockLoadRunSessionContext.mockReturnValue({
|
||||
task: 'done',
|
||||
piece: 'default',
|
||||
status: 'completed',
|
||||
movementLogs: [],
|
||||
reports: [],
|
||||
});
|
||||
it('should reuse previous piece from task data when confirmed', async () => {
|
||||
mockConfirm
|
||||
.mockResolvedValueOnce(true);
|
||||
|
||||
@ -230,7 +222,7 @@ describe('instructBranch direct execution flow', () => {
|
||||
content: 'done',
|
||||
branch: 'takt/done-task',
|
||||
worktreePath: '/project/.takt/worktrees/done-task',
|
||||
data: { task: 'done' },
|
||||
data: { task: 'done', piece: 'default' },
|
||||
});
|
||||
|
||||
expect(mockSelectPiece).not.toHaveBeenCalled();
|
||||
@ -240,14 +232,6 @@ describe('instructBranch direct execution flow', () => {
|
||||
});
|
||||
|
||||
it('should call selectPiece when previous piece reuse is declined', async () => {
|
||||
mockFindRunForTask.mockReturnValue('run-previous');
|
||||
mockLoadRunSessionContext.mockReturnValue({
|
||||
task: 'done',
|
||||
piece: 'default',
|
||||
status: 'completed',
|
||||
movementLogs: [],
|
||||
reports: [],
|
||||
});
|
||||
mockConfirm
|
||||
.mockResolvedValueOnce(false);
|
||||
mockSelectPiece.mockResolvedValue('selected-piece');
|
||||
@ -260,22 +244,14 @@ describe('instructBranch direct execution flow', () => {
|
||||
content: 'done',
|
||||
branch: 'takt/done-task',
|
||||
worktreePath: '/project/.takt/worktrees/done-task',
|
||||
data: { task: 'done' },
|
||||
data: { task: 'done', piece: 'default' },
|
||||
});
|
||||
|
||||
expect(mockSelectPiece).toHaveBeenCalledWith('/project');
|
||||
expect(mockStartReExecution).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should ignore previous piece when run metadata contains piece path', async () => {
|
||||
mockFindRunForTask.mockReturnValue('run-previous');
|
||||
mockLoadRunSessionContext.mockReturnValue({
|
||||
task: 'done',
|
||||
piece: '../secrets.yaml',
|
||||
status: 'completed',
|
||||
movementLogs: [],
|
||||
reports: [],
|
||||
});
|
||||
it('should skip reuse prompt when task data has no piece', async () => {
|
||||
mockSelectPiece.mockResolvedValue('selected-piece');
|
||||
|
||||
await instructBranch('/project', {
|
||||
@ -291,18 +267,9 @@ describe('instructBranch direct execution flow', () => {
|
||||
|
||||
expect(mockConfirm).not.toHaveBeenCalled();
|
||||
expect(mockSelectPiece).toHaveBeenCalledWith('/project');
|
||||
expect(mockStartReExecution).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return false when replacement piece selection is cancelled after declining reuse', async () => {
|
||||
mockFindRunForTask.mockReturnValue('run-previous');
|
||||
mockLoadRunSessionContext.mockReturnValue({
|
||||
task: 'done',
|
||||
piece: 'default',
|
||||
status: 'completed',
|
||||
movementLogs: [],
|
||||
reports: [],
|
||||
});
|
||||
mockConfirm.mockResolvedValueOnce(false);
|
||||
mockSelectPiece.mockResolvedValue(null);
|
||||
|
||||
@ -314,7 +281,7 @@ describe('instructBranch direct execution flow', () => {
|
||||
content: 'done',
|
||||
branch: 'takt/done-task',
|
||||
worktreePath: '/project/.takt/worktrees/done-task',
|
||||
data: { task: 'done' },
|
||||
data: { task: 'done', piece: 'default' },
|
||||
});
|
||||
|
||||
expect(result).toBe(false);
|
||||
|
||||
@ -184,11 +184,12 @@ beforeEach(() => {
|
||||
describe('retryFailedTask', () => {
|
||||
it('should run retry mode in existing worktree and execute directly', async () => {
|
||||
const task = makeFailedTask();
|
||||
mockConfirm.mockResolvedValue(true);
|
||||
|
||||
const result = await retryFailedTask(task, '/project');
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(mockSelectPiece).toHaveBeenCalledWith('/project');
|
||||
expect(mockSelectPiece).not.toHaveBeenCalled();
|
||||
expect(mockRunRetryMode).toHaveBeenCalledWith(
|
||||
'/project/.takt/worktrees/my-task',
|
||||
expect.objectContaining({
|
||||
@ -201,6 +202,7 @@ describe('retryFailedTask', () => {
|
||||
});
|
||||
|
||||
it('should execute with selected piece without mutating taskInfo', async () => {
|
||||
mockConfirm.mockResolvedValue(false);
|
||||
mockSelectPiece.mockResolvedValue('selected-piece');
|
||||
const originalTaskInfo = {
|
||||
name: 'my-task',
|
||||
@ -319,6 +321,7 @@ describe('retryFailedTask', () => {
|
||||
|
||||
it('should return false when piece selection is cancelled', async () => {
|
||||
const task = makeFailedTask();
|
||||
mockConfirm.mockResolvedValue(false);
|
||||
mockSelectPiece.mockResolvedValue(null);
|
||||
|
||||
const result = await retryFailedTask(task, '/project');
|
||||
@ -358,11 +361,7 @@ describe('retryFailedTask', () => {
|
||||
expect(mockRequeueTask).toHaveBeenCalledWith('my-task', ['failed'], undefined, '既存ノート\n\n追加指示A');
|
||||
});
|
||||
|
||||
describe('when previous piece exists', () => {
|
||||
beforeEach(() => {
|
||||
mockFindRunForTask.mockReturnValue('run-123');
|
||||
});
|
||||
|
||||
describe('when previous piece exists in task data', () => {
|
||||
it('should ask whether to reuse previous piece with default yes', async () => {
|
||||
const task = makeFailedTask();
|
||||
|
||||
@ -403,21 +402,13 @@ describe('retryFailedTask', () => {
|
||||
expect(mockLoadPieceByIdentifier).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should ignore previous piece when run metadata contains piece path', async () => {
|
||||
const task = makeFailedTask();
|
||||
mockLoadRunSessionContext.mockReturnValue({
|
||||
task: 'Do something',
|
||||
piece: '../secrets.yaml',
|
||||
status: 'failed',
|
||||
movementLogs: [],
|
||||
reports: [],
|
||||
});
|
||||
it('should skip reuse prompt when task data has no piece', async () => {
|
||||
const task = makeFailedTask({ data: { task: 'Do something' } });
|
||||
|
||||
await retryFailedTask(task, '/project');
|
||||
|
||||
expect(mockConfirm).not.toHaveBeenCalled();
|
||||
expect(mockSelectPiece).toHaveBeenCalledWith('/project');
|
||||
expect(mockLoadPieceByIdentifier).toHaveBeenCalledWith('default', '/project');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -99,7 +99,7 @@ export async function instructBranch(
|
||||
const previousRunContext = matchedSlug
|
||||
? loadRunSessionContext(worktreePath, matchedSlug)
|
||||
: undefined;
|
||||
const selectedPiece = await selectPieceWithOptionalReuse(projectDir, previousRunContext?.piece, lang);
|
||||
const selectedPiece = await selectPieceWithOptionalReuse(projectDir, target.data?.piece, lang);
|
||||
if (!selectedPiece) {
|
||||
info('Cancelled');
|
||||
return false;
|
||||
|
||||
@ -136,7 +136,7 @@ export async function retryFailedTask(
|
||||
const matchedSlug = findRunForTask(worktreePath, task.content);
|
||||
const runInfo = matchedSlug ? buildRetryRunInfo(worktreePath, matchedSlug) : null;
|
||||
|
||||
const selectedPiece = await selectPieceWithOptionalReuse(projectDir, runInfo?.piece);
|
||||
const selectedPiece = await selectPieceWithOptionalReuse(projectDir, task.data?.piece);
|
||||
if (!selectedPiece) {
|
||||
info('Cancelled');
|
||||
return false;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user