testing-tui-runtime について
実際のralph-tuiターミナル画面で変更を検証するための、ランタイムおよびエンドツーエンドテスト用スキルです。分離したテスト環境、フェイクエージェント、操作シナリオに加え、セッション・シグナル・ロックの挙動を確認する手順を提供します。
主な機能
- 01配布用CLIをビルドして実行
- 02一時プロジェクトとタスクストアを分離
- 03AI認証情報不要のフェイクエージェントを使用
- 04TUI操作と監視動作をテスト
- 05シグナル・セッション・ロックファイルを確認
活用シーン
- 01一時停止・再開・終了の変更を検証
- 02監視モードでのタスク検出をテスト
- 03シグナル処理や古いロックを調査
- 04mainのworktreeビルドと挙動を比較
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ subsy/ ralph-tui --skill testing-tui-runtimeインストール後、Claude Code で/testing-tui-runtimeを入力して読み込まれたか確認します。
詳細
ralph-tui TUI のランタイム/E2E テスト
変更内容をユニットテストで確認するのではなく、実際の TUI で検証する必要がある場合(監視モード、エンジンのステータスラベル、一時停止/再開、シャットダウン/終了経路)に使用します。
ビルドと実行
cd /path/to/ralph-tui && bun run build # produces dist/cli.js
動作がリリース版と一致するよう、bun run dev ではなく、ビルド済みの CLI を実行します。
bun /path/to/ralph-tui/dist/cli.js run [flags]
一時トラッカー(リポジトリ本体の .beads には触れないでください)
専用の beads ストアを持つ使い捨てのプロジェクトディレクトリを作成し、bd でシナリオを実行します。
export RALPH_TEST_DIR="$(mktemp -d "${TMPDIR:-/tmp}/ralph-tui-run.XXXXXX")" && cd "$RALPH_TEST_DIR"
bd init # or `bd create ...` which bootstraps the store
bd create "Watch mode epic" --type epic -p 1 # note the epic id bd prints
bd create "Alpha task" --type task -p 2 --parent <epic-id>
実行ごとに新しいディレクトリを作成し、固定のディレクトリは使わないでください。ディレクトリを再利用すると、.beads のタスク、.ralph-tui のセッションファイル、ロック、ガードが引き継がれます。これらは、このテストで確認するシナリオの大半に関係するため、前回の実行で表示された「Recovered stale session」バナーや古いロックに関するプロンプトが、テスト対象のビルドで発生した問題として誤認されます。また、ディレクトリが一意であれば、実行ごとに一意のエピック ID が割り当てられます。この ID を使ってプロセスを指定します(以下のシグナルトラップを参照)。
注意点:
bdコマンドは、一時ディレクトリを作業ディレクトリにして実行する必要があります。そうしないと「no beads database found」と表示されます。--tracker beadsを指定した場合、TUI には--epic <id>が必須です。指定しないと「No epics found」と表示され、エピックの対話的な選択によって自動化が停止します。- AGENTS.md に記載されていても、
br(beads-rust)がインストールされていない場合があります。bdは動作します。
フェイクエージェント(AI 認証情報は不要)
プラグインを ~/.config/ralph-tui/plugins/agents/ に配置します。
echo.ts—echo-agent: local fake response\n<promise>COMPLETE</promise>を即座に返します。slowecho.ts— 同じ応答を返しますが、中断可能な 500 ミリ秒単位で約 25 秒間スリープします。反復の途中での終了/一時停止をテストするには必須です。エージェントが即座に応答すると、中断する時間がありません。
--agent echo/--agent slowecho で選択します。
画面上でタイミングを確認できるようにする
終了までの遅延を録画で証明できるよう、CLI をラップします。
#!/bin/bash
cd "${RALPH_TEST_DIR:?export RALPH_TEST_DIR to the scratch dir first}"
export PATH="$PATH:$HOME/.local/bin"
echo "START $(date +%H:%M:%S.%3N) args: $*"
bun /path/to/ralph-tui/dist/cli.js run "$@"
echo "=== PROCESS EXITED code=$? at $(date +%H:%M:%S.%3N) ==="
exec bash
一般的な呼び出し方:
./runwatch.sh --watch --poll 60 --agent echo --tracker beads --epic <epic-id> --no-setup --force
一時停止/終了の遅延が明確に分かるように --poll は長め(60 秒)にし、自動取得を早く行いたい場合は短め(10 秒)にします。
TUI のキー
s 開始 · p 一時停止/再開 · q → 終了ダイアログ → y · d ダッシュボード · r 更新。
ヘッダーのステータスラベル:Waiting(監視中のアイドル状態)、Selecting、Executing、Paused、Complete。
監視中のアイドル状態では、ダッシュボードに Waiting for new tasks と表示されます。
既知の注意点
- 割り込み(Ctrl+C)経路:#431 の修正を含むビルド(
src/tui/utils/keyboard-shortcuts.ts、RunApp.tsxのonInterruptRequest、およびrun.tsxのexitSignals: ['SIGQUIT','SIGABRT'])では、キーボードの Ctrl+C と外部からのkill -INT <pid>のどちらでも、表示を維持したまま⚠ Interrupt Ralph?ダイアログが 表示されます。n/Escでキャンセルし、yで約2秒後に終了コード 0 で終了して、アクティブな タスクをopenに戻します。約1秒以内に Ctrl+C をもう一度押すと、process.exit(1)で強制終了します (ラッパーのcode=1。端末は再描画されません。exitハンドラーがreleaseLockSyncを呼び出す ビルドでは、強制終了後にロックファイルが残りませんが、古いビルドでは残ります)。 古いビルドでは Ctrl+C は完全に無反応で、kill -INTは表示を消して処理がハングします。変更を疑う前に、 必ず使用中のビルドを確認してください。 - ほとんどの端末では Ctrl+Shift+C と Ctrl+C を区別できません:konsole はどちらにもバイト
0x03を送信します(stty -isig; cat -vで確認できます。Ctrl+Shift+C は^C、Alt+C は^[cと表示されます)。そのため、端末自体がキー入力を処理しない場合、「コピー」ショートカットで 割り込みダイアログが開きます(konsole がキー入力を処理するのは、konsole 上でテキストが選択されている 場合のみです)。Alt+C は安全に区別できます。xclip/xsel/wl-pasteがない環境では、クリップボードの 内容を確認できません(Linux ではsrc/utils/clipboard.tsがこれらをシェルから呼び出します)。 クリップボードへの書き込みは未テストとして記録してください。 - 起動直後のシグナルは送信タイミングに注意が必要で、動作に癖があります:アプリの SIGINT/SIGTERM
ハンドラーは、最初の約0.7秒間は登録されません。次のように実行してください。
( sleep 0.5; kill -INT "$(pgrep -f "cli\.js run.*--epic $EPIC_ID")" ) & ./runwatch.sh ...。$EPIC_IDは今回の実行の epic に置き換えます。シグナルを送る前に、パターンが pid 1つだけに 正確に一致すること(pgrep -cf ...)を確認し、ラッパースクリプトは起動する CLI とは別のプロセスである ことにも注意してください。ラッパーではなく、一致した pid にシグナルを送ってください。pkill -f 'dist/cli.js run'は使わないでください。並行テストや比較対象のmainワークツリービルドを含め、 環境内のすべての ralph 実行に当たり、それらのセッションとタスクの状態を変更してしまいます。 #434 のsrc/session/lock.tsの修正以前の動作では、この時間帯に SIGINT を送ると130で終了しますが、 古いロックが残り、SIGTERM は送信されても無視されます(TUI がマウントされ、そのまま実行が続きます)。registerLockCleanupHandlersが起動時のシグナルを処理するビルドでは(process.listenerCount(signal) > 1によるガードあり)、この時間帯のどの遅延でも、SIGTERM はcode=143、SIGINT はcode=130で終了し、.ralph-tui/ralph.lockは削除されるはずです。acquireLockWithPromptより前(約0.05秒)にシグナルが届いた場合はデフォルトの動作になりますが、 ロックは残りません。複数の遅延時間(0.05/0.2/0.3/0.4/0.5/0.7秒)で必ずテストし、シグナルが 実際に送信されたことを記録してください。また、その都度ls .ralph-tui/ralph.lockと次回起動時の バナーを確認してください。この時間帯にシグナルを短時間で2回送っても、正常終了の130/143が 1つだけ発生するはずで、code=1にはなりません。 Recovered stale sessionとStale lock detectedは別のものです:ロックファイルが正しく削除されても、.ralph-tui/session.jsonのstatus: "running"が維持されることがあります。この場合、次回以降の起動の たびに⚠ Recovered stale session / Session status set to "interrupted" (resumable)と表示されます。 正常なq/y終了(code=0)の後でも同様です。このバナーをロックやシグナルの変更のせいと決めつける前に、jq .status .ralph-tui/session.jsonとsession-meta.jsonを確認してください。セッション永続化に関する 独立した不具合の可能性があります。- TUI モードでは、watch なしのモードで全タスクが完了しても、プロセスは自動終了しません。
終了せずに
Completeのまま待機するため、終了するには自分で終了操作を行います。「作業がなくなると終了する」 ことを確認するには、ヘッドレスモードを使ってください。--no-tui(エイリアスは--headless)では 終了コード 0 で終了し、実行の概要が出力されます。ヘッドレスの watch モードでは[watch] All tasks complete — polling every Nsがログに出力され、実行が続きます。 - 強制終了されたセッションでは、古いロックが残ることがあります(古いビルドの場合。ロック層が起動時の
シグナルを処理し、
exitハンドラーが同期的にロックを解放するようになれば修正されます)。次回起動時、 TUI の開始前に"Remove the stale lock and continue? (Y/n)"と確認されます。まずyと答えてください。 早すぎるタイミングで送信したキー(例:s)は、このプロンプトに入力されてしまいます。これを避けるため、q/yで正常に終了することを推奨します。 - タスクを外部から作成した後、自動取得に失敗したと判断する前に、ポーリング間隔1回分と数秒の余裕を見てください。
- ロックガードファイル(
.ralph-tui/ralph.lock.guard)とlockId:ロック変更を直列化するビルドでは、.ralph-tui/ralph.lockに nonce のlockIdが含まれ、作成/古いロックのクリーンアップ/--force/解放の すべてが、O_EXCLで作成されたガードファイルを経由します。確認に便利なヘルパーとして、ロックのpid/lockId、ガードの有無(および内容)、pgrepで取得した ralph の pid を表示するものを用意し、 各シナリオの前後で状態を記録してください。実行中にガードが存在することはなく、存在するのは変更処理の 間だけのはずです。手動で用意してテストするケースとして、空またはnot-jsonのガード(約250ms後に 不正な形式として回収され、通常どおり実行が始まるはずです)、停止済み pid を指定したガード(回収される はずです)、実行中 pid を指定したガード(維持され、約2秒後にTimed out waiting for the session lock (another ralph-tui process may be starting or exiting)でロック取得に失敗するはずです)があります。このようなビルドでは、終了時に 同期処理の制限時間を超えてガードが保持されていると、ロックを削除せずに残す(後で古いロックとして回収される) ことが想定されます。この場合、ロックが残っていれば合格です。ハング、数秒間の停止、または実行中の プロセスのロックが消えることが不具合です。手動で用意したガードの「実行中 pid」が本当に実行中であることを 必ずkill -0で確認してください。すでに終了した pid では、テストが気付かないうちに停止済み pid の ケースに変わってしまいます。 run --resumeは、単独のresumeが機能する場合でも失敗することがあります:run.tsxはresumeSessionの呼び出し前にセッションロックを取得しますが、resumeSessionもロックを取得するため、 ロックがすでに存在すると処理が拒否されます。中断されたセッションでResuming previous session... / Failed to resume sessionと表示され、終了コード 1 になる一方、同じセッションでralph-tui resumeを実行すると正常に開始することが確認されています。これはmainでも再現するため、 テスト対象のビルドが修正を謳っていない限り、既存の不具合として扱ってください。リグレッションと判断する前に、 必ずgit worktreeで作成したmainのビルドでも確認してください。- ロックファイルがないまま孤立した実行が続いていないか確認してください:一度だけ、TUI に何も描画されない
一方でエンジンは反復処理を続け、
.ralph-tui/ralph.lockも存在しない状態が確認されました。これは、ロックを 解放するものの終了しない、捕捉されていない例外ハンドラーの兆候です。その後の再試行では再現していません。 空白の TUI が発生した場合は、終了させる前にscript -q -c "./runwatch.sh ..." /tmp/run.logで実行をラップし、標準出力/標準エラーを取得してください (パイプとは異なり、TTY が維持されます)。runwatch.shは最後にexec bashを実行するため、scriptでラップした実行では内側のシェルに残ります。スクリプトによるループを続けるにはexitと入力してください。
Devin に必要なシークレット
ありません。偽のエージェントプラグインとローカルの bd ストアにより、外部の認証情報は一切不要です。