From 2926785c2c4e082dae8da47dd73cc9809ab6ddc2 Mon Sep 17 00:00:00 2001 From: nrslib <38722970+nrslib@users.noreply.github.com> Date: Thu, 19 Feb 2026 23:24:13 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20retry=E3=82=B3=E3=83=9E=E3=83=B3?= =?UTF-8?q?=E3=83=89=E3=81=AE=E6=9C=89=E5=8A=B9=E7=AF=84=E5=9B=B2=E3=81=A8?= =?UTF-8?q?=E6=A1=88=E5=86=85=E6=96=87=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/__tests__/conversationLoop-resume.test.ts | 13 +++++++++++++ src/features/interactive/conversationLoop.ts | 6 ++++++ src/features/interactive/interactive.ts | 1 + src/features/interactive/retryMode.ts | 10 +++++----- src/shared/i18n/labels_en.yaml | 12 +++++++++--- src/shared/i18n/labels_ja.yaml | 12 +++++++++--- 6 files changed, 43 insertions(+), 11 deletions(-) diff --git a/src/__tests__/conversationLoop-resume.test.ts b/src/__tests__/conversationLoop-resume.test.ts index 381d2c7..5d15111 100644 --- a/src/__tests__/conversationLoop-resume.test.ts +++ b/src/__tests__/conversationLoop-resume.test.ts @@ -77,6 +77,8 @@ vi.mock('../shared/i18n/index.js', () => ({ proposed: 'Proposed:', actionPrompt: 'What next?', playNoTask: 'No task for /play', + retryNoOrder: 'No previous order found.', + retryUnavailable: '/retry is not available in this mode.', cancelled: 'Cancelled', actions: { execute: 'Execute', saveTask: 'Save', continue: 'Continue' }, })), @@ -212,4 +214,15 @@ describe('/resume command', () => { expect(capture.sessionIds[0]).toBe('resumed-session-xyz'); expect(result.action).toBe('cancel'); }); + + it('should reject /retry in non-retry mode', async () => { + setupRawStdin(toRawInputs(['/retry', '/cancel'])); + setupProvider([]); + + const ctx = createSessionContext(); + const result = await runConversationLoop('/test', ctx, defaultStrategy, undefined, undefined); + + expect(mockLogInfo).toHaveBeenCalledWith('/retry is not available in this mode.'); + expect(result.action).toBe('cancel'); + }); }); diff --git a/src/features/interactive/conversationLoop.ts b/src/features/interactive/conversationLoop.ts index b3adbd2..488bc4d 100644 --- a/src/features/interactive/conversationLoop.ts +++ b/src/features/interactive/conversationLoop.ts @@ -85,6 +85,8 @@ export interface ConversationStrategy { selectAction?: (task: string, lang: 'en' | 'ja') => Promise; /** Previous order.md content for /replay command (retry/instruct only) */ previousOrderContent?: string; + /** Enable /retry slash command (retry mode only) */ + enableRetryCommand?: boolean; } /** @@ -166,6 +168,10 @@ export async function runConversationLoop( } if (trimmed === '/retry') { + if (!strategy.enableRetryCommand) { + info(ui.retryUnavailable); + continue; + } if (!strategy.previousOrderContent) { info(ui.retryNoOrder); continue; diff --git a/src/features/interactive/interactive.ts b/src/features/interactive/interactive.ts index 398ead3..b1d8b5d 100644 --- a/src/features/interactive/interactive.ts +++ b/src/features/interactive/interactive.ts @@ -46,6 +46,7 @@ export interface InteractiveUIText { cancelled: string; playNoTask: string; retryNoOrder: string; + retryUnavailable: string; } /** diff --git a/src/features/interactive/retryMode.ts b/src/features/interactive/retryMode.ts index 317cc85..b233376 100644 --- a/src/features/interactive/retryMode.ts +++ b/src/features/interactive/retryMode.ts @@ -14,13 +14,12 @@ import { } from './conversationLoop.js'; import { createSelectActionWithoutExecute, - buildReplayHint, formatMovementPreviews, type PieceContext, } from './interactive-summary.js'; import { resolveLanguage } from './interactive.js'; import { loadTemplate } from '../../shared/prompts/index.js'; -import { getLabelObject } from '../../shared/i18n/index.js'; +import { getLabel, getLabelObject } from '../../shared/i18n/index.js'; import { resolveConfigValues } from '../../infra/config/index.js'; import type { InstructModeResult, InstructUIText } from '../tasks/list/instructMode.js'; @@ -120,10 +119,10 @@ export async function runRetryMode( const templateVars = buildRetryTemplateVars(retryContext, lang, previousOrderContent); const systemPrompt = loadTemplate('score_retry_system_prompt', ctx.lang, templateVars); - const replayHint = buildReplayHint(ctx.lang, previousOrderContent !== null); + const retryIntro = getLabel('retry.ui.intro', ctx.lang); const introLabel = ctx.lang === 'ja' - ? `## リトライ: ${retryContext.failure.taskName}\n\nブランチ: ${retryContext.branchName}\n\n${ui.intro}${replayHint}` - : `## Retry: ${retryContext.failure.taskName}\n\nBranch: ${retryContext.branchName}\n\n${ui.intro}${replayHint}`; + ? `## リトライ: ${retryContext.failure.taskName}\n\nブランチ: ${retryContext.branchName}\n\n${retryIntro}` + : `## Retry: ${retryContext.failure.taskName}\n\nBranch: ${retryContext.branchName}\n\n${retryIntro}`; const policyContent = loadTemplate('score_interactive_policy', ctx.lang, {}); @@ -144,6 +143,7 @@ export async function runRetryMode( introMessage: introLabel, selectAction: createSelectActionWithoutExecute(ui), previousOrderContent: previousOrderContent ?? undefined, + enableRetryCommand: true, }; const result = await runConversationLoop(cwd, ctx, strategy, retryContext.pieceContext, undefined); diff --git a/src/shared/i18n/labels_en.yaml b/src/shared/i18n/labels_en.yaml index 31167cc..74bb946 100644 --- a/src/shared/i18n/labels_en.yaml +++ b/src/shared/i18n/labels_en.yaml @@ -10,7 +10,7 @@ interactive: conversationLabel: "Conversation:" noTranscript: "(No local transcript. Summarize the current session context.)" ui: - intro: "Interactive mode - describe your task. Commands: /go (execute), /play (run now), /resume (load session), /retry (rerun previous order), /cancel (exit)" + intro: "Interactive mode - describe your task. Commands: /go (create instruction & run), /play (run now), /resume (load session), /cancel (exit)" resume: "Resuming previous session" noConversation: "No conversation yet. Please describe your task first." summarizeFailed: "Failed to summarize conversation. Please try again." @@ -25,6 +25,7 @@ interactive: cancelled: "Cancelled" playNoTask: "Please specify task content: /play " retryNoOrder: "No previous order (order.md) found. /retry is only available during retry." + retryUnavailable: "/retry is only available in Retry mode from `takt list`." personaFallback: "No persona available for the first movement. Falling back to assistant mode." modeSelection: prompt: "Select interactive mode:" @@ -77,10 +78,10 @@ piece: # ===== Instruct Mode UI (takt list -> instruct) ===== instruct: ui: - intro: "Instruct mode - describe additional instructions. Commands: /go (summarize), /retry (rerun previous order), /cancel (exit)" + intro: "Instruct mode - describe additional instructions. Commands: /go (create instruction & run), /replay (resubmit previous order), /cancel (exit)" resume: "Resuming previous session" noConversation: "No conversation yet. Please describe your instructions first." - summarizeFailed: "Failed to summarize conversation. Please try again." + summarizeFailed: "Failed to create instruction. Please try again." continuePrompt: "Okay, continue describing your instructions." proposed: "Proposed additional instructions:" actionPrompt: "What would you like to do?" @@ -91,6 +92,11 @@ instruct: cancelled: "Cancelled" replayNoOrder: "Previous order (order.md) not found" +# ===== Retry Mode UI (takt list -> retry) ===== +retry: + ui: + intro: "Retry mode - describe additional instructions. Commands: /go (create instruction & run), /retry (rerun previous order), /cancel (exit)" + run: notifyComplete: "Run complete ({total} tasks)" notifyAbort: "Run finished with errors ({failed})" diff --git a/src/shared/i18n/labels_ja.yaml b/src/shared/i18n/labels_ja.yaml index bf0353e..6b27047 100644 --- a/src/shared/i18n/labels_ja.yaml +++ b/src/shared/i18n/labels_ja.yaml @@ -10,7 +10,7 @@ interactive: conversationLabel: "会話:" noTranscript: "(ローカル履歴なし。現在のセッション文脈を要約してください。)" ui: - intro: "対話モード - タスク内容を入力してください。コマンド: /go(実行), /play(即実行), /resume(セッション読込), /retry(前回の指示書で再実行), /cancel(終了)" + intro: "対話モード - タスク内容を入力してください。コマンド: /go(指示書作成・実行), /play(即実行), /resume(セッション読込), /cancel(終了)" resume: "前回のセッションを再開します" noConversation: "まだ会話がありません。まずタスク内容を入力してください。" summarizeFailed: "会話の要約に失敗しました。再度お試しください。" @@ -25,6 +25,7 @@ interactive: cancelled: "キャンセルしました" playNoTask: "タスク内容を指定してください: /play <タスク内容>" retryNoOrder: "前回の指示書(order.md)が見つかりません。/retry はリトライ時のみ使用できます。" + retryUnavailable: "/retry は `takt list` の Retry モードでのみ使用できます。" personaFallback: "先頭ムーブメントにペルソナがありません。アシスタントモードにフォールバックします。" modeSelection: prompt: "対話モードを選択してください:" @@ -77,10 +78,10 @@ piece: # ===== Instruct Mode UI (takt list -> instruct) ===== instruct: ui: - intro: "指示モード - 追加指示を入力してください。コマンド: /go(要約), /retry(前回の指示書で再実行), /cancel(終了)" + intro: "指示モード - 追加指示を入力してください。コマンド: /go(指示書作成・実行), /replay(前回の指示書で再投入), /cancel(終了)" resume: "前回のセッションを再開します" noConversation: "まだ会話がありません。まず追加指示を入力してください。" - summarizeFailed: "会話の要約に失敗しました。再度お試しください。" + summarizeFailed: "指示書の作成に失敗しました。再度お試しください。" continuePrompt: "続けて追加指示を入力してください。" proposed: "提案された追加指示:" actionPrompt: "どうしますか?" @@ -91,6 +92,11 @@ instruct: cancelled: "キャンセルしました" replayNoOrder: "前回の指示書(order.md)が見つかりません" +# ===== Retry Mode UI (takt list -> retry) ===== +retry: + ui: + intro: "リトライモード - 追加指示を入力してください。コマンド: /go(指示書作成・実行), /retry(前回の指示書で再実行), /cancel(終了)" + run: notifyComplete: "run完了 ({total} tasks)" notifyAbort: "runはエラー終了 ({failed})"