[v0.1/P1] Add one explicit --config PATH control surface #27
Labels
No labels
priority/P0
priority/P1
priority/P2
release/v0.1.0
status/blocked
status/planned
type/bug
type/design
type/test-gap
type/tracker
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
natsukium/felis#27
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Parent: #12 (P1.7). Supersedes the path-override decision in #4.
Why
Without an explicit config path, one-off launches cannot be reproduced without mutating the real profile and Windows config/doctor end-to-end tests remain disabled. Parallel environment-variable spellings would create permanent aliases.
Scope
--config PATHlaunch option.felis, GUI launch/exec,config, anddoctor.FELIS_CONFIGor a second spelling.Acceptance criteria
Triage plan (2026-09-03)
Source-grounded triage against
mainat69076d42, reviewed through seven rounds of an independent reviewer (pisol/luna) until it passed with no findings. The dependency order that supersedes the tracker's is posted on #12.Claim check
Accurate at HEAD.
config_path()isProjectDirs::from("", "", "felis").config_dir()/config.tomlwith no environment or flag input (crates/felis-client-core/src/config/document.rs:292-306). Every reader goes through it:EffectiveConfig::load/try_load(document.rs:198-212), the CLIconfigverbs'resolve_path(crates/felis-cli/src/cli_config.rs:96-104),doctor'sconfig_check(crates/felis-cli/src/cli_doctor.rs:170-172), the GUI's startup load (crates/felis-client/src/main.rs:339) and its file watcher (main.rs:861-866).grep -rn FELIS_CONFIGovercrates/ docs/ nix/finds nothing, so there is no second spelling to unify.crates/felis-cli/tests/cli_config.rs:4-9(#![cfg(unix)]) and the four#[cfg(unix)]tests intests/cli_doctor.rs:22-25, 102, 165, 188inject a temp home throughHOME/XDG_CONFIG_HOME(cli_config.rs:16-22), which Windows's known-folder lookup ignores.docs/reference/testing.md:1124-1136anddocs/backlog.md:29-37record the gap and explicitly defer it to "a config-path control surface decision (principle 1)". This issue is that decision.felisrebuilds the root flags it forwards tofelis-clientexplicitly inglobal_passthrough(crates/felis-cli/src/main.rs:522-540), so a new root flag has one place to be forwarded and one clap struct on the client side (felis-client/src/main.rs:249-305) to receive it.nix/hm-module.nix:212-233) and itssettingsdoc names the default locations (hm-module.nix:135-137); it needs a note, not a new option.#55 reconfirms the scope as sufficient and adds the second consumer:
config check/show-effectiveagainst a candidate file without replacing the live profile.Verdict
accept. Principle 1 test: "if a dedicated tool at the shell … does a feature as well or better … the terminal does not implement it." The shell-layer alternative is an environment variable, and it does the job worse here for a reason worth recording: felis stamps the spawning environment into every session (
docs/reference/terminal-identity.md), so aFELIS_CONFIGset for one launch would be inherited by every shell in that window and silently redirect anyfelis configrun inside it. A per-invocation flag has no such leak. Principle 4 passes (explicit path, no discovery heuristics added). No new spelling beyond the flag.Approach
Core (felis-client-core)
document.rs: addpub enum ConfigSource { Default, Explicit(PathBuf) }withfn path(&self) -> Option<PathBuf>(Default →config_path()), andEffectiveConfig::load_from(&ConfigSource, client_id)/try_load_from_source; keepload(client_id)asload_from(&Default, …)so nothing else moves.diagnose(path, client_id)gains aselected: boolargument (or adiagnose_source) so an explicitly selected file that does not exist yields an errorDiagnosticKind::MissingFile(config/diagnostics.rs:26already has the kind, used today as a warning for path-valued keys atvalidate.rs:146), while the default path missing stays the documented "use the defaults"Ok(None)(document.rs:216-217).std::env::current_dir()?.join(p)), and forward the absolute form. Rule to document: "relative to the directoryfeliswas run from; the reported path (config path, logs) is always absolute".ioerror diagnostic throughConfigDocument::read(document.rs:242-245); no change, but the test matrix must cover it.CLI (felis-cli)
Cli(main.rs:286-339):#[arg(long, value_name = "PATH")] config: Option<PathBuf>, root-before-command like--socket. Semantics per verb:attach: forwarded viaglobal_passthrough(main.rs:525) as--config <abs>;config path|check|show-effective:cli_config::run(op, &source);pathreports the selected file andexists;doctor:config_check(&source)(cli_doctor.rs:170-206); the row says "selected by --config" indetailwhen explicit;sessions,notifications,daemon,window retarget,bridge,completions) refuses it with exit 2 through the same pathreject_global_carrieruses (cli_sessions.rs:828-838; after #23, in the verb's class framing). A silently ignored root flag is exactly what #54 just removed, so accepting it there is not an option;frontend <name>: refused like the carriers (main.rs:463-469), with "put it after the frontend name".fn describe_config_source(&ConfigSource) -> Stringused byconfig pathhuman output,doctor, and the GUI startup log line.GUI client (felis-client)
Cli(felis-client/src/main.rs:249):--config <PATH>;EffectiveConfig::load_fromatmain.rs:339; the watcher (main.rs:861-866,config_watcher::spawn(handle, path, …)atconfig_watcher.rs:22) watches the selected path. Startup logs one line naming the source.Tests
felis-cli/src/tests.rs: parse matrix (--configaccepted beforeconfig,doctor, bare,attach; refused after the command;global_passthrough_rebuilds_set_flags_only(tests.rs:530) gains the flag and asserts it is absolute).tests/cli_config.rs: drop#![cfg(unix)]; replace theHOME/XDG_CONFIG_HOMEhelper (16-22) with--config <tempdir>/config.toml. Keep one Unix-only test proving the default discovery is untouched when the flag is absent (the existingconfig_pathread-back helper at27-35). Add: relative path resolved againstCommand::current_dir; missing explicit file →checkexit 1 with onemissing_fileerror,show-effectiveexit 1,doctorconfig rowfail; unreadable (chmod 000,cfg(unix)) →io; invalid →parse; the three verbs report the identicalpathstring.tests/cli_doctor.rs: the fourcfg(unix)tests switch to--configand run on Windows;the_config_row_points_at_the_config_verb(cli_doctor.rs:457) stays.ConfigSource::Expliciton a missing file is an error diagnostic;Defaulton a missing file isOk(default).windows-test(.forgejo/workflows/pr.yml:93-193) picks the tests up with no workflow change;testing.md:1124-1136andbacklog.md:29-37are rewritten to say the family now runs there.Cascade
docs/reference/cli.md: a "Global options" paragraph (or the "Config verbs" section at193-250) stating the flag, its per-verb acceptance, relative-path rule, and the missing-explicit-file diagnostic; "Window launches" (377-400) notes the forward to the GUI.docs/reference/config.md:16-30("where the file lives"): the override, one sentence, no env var.docs/explanation/architecture/control-surfaces.md"Diagnostic verbs": the decision record — chosen: one flag; rejected:FELIS_CONFIG(inherits into sessions and their children; a second spelling); rejected: a config key naming another config (evaluator-shaped); Revisit if a multi-profile workflow appears that a flag cannot serve.docs/reference/testing.md:1124-1136,docs/backlog.md:29-37: close the gap.nix/hm-module.nix:135-137: one sentence that--configoverrides the path the module writes to.felis-config.schema.jsonis untouched;just schemais a no-op (state this in the commit body so CI's stale-schema check is not expected to move).felis completions,__mangen: regenerate from clap automatically;skills/felis/SKILL.md:263-282("Is the config valid?") gains the--configline.CHANGELOG.md: one "Added" entry under CLI.Dependencies
eprintln!path that #23 then rewrites. If #23 slips, land this with the existingreject_global_carriershape and let #23 reframe it.Risk/effort
M. Three crates plus tests and docs, but every change is additive and local. Main risk: the Windows CI job now executes the config/doctor suites for the first time, and path rendering (
\separators,%APPDATA%case) may break string assertions written on Linux; write the tests againstconfig path --format jsonoutput rather than literal strings. Secondary risk:felis-clientandfelisdisagreeing on relative-path resolution if the client also resolves; resolve only infelisand treat the client's flag as already absolute (document, and assert in the passthrough test).Labels
Keep priority/P1, release/v0.1.0: it is inside the "documented environment variables / CLI option meanings" freeze boundary, and #31's Windows gate is stronger with it.