-
Notifications
You must be signed in to change notification settings - Fork 0
m7 interactive tui_ja
状態: 計画済み、レビュー済み(実装前レビュー後に改訂)。issue #9と sub-issue #58、#59、#60で追跡。
M4〜M6が確立した安全契約を弱めることなく、既存の修復パイプラインに対話式の
チェックリストを追加する。M7はプレゼンテーション層の変更である: パッケージ
列挙、エイリアス解決、衝突検出、リンク検査、SymlinkService が行う最終
再検査は引き続き権威を持つ。
M7は次の順序でスタックされた、レビュー可能な3つのプルリクエストとして 提供する:
- #58 — 仮想端末機能と端末セッションのライフタイム
- #59 — 修復候補チェックリストとコマンドライン統合
- #60 — 進捗と最終結果サマリー
実装プルリクエストはマージ前にレビューされる。実装セッションの一部として マージしてはならない; マージとissueクローズはメンテナの操作として残る。
マージ済みのM6コード(src/cli/Console.*、src/cli/Dispatch.cpp)に対する
実装前レビューにより、原案どおりに実装した場合にチェックリスト、リサイズ
処理、終了コード契約を誤動作させたであろう欠陥が見つかった。この改訂は
実装開始前にそれらの欠陥を修正する。最も影響の大きい修正: この計画の
元々の「既存の終了コード優先順位は保持される」という記述は誤りだった -
マージ済みの runFix() はすでに interrupted を anyInsufficientPermission
より優先しており、これは文書化された「権限不足は2を返す」という優先順位と
一致しない。M7はCLI自身の優先順位を修正する。下記の「進捗、サマリー、
終了コード」を参照。
- TUIは
fix --tuiによってのみ有効化される。 -
--tuiをscanまたはtest-ruleと組み合わせるのは設定エラーであり、 終了コード3を返す。 -
--tuiは--jsonと--yesと衝突する; どちらの組み合わせも終了コード3 を返す。これらのチェックは、既存の--json-with-fix-without---yesの 衝突チェックの隣、parseArguments()(applyPositionals()の後)に置く。 その手前にある--help/--versionのショートサーキットには触れない。 -
--dry-runは許可される。選択されたエントリは再検査され報告されるが、 ファイルシステムの変更は起きない。 -
--no-colorとNO_COLORはSGR色のみを無効化する。TUIが必要とする カーソルと画面制御シーケンスは無効化しない。これはConsoleの既存の 挙動そのものである(tryEnableVirtualTerminal()はnoColorに関わらず 実行される;--no-colorはcolorEnabled()のみをゲートする) - #58はこの既存の機能を公開するだけであり、変更するわけではない。 - TUIは、対話的なstdinとstdoutに加えて、stdout上での
ENABLE_VIRTUAL_TERMINAL_PROCESSINGの activation成功を要求する。 いずれかの機能が利用できない場合、TUIのエスケープシーケンスは一切 出力されない; 警告がstderrに書き込まれ、fixは既存の行指向のCLI 確認フローを通じて継続する。 - TUIのために変更されたすべてのコンソールモードはRAIIの端末セッションが
所有し、通常の復帰、キャンセル、例外、部分的な初期化失敗で復元される -
復元は最大1回のみ実行される。セッションは
CTRL_CLOSE_EVENT/CTRL_LOGOFF_EVENT/CTRL_SHUTDOWN_EVENT(C++の 巻き戻しだけでなく)でも復元する。これらはデストラクタが通常は実行され ない場合でも配信されるためである(ウィンドウを閉じる、ログオフ、 シャットダウン); ハンドラは復元後常にFALSEを返し、デフォルトのOS 処理が引き続き進行するようにする。フォーカスがチェックリストにある間、 Ctrl+Cはこのコンソール制御ハンドラのパスによって意図的に処理されない - 下記の端末所有権の注記を参照。
stdoutの ENABLE_VIRTUAL_TERMINAL_PROCESSING モードは引き続き
Console が排他的に所有する(コンストラクタが1回有効化し、デストラクタが
1回復元する)。M6から変更なし。#58が導入する新しい端末セッションは3つだけを
所有する: stdinのコンソールモード、代替スクリーンの状態、カーソルの可視性。
stdout上で GetConsoleMode/SetConsoleMode を呼び出してはならない -
呼び出すと「元のモード」として Console がすでに変更した後の値を捕捉して
しまい、復元順序が壊れる。Console は3つの読み取り専用アクセサ
(vtEnabled()、stdinInteractive()、stdoutInteractive())を得る。
これにより端末セッションとディスパッチ層は、機能を再導出も再変更もせずに
問い合わせられる。
- 衝突していない
MissingとBrokenの項目のみが選択可能である。 -
Ok、Mismatch、エイリアス衝突、有効なエイリアスを持たない実行ファイルは 決して選択可能な修復として提示されない。既存の警告と除外の挙動は そのまま残る。 - すべての選択可能な項目は未チェックの状態で始まる。修復には明示的な Spaceキーの選択に続くEnterが必要である。
- 上下でカーソルを移動し、Spaceで現在の項目をトグルし、Enterで現在の選択を 確定する。
- Escape、Q、Ctrl+C は変更前にキャンセルし、選択された修復なしで成功を
返す。ここでのCtrl+Cは
SetConsoleCtrlHandler経由ではなく、キー イベントとして読まれる: チェックリストの入力モードはENABLE_PROCESSED_INPUTをクリアするため、Ctrl+Cは、他のすべての キーを読む同じブロッキングのReadConsoleInputWループによって読まれる 通常のKEY_EVENT_RECORD(dwControlKeyStateにCtrlビットが立ったwVirtualKeyCode == 'C')として到着する - メインスレッドが決して来ない ライン指向読み取りを待ってReadConsoleInputW内でブロックしている間に 自身のスレッドで発火する別のCTRL_C_EVENTとしてではない。 (そのスレッド分離こそが、既存のバッチループのCtrl+Cハンドラ (src/cli/Dispatch.cppのg_ctrlCRequested)が依存している障害 モードそのものである - メインスレッドがチェックの間に作業をしている 非対話的な修復ループには正しいが、キーの読み取りでブロックされている ループには誤りである。)SetConsoleCtrlHandlerは、下記で説明する 修復ループフェーズの周辺で、チェックリストセッションが終了した後にのみ 登録される。 - 選択項目なしでのEnterは成功するno-opである。
- 長いリストはスクロールするビューポートを使う。コンソールのリサイズは
カーソルや選択状態を失うことなくビューポートを再計算する。
- リサイズ検出には、stdinの入力モードに
ENABLE_WINDOW_INPUTを含む 必要がある(これがないとWINDOW_BUFFER_SIZE_EVENTは決して生成 されない)とともに、ENABLE_VIRTUAL_TERMINAL_INPUTを除外する 必要がある(VT入力を有効にするとキー/リサイズ信号がエスケープ シーケンスのストリームに変換され、WINDOW_BUFFER_SIZE_EVENTを 完全に抑制してしまう)。 - マウスドラッグがテキストを選択してレンダラを停止させないよう、
ENABLE_QUICK_EDIT_MODEをクリアしなければならない; そのクリアが 有効になるのはENABLE_EXTENDED_FLAGSも同じSetConsoleMode呼び出しで渡された場合のみである -ENABLE_EXTENDED_FLAGSを省略 すると quick-edit の変更は静かにno-opになる。 -
WINDOW_BUFFER_SIZE_EVENT.dwSizeはスクリーンバッファサイズを 報告するのであって可視ウィンドウサイズではない。ビューポートの 高さ/幅として直接使ってはならない。ビューポートはGetConsoleScreenBufferInfo().srWindow(Bottom - Top + 1行、Right - Left + 1列)から導出する。 - stdin入力モードの完全な契約:
ENABLE_WINDOW_INPUT | ENABLE_EXTENDED_FLAGSを設定;ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_VIRTUAL_TERMINAL_INPUT | ENABLE_QUICK_EDIT_MODE | ENABLE_MOUSE_INPUTをクリア。
- リサイズ検出には、stdinの入力モードに
- パッケージ由来のテキストは、意図的なTUI制御シーケンスと組み合わせる前に
サニタイズされる(
sanitizeForDisplay())。 - 確認だけでは変更を許可しない。選択された各項目は共有の修復バッチ
エグゼキュータ(#60)に渡される。これはCLIが
repairLink()経由で 使うのと同じ最新の変更前検査を実行する。
代替スクリーン制御(ESC[?1049h/l)はstdoutのVT処理が有効な場合にのみ
効果を持つため、初期化は厳密に順序付けられる: stdout VTを有効化(すでに
Console が所有しており vtEnabled() で問い合わせる)→ 代替スクリーンに
入る → カーソルを隠す → 上記のstdin入力モードの変更を適用する。終了処理は
これを正確に逆順にする: stdin入力モードを復元 → カーソルを表示 → 代替
スクリーンを出る。初期化の途中の失敗は、すでに成功したステップのみを
逆順に巻き戻してから機能を利用不可として報告する - 部分的な状態
(例: 代替スクリーンに入ったがstdinモードは未変更)を決して残さない。
選択が確定した後、チェックリストセッションは修復が始まる前に(上記の
終了順序に従って)端末を復元し、共有エグゼキュータが
[current/total] alias: result の形式で永続的な進捗行を書き込む。
最終サマリーは以下を報告する:
- 選択済み、処理済み、残数;
- 作成済みと置換済み;
- ドライランモードでの計画済み操作;
-
declined — 対話的な確認をユーザーが拒否した項目。これはドライランの
plannedoutcomeとは異なる: 実際の(非ドライランの)修復を拒否する ことを、あたかも--dry-runがその計画を生成したかのように報告しては ならない。それは試みられなかった修復を、検査済みで計画済みのものと 誤って表現することになるためである。declinedは終了コードに影響 しない(下記参照)。既存のJSONスキーマ(docs/adr-phase-5.mdADR-0022)は変更されない -declinedはコンソール/TUIサマリーの カテゴリのみであり、スクリプトとの互換性を保つために新しいJSON フィールドにはしない; - スキップされた項目、具体的には最新の
Ok結果(SkippedOk)または 拒否されたミスマッチ(RefusedMismatch); - 失敗した項目; および
- バッチが中断されたかどうか、そして何件の項目が未処理のまま残ったか。
修復ループフェーズ中(チェックリストセッションが終了しバッチが実行中の間、
チェックリスト自身のキー処理ではなく既存の SetConsoleCtrlHandler ベースの
仕組みを使う)のCtrl+Cは、現在の項目を完了させてから次の項目の前に停止する。
残った項目はサマリーでカウントされる。
終了コードの優先順位。この計画の原文から修正され、今はM7以前のCLIパスとは
異なる(TUIと非対話的な fix パスの両方に適用される意図的なCLI全体の
修正であり、新しいTUIコードだけではない):
- 権限不足 → 終了コード2。同じバッチで起きた中断や他の部分失敗より優先 される。
- それ以外で、中断または他の部分失敗 → 終了コード10。残数がゼロの中断
(最後の項目がすでに処理された後にCtrl+Cが観測された場合)は、
終了コードの目的では中断として報告されない - それが妨げられたはずの
ものは何もないため - したがってこの分岐は
remaining > 0を要求する。 中断されたドライランバッチは、何も変更しなかったとしてもやはり10を 返す: 不完全なドライランレポートは、報告における部分的な失敗である。 - それ以外 → 終了コード0。完全な成功、変更前のキャンセル、空の選択、 成功した(完全で中断されていない)ドライランをカバーする。
- stdoutコンソール、stdinコンソール、VT有効の各機能を、色の有効化とは
独立して、既存の
Consoleクラス(新しい並行の機能型ではなく)上の 読み取り専用アクセサとして公開する -vtEnabled()、stdinInteractive()、stdoutInteractive()。Consoleはすでに 内部でVT機能を計算しており(tryEnableVirtualTerminal())ローカル 変数に捨てている; #58はそれを保持する。 - 注入可能な端末操作の縫い目(このコードベースがすでに使う既存の
ConsoleOperations/SymlinkServiceOperationsの縫い目パターンに 合わせる)と、stdin入力モード、代替スクリーン状態、カーソル可視性、 リサイズ/キーイベントのためのRAII端末セッションを追加する - 上記の「端末所有権の境界」で説明したとおりのスコープに厳密に従う。 stdoutのVTは引き続き排他的にConsoleの責務である。 - 成功した activation、リダイレクション(stdinとstdoutを独立して)、
VT activationの失敗、部分的な初期化失敗とその逆順の巻き戻し、
--no-color(VT機能が影響されないことを確認する - これは新しいコード パスではなく既存の挙動の検証である)、正確に1回の復元、登録された close/logoff/shutdownハンドラによってトリガーされる復元をカバーする。
- 純粋でユニットテスト可能な選択状態モデル(Win32依存なし)と、 #58の端末セッションの上に構築された薄いWin32/VTレンダラを追加する。
- 上記のstdin入力モード契約に従って、ナビゲーション、トグル、確認、 キャンセル、リサイズ、スクロールを実装する。
- 既存のインベントリと修復パイプラインに選択を統合する
(
Missing/BrokenにフィルタされたnonCollisionItems)。 - 上記に文書化されたコマンド/オプションの衝突を、
ArgParser.cppに すでにある既存の--json-with-fixの衝突チェックと並べて強制する。 - 現行の行指向のCLIパスを機能フォールバックとして保持し、フォールバック 時にはTUIのエスケープシーケンスを一切出力しない。
- CLIとTUIの両方のパスで使う1つの共有修復バッチエグゼキュータを抽出し、
構造化されたサマリー(
declinedを含むディスポジション別のカウント) を返し、上記の終了コードの決定を1か所で所有する。これにより、CLIの 非対話的なfixパスとTUIパスが互いに乖離できないようにする。 - 修復の決定を重複させることなく、項目ごとの進捗と最終サマリーを報告する。
-
declined、ドライラン、権限失敗、一般的な失敗、残数ありの中断、 残数ゼロの中断を含むすべての結果カテゴリをカバーする。
各実装プルリクエストは以下を満たさなければならない:
-
/W4 /WXでDebug|x64、Release|x64、Debug|ARM64、Release|ARM64をビルドする; - DebugとReleaseのx64 MSTestスイートを実行し、実際の結果を報告する;
- ARM64ホストでテストしない限り、クロスビルドのみとして記述する;
- すべての新しい状態遷移とWin32操作の縫い目に焦点を絞ったユニットテストを 追加する;
- 対話的なキー(キーとして読まれるCtrl+Cを含む。制御ハンドライベントとして ではなく)、リサイズ、ドライラン、no-color、機能フォールバック、 衝突/ミスマッチの除外、修復ループ中のCtrl+C、declined vs. planned の報告、暫定のPackages/Linksディレクトリに対する最終サマリーを手動で 検証する;
- 新しいサードパーティ依存を一切追加しない;
- Copilotレビューを要求し、実行可能なフィードバックを取り込み、対応した 各コメントに返信し、そのレビュースレッドを解決する; および
- レビュー可能な状態になった後もマージせずに残す。
各プルリクエストはそのsub-issueに対して Closes を使用し、メンテナが
最終的にマージした時点でissueがクローズされるようにする。それまでは、
#58、#59、#60、親の#9は開いたままとする。
-
docs/adr-phase-6.md: 3つのスタックされたプルリクエストが同じADR セクションを同時に編集するのを避けるため、共有の1つのADRではなく sub-issueごとに1つのADR - ADR-0026(#58、端末所有権とフォールバック)、 ADR-0027(#59、選択/オプション契約)、ADR-0028(#60、共有エグゼキュータ と修正済みの終了コード優先順位)。ADR-0026は#58専用であり、M8の issue #113(--tui/--verbose/--quiet)は、この改訂が見つけた番号の 衝突を避けるためADR-0026ではなくADR-0029を使う。 -
docs/TODO.md: 対応するsub-issueごとにM7の項目を1つチェックする。 -
docs/PLAN.md: TUIのDefinition-of-Done項目は#60でのみマークする。 -
docs/task.md: すべてのsub-issueについて実装と検証の証跡を記録する。
正典ドキュメントとコードコメントは英語のままである。ローカライズされた
*_ja.md ファイルは実装のインプットではなく、変更されない。
実装された M7 のチェックリストは、ADR-0027 決定 4 に従い Missing と Broken の候補
だけ を表示していた。issue #179 はそれでは不十分であることを示した。Ok なリンクが
20 件、Mismatch が 1 件というホストでは fix --tui がチェックリストをまったく出さず、
無言で非対話ループに落ちてしまい、注意が必要な唯一の候補を隠していた。
現在は Mismatch を選択不可の情報行として表示する。現行の契約は次のとおり。
「fix は Mismatch を決して修復しない」という点は忘れられやすいので、ここに残しておく。
LinkStatus |
チェックリストに出る? | 選択可能? |
fix の動作 |
|---|---|---|---|
Missing |
出る | 可 — チェックが作成への同意 | シンボリックリンクを作成する |
Broken |
出る | 可 — チェックが置換への同意 | 削除して作り直す |
Mismatch |
出る ([-] ... [cannot repair]) |
不可 |
何もしない — refused (mismatch) を報告 |
Ok |
出ない | — | 何もしない — already Ok を報告 |
エイリアス衝突はチェックリストより手前で除外され、到達しない (ADR-0021)。
Mismatch は今後も修復しない。Links\ 配下のエントリが通常ファイル、シンボリック
リンク以外のリパースポイント、または 別の 既存ファイルを指すシンボリックリンクである
という状態であり、置き換えれば実際にそこにあるものを破壊してしまう (ADR-0014、ADR-0016)。
--force / --replace-mismatch に相当するオプションは存在せず、その判断は維持する。
行を表示するのは、手動対応が必要な対象をユーザーが把握できるようにするためであって、
ここで操作させるためではない。
同じ変更で 2 点を併せて修正した。「表示するものが何もない」場合に stderr へ警告を出すように
した (ADR-0027 決定 5 は元々これを規定していたが、実装されていたのは端末能力の分岐だけ
だった)。また、グループ化プレビューの抑止を、--tui が指定されただけのときではなく、
チェックリストが実際に起動したときだけに限定した。
詳細は --tui の表示内容とパス表示
と docs/adr-phase-10.md の ADR-0047 を参照。