Skip to content

[#838] return-interactive-prompt - #853

Open
nrslib wants to merge 1 commit into
mainfrom
takt/838/return-interactive-prompt
Open

[#838] return-interactive-prompt#853
nrslib wants to merge 1 commit into
mainfrom
takt/838/return-interactive-prompt

Conversation

@nrslib

@nrslib nrslib commented Jun 18, 2026

Copy link
Copy Markdown
Owner

Summary

Problem

When TAKT is used interactively, a workflow/task run exits back to the shell after completion or failure. This forces the user to run takt again for the next operation, even during an interactive session where they are repeatedly adding, running, inspecting, or retrying tasks.

This is inconvenient for manual review/fix workflows where the user wants to keep working in TAKT after each run.

Goal

For normal interactive TAKT usage, return to the interactive prompt after a workflow/task finishes.

The user should be asked whether to continue:

Task completed
Continue? [Y/n]

or:

Task failed
Continue? [Y/n]

If the user chooses yes, TAKT should return to the initial interactive prompt/menu. If the user chooses no, TAKT exits as it does today.

Scope

Apply this only to interactive usage, such as invoking takt without a non-interactive command path and entering the existing interactive flow.

Do not change behavior for scripted or pipeline execution.

Non-goals

  • Do not add a new takt interactive subcommand for this issue.
  • Do not implement a command REPL or slash-command shell.
  • Do not add AI command routing.
  • Do not change pipeline execution semantics.

Expected behavior

In interactive mode:

  1. User starts TAKT interactively.
  2. User selects or enters an operation that runs a task/workflow.
  3. The workflow completes, fails, aborts, or exceeds limits.
  4. TAKT prints the result summary as today.
  5. TAKT asks whether to continue.
  6. If yes, TAKT returns to the initial interactive prompt/menu.
  7. If no, TAKT exits normally.

Modes that must not prompt

Do not ask Continue? in non-interactive or automation-oriented paths, including:

  • --pipeline
  • direct one-shot commands such as takt add --pr ...
  • direct run/resume commands intended for scripts
  • quiet mode
  • CI/non-TTY execution
  • any mode where prompting would block automation

Failure handling

The continue prompt should be shown for terminal workflow results such as:

  • completed
  • failed
  • aborted
  • exceeded

The prompt should not hide the failure. The previous run result must remain visible before the continue question.

Ctrl-C / EOF

  • Ctrl-C should exit the interactive loop cleanly.
  • Ctrl-D / EOF should exit cleanly.
  • If a workflow is currently running, preserve the existing interrupt behavior unless this issue explicitly needs a small adaptation.

Implementation notes

Prefer wrapping the existing interactive flow in a loop rather than introducing a new command mode.

The loop should be guarded by an explicit interactive-mode check so that non-interactive code paths cannot accidentally prompt.

Avoid calling process.exit() deep inside the interactive flow if that prevents returning to the top prompt. If needed, refactor those exits into returned status values only for the interactive path.

Acceptance criteria

  • Running TAKT in normal interactive mode returns to the initial prompt after a task/workflow completes when the user answers yes.
  • The same happens after a failed/aborted/exceeded run.
  • Answering no exits normally.
  • --pipeline and direct scripted commands do not prompt.
  • Quiet/non-TTY execution does not prompt.
  • Existing one-shot CLI behavior remains unchanged.
  • Tests cover both continue and exit paths for interactive mode, plus at least one non-interactive path that must not prompt.

Execution Report

Workflow takt-default completed successfully.

Closes #838

Summary by CodeRabbit

リリースノート

  • New Features

    • インタラクティブモードでタスク実行完了後に「Continue? [Y/n]」プロンプトを表示するようになりました。ユーザーは続行または終了を選択できます。このプロンプトはパイプライン実行、自動化経路、非TTY環境では表示されません。
  • Documentation

    • CLI リファレンスを更新し、インタラクティブ継続プロンプトの仕様と表示条件を明記しました。

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

ウォークスルー

タスク完了後にインタラクティブモードで Continue? [Y/n] を表示して初期プロンプトへ戻る機能を実装。selectAndExecuteTask の戻り値を構造化結果型に変更し、新規の continuePrompt モジュールと interactiveLoop モジュールを追加。routing.ts の大きなインライン実装を runInteractiveLoop へ委譲するよう整理した。

変更内容

インタラクティブ継続プロンプト機能

レイヤー / ファイル 概要
結果型・オプション型の追加
src/features/tasks/execute/types.ts, src/features/tasks/index.ts
SelectAndExecuteTaskStatusSelectAndExecuteTaskResultexitOnFailure オプション、InteractiveContinuePromptOptions を新規追加し、公開型としてエクスポート。
continuePrompt モジュールの実装と公開
src/features/interactive/continuePrompt.ts, src/features/interactive/index.ts, src/shared/i18n/labels_en.yaml, src/shared/i18n/labels_ja.yaml
shouldPromptForInteractiveContinue(quiet/CI/非TTY 判定)と promptContinueAfterTaskResult(Y/n 入力処理)を新規実装。Task completed / Task failed / Continue? の i18n ラベルを en/ja に追加し、interactive/index.ts から再エクスポート。
selectAndExecuteTask の構造化結果返却
src/features/tasks/execute/selectAndExecute.ts
戻り値を void から SelectAndExecuteTaskResult に変更。executeTask から executeTaskWithResult に切り替え、exceeded/failed/interrupted のステータスマッピングと exitOnFailure による process.exit 制御を実装。
interactiveLoop の新規実装
src/app/cli/interactiveLoop.ts
runInteractiveLoop を新規追加。ワークフロー決定・モード選択・dispatchConversationAction(execute/create_issue/save_task/cancel)・継続プロンプト判定によるループ継続または終了を実装。
routing.ts の委譲リファクタリング
src/app/cli/routing.ts
executeDefaultAction のインタラクティブ処理インライン実装(167行)を削除し、runInteractiveLoop への委譲呼び出しに置き換え。不要な import を除去。
continuePrompt・selectAndExecute のユニットテスト
src/__tests__/interactiveContinuePrompt.test.ts, src/__tests__/selectAndExecute-autoPr.test.ts, src/__tests__/selectAndExecute-skipTaskList.test.ts
shouldPromptForInteractiveContinuepromptContinueAfterTaskResult の全条件分岐テストを新規追加。selectAndExecute テストを executeTaskWithResult 対応に更新し、exitOnFailure: false のテストケースを追加。
ルーティング統合テストの拡張
src/__tests__/cli-routing-issue-resolve.test.ts, src/__tests__/cli-routing-pr-resolve.test.ts
interactive continue prompt describe ブロックを新設し、全タスク結果種別×継続選択/拒否のシナリオを網羅。パイプラインモード時に継続プロンプト関数が呼ばれないことを検証。
i18n テスト・isTTY 修正
src/__tests__/i18n.test.ts, src/__tests__/ask-user-question-handler.test.ts
interactive.taskResult.* キーの完全性検証を追加。Object.definePropertyconfigurable: true を追加して isTTY 再定義の信頼性を向上。
CLI リファレンスドキュメントの更新
docs/cli-reference.md, docs/cli-reference.ja.md
Continue? [Y/n] の挙動・表示条件・実行例を en/ja 両方のドキュメントに追記。

シーケンス図

sequenceDiagram
    actor User
    participant runInteractiveLoop
    participant selectInteractiveMode
    participant selectAndExecuteTask
    participant shouldPromptForInteractiveContinue
    participant promptContinueAfterTaskResult

    rect rgba(100, 149, 237, 0.5)
        Note over runInteractiveLoop: インタラクティブループ開始
        User->>runInteractiveLoop: takt 起動(通常インタラクティブ実行)
        loop ループ継続中
            runInteractiveLoop->>selectInteractiveMode: モード選択
            selectInteractiveMode-->>runInteractiveLoop: selectedMode
            runInteractiveLoop->>selectAndExecuteTask: タスク実行(exitOnFailure: false)
            selectAndExecuteTask-->>runInteractiveLoop: SelectAndExecuteTaskResult
            runInteractiveLoop->>shouldPromptForInteractiveContinue: 継続プロンプト表示可否判定
            shouldPromptForInteractiveContinue-->>runInteractiveLoop: true / false
            alt 表示する場合
                runInteractiveLoop->>promptContinueAfterTaskResult: Continue? [Y/n] を表示
                promptContinueAfterTaskResult-->>User: Task completed/failed\nContinue? [Y/n]
                User-->>promptContinueAfterTaskResult: y または n
                promptContinueAfterTaskResult-->>runInteractiveLoop: true / false
            end
            alt false(継続しない)
                runInteractiveLoop->>runInteractiveLoop: process.exit(1) または正常終了
            end
        end
    end
Loading

推定コードレビュー工数

🎯 4 (Complex) | ⏱️ ~60 minutes

関連する可能性のあるPR

  • nrslib/takt#829: buildTraceTaskMetadata / traceTaskMetadata のプロパゲーションなど、selectAndExecute 周辺のタスク実行メタデータ処理と実装領域が重なる。
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed タイトル「[#838] return-interactive-prompt」は、関連するissue #838を明示し、変更の主な目的である「インタラクティブプロンプトへの返却機能」を簡潔に表現しており、変更セット全体をよく表している。
Linked Issues check ✅ Passed PR全体がissue #838の要件を満たしており、インタラクティブプロンプトへの返却機能が実装されている。ドキュメント更新、継続プロンプト判定ロジック、各種テスト、UI文言の多言語対応、ループ処理の実装などすべての要件がカバーされている。
Out of Scope Changes check ✅ Passed すべての変更がissue #838の要件に直結しており、スコープ外の変更は存在しない。ドキュメント、テスト、実装ファイル、i18n対応すべてが要件に関連している。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch takt/838/return-interactive-prompt

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/cli-reference.ja.md`:
- Around line 99-107: The Japanese CLI reference documentation
(docs/cli-reference.ja.md) shows an English continuation prompt "Continue?
[Y/n]" in the execution example output, but the actual implementation uses
getLabel to return the Japanese text "続行しますか? [Y/n]" to Japanese users. Replace
the English prompt text with the Japanese equivalent "続行しますか? [Y/n]" in the
execution example to align the documentation with the actual localized output
that users will see.

In `@src/__tests__/cli-routing-issue-resolve.test.ts`:
- Around line 923-936: The test case uses exitSpy.mockRestore() at the end
without a guaranteed cleanup mechanism, which means if the test fails before
reaching that line, the spy won't be restored and will leak side effects to
subsequent tests. Wrap the test body in a try/finally block to ensure
mockRestore() is always called regardless of test success or failure, or
alternatively move the spy creation and cleanup to shared beforeEach/afterEach
hooks. Apply this same pattern consistently to all affected test cases including
those at lines 938-951 and 1046-1059.

In `@src/__tests__/cli-routing-pr-resolve.test.ts`:
- Around line 595-616: The test modifies the pipelineMode property at the
beginning but only restores it at the end without error handling. If the test
execution fails before reaching the restoration code, pipelineMode will remain
modified and break subsequent tests. Wrap the test execution logic and the
pipelineMode restoration in a try-finally block to ensure the original
pipelineMode value is always restored to the originalPipelineMode variable
regardless of whether the test passes or fails during the executeDefaultAction
call and assertions.

In `@src/__tests__/interactiveContinuePrompt.test.ts`:
- Around line 119-127: The test for promptContinueAfterTaskResult with false
input only verifies that mockInfo and mockReadMultilineInput were called, but
does not verify the order in which they were called. To ensure the user-facing
contract is met (failure message displays before the continue prompt), add an
assertion to verify that mockInfo is called before mockReadMultilineInput. Use
Jest's toHaveBeenCalledBefore matcher or check the call order by examining the
call indices of both mocks to confirm the failure status message appears before
the continue prompt question.

In `@src/__tests__/selectAndExecute-autoPr.test.ts`:
- Around line 200-215: The process.exit spy restoration in the test around the
selectAndExecuteTask function call is not guaranteed to execute if assertions
fail, potentially causing state to leak to subsequent tests. Move the
processExitSpy.mockRestore() call into a finally block that wraps the test logic
(the selectAndExecuteTask call and all expect assertions) to ensure cleanup
happens regardless of test success or failure.

In `@src/app/cli/interactiveLoop.ts`:
- Around line 269-271: When the `--continue` option is specified and the
provider is not configured, the error thrown in the providerType check will
propagate unhandled and crash the process with a stack trace. Evaluate whether
this error should be caught at a higher level in the call stack and handled
gracefully with a user-friendly error message and appropriate exit code before
the process terminates, rather than allowing the raw error to propagate. If
intentional as a hard fail, add a comment documenting this is by design;
otherwise, add proper error handling around the provider validation check to
intercept this case and exit cleanly with a clear message.

In `@src/features/tasks/execute/selectAndExecute.ts`:
- Around line 47-53: The condition in the return statement at line 47
incorrectly requires both result.exceeded and result.exceededInfo to be truthy,
which causes cases where exceeded is true but exceededInfo is missing to be
misclassified as failed status. Change the condition to check only
result.exceeded for determining the exceeded status, then conditionally include
exceededInfo in the return object only when it exists, similar to how the reason
property is conditionally added using the spread operator.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ce121b0d-d94e-4ab2-9226-c24daa0b2084

📥 Commits

Reviewing files that changed from the base of the PR and between c30811d and 7c7f07a.

📒 Files selected for processing (18)
  • docs/cli-reference.ja.md
  • docs/cli-reference.md
  • src/__tests__/ask-user-question-handler.test.ts
  • src/__tests__/cli-routing-issue-resolve.test.ts
  • src/__tests__/cli-routing-pr-resolve.test.ts
  • src/__tests__/i18n.test.ts
  • src/__tests__/interactiveContinuePrompt.test.ts
  • src/__tests__/selectAndExecute-autoPr.test.ts
  • src/__tests__/selectAndExecute-skipTaskList.test.ts
  • src/app/cli/interactiveLoop.ts
  • src/app/cli/routing.ts
  • src/features/interactive/continuePrompt.ts
  • src/features/interactive/index.ts
  • src/features/tasks/execute/selectAndExecute.ts
  • src/features/tasks/execute/types.ts
  • src/features/tasks/index.ts
  • src/shared/i18n/labels_en.yaml
  • src/shared/i18n/labels_ja.yaml

Comment thread docs/cli-reference.ja.md
Comment on lines +99 to 107
Task completed
Continue? [Y/n] y

Select workflow:
> default (current)
Development/
Research/
Cancel
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

TAKT プロジェクトの i18n ラベル定義(特に interactive.taskResult.continuePrompt の日本語版)で実際に使用されるプロンプトテキストは何か

💡 Result:

TAKT プロジェクトの i18n ラベル定義において、interactive.taskResult.continuePrompt(実際には labels_ja.yaml 内で interactive.continuePrompt として定義)に対応する日本語のプロンプトテキストは以下の通りです。 続けてタスク内容を入力してください。 このテキストは、TAKT プロジェクトのリポジトリ内にある src/shared/i18n/labels_ja.yaml ファイルにて定義されています [1]。この設定値は、インタラクティブモードでの会話ループにおいて、ユーザーがタスクの要約後に会話を続けることを選択した際に表示されるメッセージとして使用されます [2][3]。 トップ結果: [1][2][3]

Citations:


🏁 Script executed:

# Find and read the Japanese i18n labels file to get the actual prompt text
fd -t f "labels_ja.yaml" | head -5

Repository: nrslib/takt

Length of output: 86


🏁 Script executed:

# Once we find the file, let's check the interactive.continuePrompt or interactive.taskResult.continuePrompt label
rg -A 2 -B 2 "interactive.*continuePrompt|interactive\.taskResult" --type yaml

Repository: nrslib/takt

Length of output: 37


🏁 Script executed:

# Also check the current documentation to confirm the English text
sed -n '99,107p' docs/cli-reference.ja.md

Repository: nrslib/takt

Length of output: 176


🏁 Script executed:

# Read the labels_ja.yaml file to find the actual Japanese prompt text
cat src/shared/i18n/labels_ja.yaml | head -100

Repository: nrslib/takt

Length of output: 3736


🏁 Script executed:

# Search for keywords related to continue or prompt in the file
rg "continue|Continue|継続|プロンプト" src/shared/i18n/labels_ja.yaml -i

Repository: nrslib/takt

Length of output: 423


🏁 Script executed:

# Verify which label key is actually used in the implementation for task result continue prompt
rg -B 5 -A 5 "taskResult.*continuePrompt|interactive.*continuePrompt" src/features --type ts

Repository: nrslib/takt

Length of output: 1036


🏁 Script executed:

# Check the specific implementation of the continue prompt display in interactive mode
rg -B 3 -A 3 "getLabel.*continue|continuePrompt" src/features/interactive --type ts

Repository: nrslib/takt

Length of output: 2580


日本語版実行例に表示される継続プロンプトを日本語に統一してください。

実装では getLabel('interactive.taskResult.continuePrompt', lang) で日本語テキスト「続行しますか?」を取得しており、実際の日本語ユーザーには「続行しますか? [Y/n]」と表示されます。しかし、docs/cli-reference.ja.md(行99-107)の実行例には英語テキスト「Continue? [Y/n]」が記載されており、実装と見本が乖離しています。

ドキュメント例を以下のように修正してください:

タスク完了
続行しますか? [Y/n] y
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/cli-reference.ja.md` around lines 99 - 107, The Japanese CLI reference
documentation (docs/cli-reference.ja.md) shows an English continuation prompt
"Continue? [Y/n]" in the execution example output, but the actual implementation
uses getLabel to return the Japanese text "続行しますか? [Y/n]" to Japanese users.
Replace the English prompt text with the Japanese equivalent "続行しますか? [Y/n]" in
the execution example to align the documentation with the actual localized
output that users will see.

Comment on lines +923 to +936
it('should exit normally after a successful run when the user declines to continue', async () => {
const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => {
throw new Error('process.exit called');
});
mockShouldPromptForInteractiveContinue.mockReturnValue(true);
mockPromptContinueAfterTaskResult.mockResolvedValue(false);

await executeDefaultAction();

expect(mockPromptContinueAfterTaskResult).toHaveBeenCalledWith(true, 'en');
expect(exitSpy).not.toHaveBeenCalled();
expect(mockInteractiveMode).toHaveBeenCalledTimes(1);
exitSpy.mockRestore();
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

process.exit モックの復元を全ケースで finally に統一してください。

これらのテストは mockRestore() が末尾直書きのため、途中失敗時に復元されず後続ケースへ副作用が波及します。try/finally(または共通 afterEach)で確実に復元してください。

Also applies to: 938-951, 1046-1059

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/__tests__/cli-routing-issue-resolve.test.ts` around lines 923 - 936, The
test case uses exitSpy.mockRestore() at the end without a guaranteed cleanup
mechanism, which means if the test fails before reaching that line, the spy
won't be restored and will leak side effects to subsequent tests. Wrap the test
body in a try/finally block to ensure mockRestore() is always called regardless
of test success or failure, or alternatively move the spy creation and cleanup
to shared beforeEach/afterEach hooks. Apply this same pattern consistently to
all affected test cases including those at lines 938-951 and 1046-1059.

Comment on lines +595 to +616
it('should not call interactive continue prompt helpers in pipeline mode', async () => {
const programModule = await import('../app/cli/program.js');
const originalPipelineMode = programModule.pipelineMode;
Object.defineProperty(programModule, 'pipelineMode', { value: true, writable: true });

mockOpts.workflow = 'default';
mockShouldPromptForInteractiveContinue.mockReturnValue(true);
mockExecutePipeline.mockResolvedValue(0);

await executeDefaultAction();

expect(mockExecutePipeline).toHaveBeenCalledWith(
expect.objectContaining({
workflow: 'default',
}),
);
expect(mockSelectInteractiveMode).not.toHaveBeenCalled();
expect(mockShouldPromptForInteractiveContinue).not.toHaveBeenCalled();
expect(mockPromptContinueAfterTaskResult).not.toHaveBeenCalled();

Object.defineProperty(programModule, 'pipelineMode', { value: originalPipelineMode, writable: true });
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

pipelineMode の一時上書きは finally で必ず復元してください。

現状はテスト途中で失敗すると pipelineMode が復元されず、後続テストの分岐条件を壊す可能性があります。

✅ 提案差分
     it('should not call interactive continue prompt helpers in pipeline mode', async () => {
       const programModule = await import('../app/cli/program.js');
       const originalPipelineMode = programModule.pipelineMode;
       Object.defineProperty(programModule, 'pipelineMode', { value: true, writable: true });

-      mockOpts.workflow = 'default';
-      mockShouldPromptForInteractiveContinue.mockReturnValue(true);
-      mockExecutePipeline.mockResolvedValue(0);
-
-      await executeDefaultAction();
-
-      expect(mockExecutePipeline).toHaveBeenCalledWith(
-        expect.objectContaining({
-          workflow: 'default',
-        }),
-      );
-      expect(mockSelectInteractiveMode).not.toHaveBeenCalled();
-      expect(mockShouldPromptForInteractiveContinue).not.toHaveBeenCalled();
-      expect(mockPromptContinueAfterTaskResult).not.toHaveBeenCalled();
-
-      Object.defineProperty(programModule, 'pipelineMode', { value: originalPipelineMode, writable: true });
+      try {
+        mockOpts.workflow = 'default';
+        mockShouldPromptForInteractiveContinue.mockReturnValue(true);
+        mockExecutePipeline.mockResolvedValue(0);
+
+        await executeDefaultAction();
+
+        expect(mockExecutePipeline).toHaveBeenCalledWith(
+          expect.objectContaining({
+            workflow: 'default',
+          }),
+        );
+        expect(mockSelectInteractiveMode).not.toHaveBeenCalled();
+        expect(mockShouldPromptForInteractiveContinue).not.toHaveBeenCalled();
+        expect(mockPromptContinueAfterTaskResult).not.toHaveBeenCalled();
+      } finally {
+        Object.defineProperty(programModule, 'pipelineMode', { value: originalPipelineMode, writable: true });
+      }
     });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('should not call interactive continue prompt helpers in pipeline mode', async () => {
const programModule = await import('../app/cli/program.js');
const originalPipelineMode = programModule.pipelineMode;
Object.defineProperty(programModule, 'pipelineMode', { value: true, writable: true });
mockOpts.workflow = 'default';
mockShouldPromptForInteractiveContinue.mockReturnValue(true);
mockExecutePipeline.mockResolvedValue(0);
await executeDefaultAction();
expect(mockExecutePipeline).toHaveBeenCalledWith(
expect.objectContaining({
workflow: 'default',
}),
);
expect(mockSelectInteractiveMode).not.toHaveBeenCalled();
expect(mockShouldPromptForInteractiveContinue).not.toHaveBeenCalled();
expect(mockPromptContinueAfterTaskResult).not.toHaveBeenCalled();
Object.defineProperty(programModule, 'pipelineMode', { value: originalPipelineMode, writable: true });
});
it('should not call interactive continue prompt helpers in pipeline mode', async () => {
const programModule = await import('../app/cli/program.js');
const originalPipelineMode = programModule.pipelineMode;
Object.defineProperty(programModule, 'pipelineMode', { value: true, writable: true });
try {
mockOpts.workflow = 'default';
mockShouldPromptForInteractiveContinue.mockReturnValue(true);
mockExecutePipeline.mockResolvedValue(0);
await executeDefaultAction();
expect(mockExecutePipeline).toHaveBeenCalledWith(
expect.objectContaining({
workflow: 'default',
}),
);
expect(mockSelectInteractiveMode).not.toHaveBeenCalled();
expect(mockShouldPromptForInteractiveContinue).not.toHaveBeenCalled();
expect(mockPromptContinueAfterTaskResult).not.toHaveBeenCalled();
} finally {
Object.defineProperty(programModule, 'pipelineMode', { value: originalPipelineMode, writable: true });
}
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/__tests__/cli-routing-pr-resolve.test.ts` around lines 595 - 616, The
test modifies the pipelineMode property at the beginning but only restores it at
the end without error handling. If the test execution fails before reaching the
restoration code, pipelineMode will remain modified and break subsequent tests.
Wrap the test execution logic and the pipelineMode restoration in a try-finally
block to ensure the original pipelineMode value is always restored to the
originalPipelineMode variable regardless of whether the test passes or fails
during the executeDefaultAction call and assertions.

Comment on lines +119 to +127
it('prints failed status and returns false for no input', async () => {
mockReadMultilineInput.mockResolvedValue('n');

const result = await promptContinueAfterTaskResult(false, 'en');

expect(result).toBe(false);
expect(mockInfo).toHaveBeenCalledWith('Task failed');
expect(mockReadMultilineInput).toHaveBeenCalledWith('Continue? [Y/n]');
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

失敗時の表示順(失敗メッセージ→Continue質問)を呼び出し順で固定してください。

現在は両方が呼ばれたことしか検証しておらず、順序が逆でも通ってしまいます。ユーザー向け契約を担保するため、呼び出し順のアサーションを追加してください。

✅ 提案差分
   it('prints failed status and returns false for no input', async () => {
     mockReadMultilineInput.mockResolvedValue('n');

     const result = await promptContinueAfterTaskResult(false, 'en');

     expect(result).toBe(false);
     expect(mockInfo).toHaveBeenCalledWith('Task failed');
     expect(mockReadMultilineInput).toHaveBeenCalledWith('Continue? [Y/n]');
+    expect(mockInfo.mock.invocationCallOrder[0]).toBeLessThan(
+      mockReadMultilineInput.mock.invocationCallOrder[0],
+    );
   });

As per coding guidelines, "When judging failures in user-facing contract or prompt ordering (failure info visible before continue question), require explicit test proof—not only logs/snapshots."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/__tests__/interactiveContinuePrompt.test.ts` around lines 119 - 127, The
test for promptContinueAfterTaskResult with false input only verifies that
mockInfo and mockReadMultilineInput were called, but does not verify the order
in which they were called. To ensure the user-facing contract is met (failure
message displays before the continue prompt), add an assertion to verify that
mockInfo is called before mockReadMultilineInput. Use Jest's
toHaveBeenCalledBefore matcher or check the call order by examining the call
indices of both mocks to confirm the failure status message appears before the
continue prompt question.

Source: Coding guidelines

Comment on lines +200 to +215
const processExitSpy = vi.spyOn(process, 'exit').mockImplementation((() => {
throw new Error('process exit');
}) as (code?: string | number | null | undefined) => never);

const result = await selectAndExecuteTask('/project', 'test task', {
workflow: 'default',
exitOnFailure: false,
});

expect(result).toEqual({ success: false, status: 'failed', reason: 'Task failed' });
expect(processExitSpy).not.toHaveBeenCalled();
expect(mockAddTask).toHaveBeenCalledWith('test task', { workflow: 'default' });
expect(mockFailTask).toHaveBeenCalledTimes(1);
expect(mockCompleteTask).not.toHaveBeenCalled();
processExitSpy.mockRestore();
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

process.exit スパイの復元を finally で保証してください。

このテストは失敗時に processExitSpy.mockRestore() まで到達せず、後続テストへ状態が漏れる可能性があります。

✅ 提案差分
-    const result = await selectAndExecuteTask('/project', 'test task', {
-      workflow: 'default',
-      exitOnFailure: false,
-    });
-
-    expect(result).toEqual({ success: false, status: 'failed', reason: 'Task failed' });
-    expect(processExitSpy).not.toHaveBeenCalled();
-    expect(mockAddTask).toHaveBeenCalledWith('test task', { workflow: 'default' });
-    expect(mockFailTask).toHaveBeenCalledTimes(1);
-    expect(mockCompleteTask).not.toHaveBeenCalled();
-    processExitSpy.mockRestore();
+    try {
+      const result = await selectAndExecuteTask('/project', 'test task', {
+        workflow: 'default',
+        exitOnFailure: false,
+      });
+
+      expect(result).toEqual({ success: false, status: 'failed', reason: 'Task failed' });
+      expect(processExitSpy).not.toHaveBeenCalled();
+      expect(mockAddTask).toHaveBeenCalledWith('test task', { workflow: 'default' });
+      expect(mockFailTask).toHaveBeenCalledTimes(1);
+      expect(mockCompleteTask).not.toHaveBeenCalled();
+    } finally {
+      processExitSpy.mockRestore();
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/__tests__/selectAndExecute-autoPr.test.ts` around lines 200 - 215, The
process.exit spy restoration in the test around the selectAndExecuteTask
function call is not guaranteed to execute if assertions fail, potentially
causing state to leak to subsequent tests. Move the processExitSpy.mockRestore()
call into a finally block that wraps the test logic (the selectAndExecuteTask
call and all expect assertions) to ensure cleanup happens regardless of test
success or failure.

Comment on lines +269 to +271
if (!providerType) {
throw new Error('Provider is not configured.');
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | 💤 Low value

--continue オプション使用時のプロバイダー未設定エラーがハンドリングされずに伝播する可能性

--continue が指定されているがプロバイダーが設定されていない場合、ここで投げられたエラーは上位でキャッチされず、スタックトレース付きでプロセスがクラッシュします。設定エラーとしてフェイルファストは妥当ですが、ユーザー向けにはもう少しフレンドリーなエラーメッセージと終了コードが望ましい可能性があります。

現状の挙動が意図通りであれば、このままで問題ありません。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/cli/interactiveLoop.ts` around lines 269 - 271, When the `--continue`
option is specified and the provider is not configured, the error thrown in the
providerType check will propagate unhandled and crash the process with a stack
trace. Evaluate whether this error should be caught at a higher level in the
call stack and handled gracefully with a user-friendly error message and
appropriate exit code before the process terminates, rather than allowing the
raw error to propagate. If intentional as a hard fail, add a comment documenting
this is by design; otherwise, add proper error handling around the provider
validation check to intercept this case and exit cleanly with a clear message.

Comment on lines +47 to +53
if (result.exceeded && result.exceededInfo) {
return {
success: false,
status: 'exceeded',
...(result.reason ? { reason: result.reason } : {}),
exceededInfo: result.exceededInfo,
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

exceeded 判定が exceededInfo 依存になっており状態を取りこぼします

Line 47 の条件が result.exceeded && result.exceededInfo になっているため、exceeded: true でも exceededInfo が欠けた入力は status: 'failed' に誤分類されます。statusexceeded を優先判定し、exceededInfo は存在時のみ添付してください。

修正例
-  if (result.exceeded && result.exceededInfo) {
+  if (result.exceeded) {
     return {
       success: false,
       status: 'exceeded',
       ...(result.reason ? { reason: result.reason } : {}),
-      exceededInfo: result.exceededInfo,
+      ...(result.exceededInfo ? { exceededInfo: result.exceededInfo } : {}),
     };
   }

As per coding guidelines, 「契約/UI/CLI behavior changes は edge case を含めて一致させる」「REJECT条件(契約不整合)」に該当しないよう、状態境界の判定を契約どおり維持する必要があります。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/tasks/execute/selectAndExecute.ts` around lines 47 - 53, The
condition in the return statement at line 47 incorrectly requires both
result.exceeded and result.exceededInfo to be truthy, which causes cases where
exceeded is true but exceededInfo is missing to be misclassified as failed
status. Change the condition to check only result.exceeded for determining the
exceeded status, then conditionally include exceededInfo in the return object
only when it exists, similar to how the reason property is conditionally added
using the spread operator.

Source: Coding guidelines

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Return to interactive prompt after workflow completion

1 participant