[v0.1/CLI Args Review] CLI 引数のリリース前設計レビュー #55

Closed
opened 2026-09-03 16:45:17 +09:00 by natsukium · 3 comments
Owner

概要

v0.1.0 の public contract freeze 前提で、CLI argument grammar を 2645b986 + review working tree (5077d74b) に対して重点レビューした。互換性は考慮せず、以下を実施した。

  • felis / 全 visible subcommand / felis-client / felis-daemon の実 help を生成して比較
  • clap derive 定義と parser tests を全走査
  • docs/reference/cli.mdreference/control-surfaces.mdexplanation/architecture/control-surfaces.md、IPC/requirements/backlog と照合
  • typo、option placement、mutual exclusion、machine mode、command argv separator、dynamic completion の失敗経路を実コマンドで確認

設計の主軸(window launch と headless verbs の分離、typed argv、session prefix、Point/Stream/Exempt の分類)は維持できる。一方、凍結前に直すべき新規2件と、既存 P0/P1 issue に追加して確認すべき argument-level gap が見つかった。

新規 issue

Issue 優先度 判定 理由
#53 P1 破壊的変更推奨 sessions spawn だけ -- を要求せず、option typo (spawn --json) を program 実行要求として受理する。bare launch/retarget と -- <CMD>... に統一する。
#54 P1 削除または scope 制限推奨 root --trace-perf は headless/config/doctor/bridge で受理後 silent ignore。RUST_LOG と process-local 診断面があるため public global に置く根拠が弱い。

既存 issue に統合する確認事項

#17 — version grammar

felis --version sessions listfelis --version -- htop が parse に成功し、後続 command を無視して version report を exit 0 で返す。#17 の offline/self-only 化に加え、標準 --version と同様に exclusive であることを acceptance に含めるべき。

#23 — machine/usage contract

#23 の方針は妥当だが、実装時に次を golden test へ含める必要がある。

  1. sessions spawn --format json --json の daemon-autospawn failure では、stderr に tracing の INFO 行が先行し、現在の「point failure は exactly one JSON object」という契約を破る。machine channel と logs を分離または machine mode で console logs を抑止する。
  2. --formatcapture --source は custom value_parser = fn のため、help/completion metadata に possible values が載らない。completions を public surface とするなら finite vocabulary を clap に公開する。
  3. window retarget --session 1a -- htop、SSH carrier なしの --ssh-arg、payload なしの send は clap 後の usage error。#23 の「valid machine format 認識後の usage error は同 format」をこの全経路で揃える。
  4. send --raw --wait / send --raw --key enter--raw の対象 payload がないのに受理される。無効 option を拒否するか、効果がある意味を定義する。

#27 — config path

--config PATH は argument review でも必要性を再確認した。Windows test だけでなく、実 profile を置換せず config check/show-effective を試す唯一の再現可能な入口になる。既存 #27 の scope で十分。

#23 / #37 / #38 / #39 — carrier spelling and placement

同じ --host が root では「この invocation が dial する daemon」、window retarget 配下では「window の destination」を意味し、flag の位置だけで役割が変わる。#23 の --to-host + felis ssh removal が最も単純で、#37–#39 の問いをまとめて閉じられる。root carrier を真の global option として subcommand 後にも許すか、常に root-before-command と凍結するかも同時に決める。

#39 / #42 — dynamic completion の network side effect

fish/zsh completion は <TAB> ごとに hidden __complete-sessions を実行し、in-flight --host を転送して SSH 接続する。connect_stdio_command は SSH stderr/TTY を継承し felis-side timeout を持たないため、remote session-id completion が password/host-key prompt や不達 host で対話シェルを停止し得る。

completion は local-only に戻す、短い hard timeout + noninteractive SSH + cache を設ける、または remote dynamic completion を削る、のどれかを #39/#42 の carrier/timeout 決定に含めるべき。explanation/architecture/control-surfaces.md は helper を local-only と記述する一方、実装と reference/cli.md は remote forwarding を normative としており、現状は docs も不整合。

今回は変更不要と判断した点

  • felis attach を top-level launcher、sessions switch を headless IPC verb とする分離: process boundary を正しく表しており、closed #3 の再オープン理由なし。
  • frontend <name> [ARGS]... のみ opaque pass-through: namespace が明示されており、unknown built-in typo を exec しない。
  • send <id> - の stdin convention、repeatable --key--env KEY=VAL: shell string evaluator を導入せず typed data のまま。
  • session id の full hex + unique prefix: human paste-back と persisted id の区別が明確。

推奨順序

  1. #17--version の standard/exclusive contract を確定。
  2. #23 と #37–#39 で machine output / carrier naming / alias を一度に確定。
  3. #53 で全 command-launch form の separator を統一。
  4. #54 で ignored root option を除去。
  5. #39/#42 で completion の remote dial policy を確定。
  6. #27 を実装後、全 clap help/completion/man/schema golden を #29 で freeze。

Principle verdict

提案はいずれも capability を増やさず、曖昧・重複・silent no-op を削る。principles 1–4 はすべて Pass。特に command separator と ignored option の除去は principle 4 の explicitness を強める。

完了条件

  • 新規2件と上記既存 P0/P1 issue の argument-level acceptance が解決済み。
  • 全 visible command の help snapshot と valid/invalid argv matrix がテストされる。
  • machine mode の stdout/stderr に console log が混ざらない。
  • finite option values が help と generated completion の両方に現れる。
  • completion が <TAB> から無期限/対話的 network operation を起動しない。
  • docs/reference/cli.md、reference/explanation twins、tutorials、man、completion、skills/felisCHANGELOG.md が同じ grammar を示す。

レビュー対象

  • crates/felis-cli/src/{main,cli_sessions,cli_output,cli_completions,cli_notifications,cli_config,cli_daemon}.rs
  • crates/felis-client/src/main.rs
  • crates/felis-daemon/src/main.rs
  • crates/felis-client-core/src/{dial,connector,local_socket}.rs
  • docs/reference/{cli,control-surfaces,spec}.md
  • docs/explanation/{design,principles,non-goals,architecture/control-surfaces,architecture/ipc}.md
  • docs/backlog.md

Parent release tracker: #12

## 概要 v0.1.0 の public contract freeze 前提で、CLI argument grammar を `2645b986` + review working tree (`5077d74b`) に対して重点レビューした。互換性は考慮せず、以下を実施した。 - `felis` / 全 visible subcommand / `felis-client` / `felis-daemon` の実 help を生成して比較 - clap derive 定義と parser tests を全走査 - `docs/reference/cli.md`、`reference/control-surfaces.md`、`explanation/architecture/control-surfaces.md`、IPC/requirements/backlog と照合 - typo、option placement、mutual exclusion、machine mode、command argv separator、dynamic completion の失敗経路を実コマンドで確認 設計の主軸(window launch と headless verbs の分離、typed argv、session prefix、Point/Stream/Exempt の分類)は維持できる。一方、**凍結前に直すべき新規2件**と、既存 P0/P1 issue に追加して確認すべき argument-level gap が見つかった。 ## 新規 issue | Issue | 優先度 | 判定 | 理由 | |---|---:|---|---| | #53 | P1 | 破壊的変更推奨 | `sessions spawn` だけ `--` を要求せず、option typo (`spawn --json`) を program 実行要求として受理する。bare launch/retarget と `-- <CMD>...` に統一する。 | | #54 | P1 | 削除または scope 制限推奨 | root `--trace-perf` は headless/config/doctor/bridge で受理後 silent ignore。`RUST_LOG` と process-local 診断面があるため public global に置く根拠が弱い。 | ## 既存 issue に統合する確認事項 ### #17 — version grammar `felis --version sessions list` と `felis --version -- htop` が parse に成功し、後続 command を無視して version report を exit 0 で返す。#17 の offline/self-only 化に加え、標準 `--version` と同様に exclusive であることを acceptance に含めるべき。 ### #23 — machine/usage contract #23 の方針は妥当だが、実装時に次を golden test へ含める必要がある。 1. `sessions spawn --format json --json` の daemon-autospawn failure では、stderr に tracing の INFO 行が先行し、現在の「point failure は exactly one JSON object」という契約を破る。machine channel と logs を分離または machine mode で console logs を抑止する。 2. `--format` と `capture --source` は custom `value_parser = fn` のため、help/completion metadata に possible values が載らない。`completions` を public surface とするなら finite vocabulary を clap に公開する。 3. `window retarget --session 1a -- htop`、SSH carrier なしの `--ssh-arg`、payload なしの `send` は clap 後の usage error。#23 の「valid machine format 認識後の usage error は同 format」をこの全経路で揃える。 4. `send --raw --wait` / `send --raw --key enter` は `--raw` の対象 payload がないのに受理される。無効 option を拒否するか、効果がある意味を定義する。 ### #27 — config path `--config PATH` は argument review でも必要性を再確認した。Windows test だけでなく、実 profile を置換せず `config check/show-effective` を試す唯一の再現可能な入口になる。既存 #27 の scope で十分。 ### #23 / #37 / #38 / #39 — carrier spelling and placement 同じ `--host` が root では「この invocation が dial する daemon」、`window retarget` 配下では「window の destination」を意味し、flag の位置だけで役割が変わる。#23 の `--to-host` + `felis ssh` removal が最も単純で、#37–#39 の問いをまとめて閉じられる。root carrier を真の global option として subcommand 後にも許すか、常に root-before-command と凍結するかも同時に決める。 ### #39 / #42 — dynamic completion の network side effect fish/zsh completion は `<TAB>` ごとに hidden `__complete-sessions` を実行し、in-flight `--host` を転送して SSH 接続する。`connect_stdio_command` は SSH stderr/TTY を継承し felis-side timeout を持たないため、remote session-id completion が password/host-key prompt や不達 host で対話シェルを停止し得る。 completion は local-only に戻す、短い hard timeout + noninteractive SSH + cache を設ける、または remote dynamic completion を削る、のどれかを #39/#42 の carrier/timeout 決定に含めるべき。`explanation/architecture/control-surfaces.md` は helper を local-only と記述する一方、実装と `reference/cli.md` は remote forwarding を normative としており、現状は docs も不整合。 ## 今回は変更不要と判断した点 - `felis attach` を top-level launcher、`sessions switch` を headless IPC verb とする分離: process boundary を正しく表しており、closed #3 の再オープン理由なし。 - `frontend <name> [ARGS]...` のみ opaque pass-through: namespace が明示されており、unknown built-in typo を exec しない。 - `send <id> -` の stdin convention、repeatable `--key`、`--env KEY=VAL`: shell string evaluator を導入せず typed data のまま。 - session id の full hex + unique prefix: human paste-back と persisted id の区別が明確。 ## 推奨順序 1. #17 で `--version` の standard/exclusive contract を確定。 2. #23 と #37–#39 で machine output / carrier naming / alias を一度に確定。 3. #53 で全 command-launch form の separator を統一。 4. #54 で ignored root option を除去。 5. #39/#42 で completion の remote dial policy を確定。 6. #27 を実装後、全 clap help/completion/man/schema golden を #29 で freeze。 ## Principle verdict 提案はいずれも capability を増やさず、曖昧・重複・silent no-op を削る。principles 1–4 はすべて Pass。特に command separator と ignored option の除去は principle 4 の explicitness を強める。 ## 完了条件 - [ ] 新規2件と上記既存 P0/P1 issue の argument-level acceptance が解決済み。 - [ ] 全 visible command の help snapshot と valid/invalid argv matrix がテストされる。 - [ ] machine mode の stdout/stderr に console log が混ざらない。 - [ ] finite option values が help と generated completion の両方に現れる。 - [ ] completion が `<TAB>` から無期限/対話的 network operation を起動しない。 - [ ] `docs/reference/cli.md`、reference/explanation twins、tutorials、man、completion、`skills/felis`、`CHANGELOG.md` が同じ grammar を示す。 ## レビュー対象 - `crates/felis-cli/src/{main,cli_sessions,cli_output,cli_completions,cli_notifications,cli_config,cli_daemon}.rs` - `crates/felis-client/src/main.rs` - `crates/felis-daemon/src/main.rs` - `crates/felis-client-core/src/{dial,connector,local_socket}.rs` - `docs/reference/{cli,control-surfaces,spec}.md` - `docs/explanation/{design,principles,non-goals,architecture/control-surfaces,architecture/ipc}.md` - `docs/backlog.md` Parent release tracker: #12
Author
Owner

Triage (2026-09-05): the 2026-09-05 CLI review adds #136, #141, #145 as P1 children of this tracker. #53 and #54 are merged. Closure now waits on those three plus #29's goldens.

Triage (2026-09-05): the 2026-09-05 CLI review adds #136, #141, #145 as P1 children of this tracker. #53 and #54 are merged. Closure now waits on those three plus #29's goldens.
Author
Owner

残っていた完了条件のうち子 issue がなかった2件を分割した:

  • #184 — 有限語彙 (--format/--source) の clap 公開
  • #185 — 全 visible command の help snapshot と argv matrix

他 (version 排他、machine/log 分離、completion local-only、send --raw requires) は実装済みを確認。

残っていた完了条件のうち子 issue がなかった2件を分割した: - #184 — 有限語彙 (--format/--source) の clap 公開 - #185 — 全 visible command の help snapshot と argv matrix 他 (version 排他、machine/log 分離、completion local-only、send --raw requires) は実装済みを確認。
Author
Owner

Closed (2026-09-08)

Every child issue this tracker raised is closed and every prose completion criterion has landed evidence on main.

Children

  • #53: closed by commit 5a1a49a2 (fix(cli): require -- before spawn argv and drop ignored --trace-perf)
  • #54: closed by commit 5a1a49a2 (fix(cli): require -- before spawn argv and drop ignored --trace-perf)
  • #136: closed by PR #151 (cli: refuse every global flag no verb can honor)
  • #141: closed by PR #161 (docs: make the auto-spawn contract per verb, not per carrier)
  • #145: closed by PR #163 (docs: name the Point-diagnostic verb class)
  • #29: closed by PR #180 (cli: publish JSON Schemas for the machine surface)
  • #184: closed as a duplicate, merged into #187
  • #185: closed by PR #206 (cli: pin the frozen argv grammar as one table)
  • #187: closed by PR #198 (cli: publish the finite option vocabularies through clap) and PR #206 (cli: snapshot every visible help page / pin the frozen argv grammar)

The pre-existing issues named in this tracker's recommended order are closed too:

  • #17: closed by PR #83 (feat(protocol): give every build one typed identity; feat(cli): split the self-report from the build comparison)
  • #23: closed by PR #85 (feat(cli): simplify the machine contract before the freeze)
  • #27: closed by PR #87 (feat(cli): add the --config launch option)
  • #37 and #39: closed as duplicates of #23, which owns the carrier-spelling decision
  • #38: closed with no code change; one --ssh-arg stays one verbatim argv token and multiplexing stays OpenSSH's, recorded in docs/explanation/architecture/ipc.md
  • #42: closed by PR #58 (fix(cli): make dynamic session completion local-only)

Completion criteria

  • 新規2件と既存 P0/P1 issue の argument-level acceptance が解決済み — every issue listed above reports state: closed via the API; no open issue carries release/v0.1.0 besides #12, #31, and this tracker.
  • 全 visible command の help snapshot と valid/invalid argv matrix がテストされるcrates/felis-cli/src/tests.rs: fn every_visible_help_page_is_snapshotted() walks the whole non-hidden subcommand tree into crates/felis-cli/src/snapshots/felis__tests__every_visible_help_page_is_snapshotted.snap, and mod argv_matrix pins the parser layer: "The frozen argv grammar as a table: one row per line whose outcome the surface has committed to, so a refactor that loosens a rule fails here instead of shipping."
  • machine mode の stdout/stderr に console log が混ざらないcrates/felis-cli/src/main.rs: "A machine format silences the console: a point verb's stderr is one typed error object, and an autospawn INFO line beside it would make that two lines. RUST_LOG still wins", implemented as let fallback = if cli.machine_output() { "off" } else { "info,felis=debug" };.
  • finite option values が help と generated completion の両方に現れるcrates/felis-cli/src/tests.rs asserts help.contains("[possible values: human, jsonl]"), help.contains("[possible values: visible, scrollback, command-output, last-command]"), and for a point verb point.contains("[possible values: human, json]"); the vocabularies became clap ValueEnums in PR #198.
  • completion が <TAB> から無期限/対話的 network operation を起動しないcrates/felis-cli/src/cli_completions.rs: "Completion is local-only: the helper never dials SSH." (and the same rule again for the retarget helpers), landed by PR #58.
  • docs/reference/cli.md、reference/explanation twins、tutorials、man、completion、skills/felisCHANGELOG.md が同じ grammar を示す — the separator grammar reads the same everywhere: docs/reference/cli.md "detached command without a window, use felis sessions spawn -- <cmd>", skills/felis/SKILL.md felis sessions spawn -- /bin/zsh and felis sessions spawn --tag agent -- …, with the matching CHANGELOG.md entries; the generated help, man, and completion surfaces are frozen by the snapshot test above and the schemas by #29.

Follow-ups

None. No follow-up issue from this review moved to a post-v0.1 backlog; #178, raised from the #29 schema work, was itself closed on main.

## Closed (2026-09-08) Every child issue this tracker raised is closed and every prose completion criterion has landed evidence on `main`. ### Children - #53: closed by commit `5a1a49a2` (fix(cli): require -- before spawn argv and drop ignored --trace-perf) - #54: closed by commit `5a1a49a2` (fix(cli): require -- before spawn argv and drop ignored --trace-perf) - #136: closed by PR #151 (cli: refuse every global flag no verb can honor) - #141: closed by PR #161 (docs: make the auto-spawn contract per verb, not per carrier) - #145: closed by PR #163 (docs: name the Point-diagnostic verb class) - #29: closed by PR #180 (cli: publish JSON Schemas for the machine surface) - #184: closed as a duplicate, merged into #187 - #185: closed by PR #206 (cli: pin the frozen argv grammar as one table) - #187: closed by PR #198 (cli: publish the finite option vocabularies through clap) and PR #206 (cli: snapshot every visible help page / pin the frozen argv grammar) The pre-existing issues named in this tracker's recommended order are closed too: - #17: closed by PR #83 (feat(protocol): give every build one typed identity; feat(cli): split the self-report from the build comparison) - #23: closed by PR #85 (feat(cli): simplify the machine contract before the freeze) - #27: closed by PR #87 (feat(cli): add the --config launch option) - #37 and #39: closed as duplicates of #23, which owns the carrier-spelling decision - #38: closed with no code change; one `--ssh-arg` stays one verbatim argv token and multiplexing stays OpenSSH's, recorded in `docs/explanation/architecture/ipc.md` - #42: closed by PR #58 (fix(cli): make dynamic session completion local-only) ### Completion criteria - **新規2件と既存 P0/P1 issue の argument-level acceptance が解決済み** — every issue listed above reports `state: closed` via the API; no open issue carries `release/v0.1.0` besides #12, #31, and this tracker. - **全 visible command の help snapshot と valid/invalid argv matrix がテストされる** — `crates/felis-cli/src/tests.rs`: `fn every_visible_help_page_is_snapshotted()` walks the whole non-hidden subcommand tree into `crates/felis-cli/src/snapshots/felis__tests__every_visible_help_page_is_snapshotted.snap`, and `mod argv_matrix` pins the parser layer: "The frozen argv grammar as a table: one row per line whose outcome the surface has committed to, so a refactor that loosens a rule fails here instead of shipping." - **machine mode の stdout/stderr に console log が混ざらない** — `crates/felis-cli/src/main.rs`: "A machine format silences the console: a point verb's stderr is one typed error object, and an autospawn INFO line beside it would make that two lines. `RUST_LOG` still wins", implemented as `let fallback = if cli.machine_output() { "off" } else { "info,felis=debug" };`. - **finite option values が help と generated completion の両方に現れる** — `crates/felis-cli/src/tests.rs` asserts `help.contains("[possible values: human, jsonl]")`, `help.contains("[possible values: visible, scrollback, command-output, last-command]")`, and for a point verb `point.contains("[possible values: human, json]")`; the vocabularies became clap `ValueEnum`s in PR #198. - **completion が `<TAB>` から無期限/対話的 network operation を起動しない** — `crates/felis-cli/src/cli_completions.rs`: "Completion is local-only: the helper never dials SSH." (and the same rule again for the retarget helpers), landed by PR #58. - **`docs/reference/cli.md`、reference/explanation twins、tutorials、man、completion、`skills/felis`、`CHANGELOG.md` が同じ grammar を示す** — the separator grammar reads the same everywhere: `docs/reference/cli.md` "detached command without a window, use `felis sessions spawn -- <cmd>`", `skills/felis/SKILL.md` `felis sessions spawn -- /bin/zsh` and `felis sessions spawn --tag agent -- …`, with the matching `CHANGELOG.md` entries; the generated help, man, and completion surfaces are frozen by the snapshot test above and the schemas by #29. ### Follow-ups None. No follow-up issue from this review moved to a post-v0.1 backlog; #178, raised from the #29 schema work, was itself closed on `main`.
Sign in to join this conversation.
No description provided.