feat(cli): add the --config launch option #87

Merged
natsukium merged 3 commits from feat/config-path-flag-27 into main 2026-09-04 21:29:17 +09:00
Owner

Fixes #27.

A one-off launch could not name its own config.toml. This adds one
root flag, felis --config PATH, that reaches window launches (the
resolved absolute path is forwarded to the frontend, which watches it
for live reloads too), the config path|check|show-effective verbs,
and doctor. A relative path is resolved in the front door and only
there, because the window it launches runs elsewhere and cannot
recover what the user meant.

felis-client-core now distinguishes a selected config from the
default one: a file missing at the default location stays the
first-run "use the defaults" case, while a selection that is not
there is a missing_file error. The verbs that read no config file
refuse the flag at exit 2 rather than ignoring it, the same way they
already refuse the carrier globals. There is no environment-variable
spelling: felis stamps the spawning environment into every session, so
one set for a launch would redirect the felis config runs inside
that window too.

Doc cascade: docs/reference/cli.md (global options, the config
verb exit codes, the frontend exec contract),
docs/reference/config.md (discovery, the missing_file diagnostic,
the absent/unreadable-file table),
docs/explanation/architecture/control-surfaces.md (why one flag and
no env var), docs/reference/control-surfaces.md,
docs/reference/testing.md, docs/backlog.md, CHANGELOG.md, and
the product-shipped skills/felis/SKILL.md.

Rebased onto current main; the stacked parent (#23) is already in
main, and the felis version verb that landed meanwhile was folded
into the no-config verb list, its refusal test, and the docs.

Verified: just check green (fmt, clippy, nextest, deny); reviewed by
plan/correctness/docs lenses and pi sol

Deferred

  • describe_config_source helper plus the "selected by --config"
    phrasing in doctor's config row and config path's human output —
    no acceptance criterion needs the wording; the selected absolute
    path is already reported by every consumer.
  • Making an explicitly selected but unusable config a hard startup
    failure for the GUI client (it currently warns and falls back to
    defaults, as load always has) — no criterion covers GUI startup
    behavior, and changing it would alter the stay-alive-on-a-typo
    contract.
  • An end-to-end test that a launched window actually reads the
    forwarded file — needs a real winit/wgpu session; the forwarding is
    pinned at the argv level by
    a_relative_config_selection_is_forwarded_absolute instead.
  • The single source-description helper the plan required was not
    implemented — already on the implementer's deferred list with the
    same reasoning: no acceptance criterion requires the wording, and
    the selected absolute path is already reported by every consumer.
  • sessions/notifications --help still advertises short_id
    fields the JSON no longer emits — traces to commit 5da5427d
    ("Refs #23"), stacked below issue #27's commits but not part of
    them; out of scope for the --config flag acceptance criteria.
  • A landing that reaches the pool before the fan-out is recorded is
    dropped, leaving the target pending forever — traces to commit
    5b2f709e, an unrelated switch/queued-landing feature stacked below
    issue #27's commits; not part of this issue's scope.
  • CLI --help text still advertises short_id fields removed from
    these replies — duplicate of the short_id finding; traces to
    commit 5da5427d ("Refs #23"), out of scope for issue #27.
  • CHANGELOG entry still describes a felis ssh alias this same branch
    removes — traces to commits 5da5427d/f1509a33 ("Refs #23"), stacked
    below issue #27's commits; not part of the --config flag work this
    round is reviewing.
  • Nothing tests that felis-client accepts or honors --config; only
    the sender side of the exec contract is pinned — no acceptance
    criterion names a required GUI-side unit test by number, and the
    implementer's own deferred list already excludes the stronger
    end-to-end version of this same gap as needing a real winit/wgpu
    session; a lighter parse-level test is the same class of extra
    hardening, not a demonstrated wrong output.
Fixes #27. A one-off launch could not name its own `config.toml`. This adds one root flag, `felis --config PATH`, that reaches window launches (the resolved absolute path is forwarded to the frontend, which watches it for live reloads too), the `config path|check|show-effective` verbs, and `doctor`. A relative path is resolved in the front door and only there, because the window it launches runs elsewhere and cannot recover what the user meant. `felis-client-core` now distinguishes a selected config from the default one: a file missing at the default location stays the first-run "use the defaults" case, while a selection that is not there is a `missing_file` error. The verbs that read no config file refuse the flag at exit `2` rather than ignoring it, the same way they already refuse the carrier globals. There is no environment-variable spelling: felis stamps the spawning environment into every session, so one set for a launch would redirect the `felis config` runs inside that window too. Doc cascade: `docs/reference/cli.md` (global options, the `config` verb exit codes, the frontend exec contract), `docs/reference/config.md` (discovery, the `missing_file` diagnostic, the absent/unreadable-file table), `docs/explanation/architecture/control-surfaces.md` (why one flag and no env var), `docs/reference/control-surfaces.md`, `docs/reference/testing.md`, `docs/backlog.md`, `CHANGELOG.md`, and the product-shipped `skills/felis/SKILL.md`. Rebased onto current `main`; the stacked parent (#23) is already in `main`, and the `felis version` verb that landed meanwhile was folded into the no-config verb list, its refusal test, and the docs. Verified: just check green (fmt, clippy, nextest, deny); reviewed by plan/correctness/docs lenses and pi sol ## Deferred - `describe_config_source` helper plus the "selected by --config" phrasing in doctor's config row and `config path`'s human output — no acceptance criterion needs the wording; the selected absolute path is already reported by every consumer. - Making an explicitly selected but unusable config a hard startup failure for the GUI client (it currently warns and falls back to defaults, as `load` always has) — no criterion covers GUI startup behavior, and changing it would alter the stay-alive-on-a-typo contract. - An end-to-end test that a launched window actually reads the forwarded file — needs a real winit/wgpu session; the forwarding is pinned at the argv level by `a_relative_config_selection_is_forwarded_absolute` instead. - The single source-description helper the plan required was not implemented — already on the implementer's deferred list with the same reasoning: no acceptance criterion requires the wording, and the selected absolute path is already reported by every consumer. - `sessions`/`notifications` `--help` still advertises `short_id` fields the JSON no longer emits — traces to commit 5da5427d ("Refs #23"), stacked below issue #27's commits but not part of them; out of scope for the `--config` flag acceptance criteria. - A landing that reaches the pool before the fan-out is recorded is dropped, leaving the target pending forever — traces to commit 5b2f709e, an unrelated switch/queued-landing feature stacked below issue #27's commits; not part of this issue's scope. - CLI `--help` text still advertises `short_id` fields removed from these replies — duplicate of the `short_id` finding; traces to commit 5da5427d ("Refs #23"), out of scope for issue #27. - CHANGELOG entry still describes a `felis ssh` alias this same branch removes — traces to commits 5da5427d/f1509a33 ("Refs #23"), stacked below issue #27's commits; not part of the `--config` flag work this round is reviewing. - Nothing tests that `felis-client` accepts or honors `--config`; only the sender side of the exec contract is pinned — no acceptance criterion names a required GUI-side unit test by number, and the implementer's own deferred list already excludes the stronger end-to-end version of this same gap as needing a real winit/wgpu session; a lighter parse-level test is the same class of extra hardening, not a demonstrated wrong output.
`--config PATH` needs a loader that can tell "the file the user named"
from "the file platform discovery guessed at": an absent file is a
first run in the second case and a typo in the first, and answering a
question about a named document with the defaults would report facts
about a different one. `ConfigSource` carries that distinction to
every front door, so the CLI verbs, `doctor`, and the GUI client
cannot disagree about which file they read or about what its absence
means.

Refs #27
A one-off launch could not name its own config.toml, so trying a
setting meant editing the real profile and putting it back; the
Windows config and doctor suites were `cfg(unix)` for the same
missing surface, because the known-folder API that resolves
`%APPDATA%` reads no environment variable and a temp profile is
therefore unreachable from a test.

One root flag serves both. It reaches window launches (forwarded to
the frontend, which also watches the selected file for live reloads),
the `config` verbs, and `doctor`; the verbs that read no config file
refuse it rather than ignoring it, as they already refuse the carrier
globals. A relative path is resolved here and only here, because the
window it launches runs elsewhere and cannot recover what the user
meant. An environment variable was the alternative and is rejected:
felis stamps the spawning environment into every session, so one set
for a launch would redirect the `felis config` runs inside that
window too.

The config and doctor suites now select their fixture with the flag
and run on Windows; what stays Unix-only is the discovery test and
the unreadable-file case, which need the known folder and mode bits.

Refs #27
docs: record the one --config control surface
All checks were successful
bench / Criterion full-suite snapshot (pull_request) Has been skipped
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
darwin / build felis (aarch64-darwin) (pull_request) Successful in 51s
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 1m5s
pr / publish felis (x86_64-linux) (pull_request) Has been skipped
windows / cargo clippy (Windows cross) (pull_request) Successful in 14s
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been skipped
fuzz / cargo fuzz nightly long-run (push) Has been skipped
bench / Criterion regression gate (pull_request) Successful in 2m0s
pr / nix flake check (pull_request) Successful in 27s
pr / cargo build / clippy / test / deny (pull_request) Successful in 1m44s
pr / wire schema is compatible with the base (pull_request) Successful in 8s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 50s
windows / cargo nextest (Windows) (pull_request) Successful in 6m27s
windows / frontend smoke (Windows) (pull_request) Successful in 1m35s
darwin / build felis (aarch64-darwin) (push) Successful in 13s
fuzz / cargo fuzz smoke (per target) (push) Successful in 1m8s
pr / nix flake check (push) Successful in 6s
pr / cargo build / clippy / test / deny (push) Successful in 1m36s
pr / publish felis (x86_64-linux) (push) Successful in 9s
windows / cargo nextest (Windows) (push) Successful in 6m4s
windows / package felis (x86_64-pc-windows-msvc) (push) Successful in 1m38s
pr / wire schema is compatible with the base (push) Successful in 8s
pr / frontend smoke (x86_64-linux) (push) Successful in 6s
windows / cargo clippy (Windows cross) (push) Successful in 14s
windows / frontend smoke (Windows) (push) Successful in 1m38s
84f26c4706
The decision needs a home before the flag can be relied on: the
explanation twin now carries why one per-invocation flag was chosen
and why an environment variable and a config key naming another
config were rejected, with the revisit trigger for a multi-profile
workflow. The reference pages state the resulting rules — resolution,
the per-verb acceptance, and the missing-selection error — and the
Windows coverage gap closes in testing.md and the backlog because the
suites that were blocked on this decision now run there.

Refs #27
natsukium deleted branch feat/config-path-flag-27 2026-09-04 21:29:17 +09:00
Sign in to join this conversation.
No description provided.