From ebbd1a67a9b911ea6d814908a873fe2ecd33c2b8 Mon Sep 17 00:00:00 2001 From: nrslib <38722970+nrslib@users.noreply.github.com> Date: Fri, 6 Mar 2026 00:37:54 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=E3=83=94=E3=83=BC=E3=82=B9=E5=86=8D?= =?UTF-8?q?=E5=88=A9=E7=94=A8=E7=A2=BA=E8=AA=8D=E3=82=92=20task.data.piece?= =?UTF-8?q?=20=E3=81=8B=E3=82=89=E5=8F=96=E5=BE=97=20&=20config=20?= =?UTF-8?q?=E3=83=86=E3=83=B3=E3=83=97=E3=83=AC=E3=83=BC=E3=83=88=E6=8B=A1?= =?UTF-8?q?=E5=85=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - retryFailedTask / instructBranch でピース名の取得元を runInfo?.piece から task.data?.piece に変更 (worktree 内に .takt/runs/ が存在しないため runInfo は常に null だった) - ~/.takt/config.yaml テンプレートに不足していた設定項目を追加 (provider, model, concurrency, analytics, pipeline, persona_providers 等) --- builtins/en/config.yaml | 87 +++++++++++++++++-- builtins/ja/config.yaml | 85 +++++++++++++++++- src/__tests__/taskInstructionActions.test.ts | 43 ++------- src/__tests__/taskRetryActions.test.ts | 23 ++--- .../tasks/list/taskInstructionActions.ts | 2 +- src/features/tasks/list/taskRetryActions.ts | 2 +- 6 files changed, 177 insertions(+), 65 deletions(-) diff --git a/builtins/en/config.yaml b/builtins/en/config.yaml index 553d390..ff5e93b 100644 --- a/builtins/en/config.yaml +++ b/builtins/en/config.yaml @@ -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 diff --git a/builtins/ja/config.yaml b/builtins/ja/config.yaml index 5a179bd..b239282 100644 --- a/builtins/ja/config.yaml +++ b/builtins/ja/config.yaml @@ -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 # ブックマーク保存先 diff --git a/src/__tests__/taskInstructionActions.test.ts b/src/__tests__/taskInstructionActions.test.ts index 92047f2..feadd68 100644 --- a/src/__tests__/taskInstructionActions.test.ts +++ b/src/__tests__/taskInstructionActions.test.ts @@ -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); diff --git a/src/__tests__/taskRetryActions.test.ts b/src/__tests__/taskRetryActions.test.ts index 13b5d16..ffb74f7 100644 --- a/src/__tests__/taskRetryActions.test.ts +++ b/src/__tests__/taskRetryActions.test.ts @@ -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'); }); }); }); diff --git a/src/features/tasks/list/taskInstructionActions.ts b/src/features/tasks/list/taskInstructionActions.ts index 3824d02..261e775 100644 --- a/src/features/tasks/list/taskInstructionActions.ts +++ b/src/features/tasks/list/taskInstructionActions.ts @@ -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; diff --git a/src/features/tasks/list/taskRetryActions.ts b/src/features/tasks/list/taskRetryActions.ts index 3ce491b..00cf28a 100644 --- a/src/features/tasks/list/taskRetryActions.ts +++ b/src/features/tasks/list/taskRetryActions.ts @@ -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;