# Phase 1 Step 2 — Implementation Notes ## Status Done. ## Plan reference - Plan: `docs/PHASE-1-IMPLEMENTATION-PLAN.md` - Section: "Step 2: Migrate static methods off Config" ## Summary Extracted 33 static (no-`self`) methods from `impl Config` into a new `src/config/paths.rs` module and migrated every caller across the codebase. The deprecated forwarders the plan suggested as an intermediate step were added, used to drive the callsite migration, and then deleted in the same step because the migration was mechanically straightforward with `ast-grep` and the forwarders became dead immediately. ## What was changed ### New files - **`src/config/paths.rs`** (~270 lines) - Module docstring explaining the extraction rationale and the (transitional) compatibility shim pattern. - `#![allow(dead_code)]` at module scope because most functions were briefly dead during the in-flight migration; kept for the duration of Step 2 and could be narrowed or removed in a later cleanup (see "Follow-up" below). - All 33 functions as free-standing `pub fn`s, implementations copied verbatim from `impl Config`: - Path helpers: `config_dir`, `local_path`, `cache_path`, `oauth_tokens_path`, `token_file`, `log_path`, `config_file`, `roles_dir`, `role_file`, `macros_dir`, `macro_file`, `env_file`, `rags_dir`, `functions_dir`, `functions_bin_dir`, `mcp_config_file`, `global_tools_dir`, `global_utils_dir`, `bash_prompt_utils_file`, `agents_data_dir`, `agent_data_dir`, `agent_config_file`, `agent_bin_dir`, `agent_rag_file`, `agent_functions_file`, `models_override_file` - Listing helpers: `list_roles`, `list_rags`, `list_macros` - Existence checks: `has_role`, `has_macro` - Config loaders: `log_config`, `local_models_override` ### Modified files Migration touched 14 source files — all of `src/config/mod.rs`'s internal callers, plus every external `Config::method()` callsite: - **`src/config/mod.rs`** — removed the 33 static-method definitions from `impl Config`, rewrote every `Self::method()` internal caller to use `paths::method()`, and removed the `log::LevelFilter` import that became unused after `log_config` moved away. - **`src/config/bridge.rs`** — no changes (bridge is unaffected by path migrations). - **`src/config/macros.rs`** — added `use crate::config::paths;`, migrated one `Config::macros_dir().display()` call. - **`src/config/agent.rs`** — added `use crate::config::paths;`, migrated 2 `Config::agents_data_dir()` calls, 4 `agent_data_dir` calls, 3 `agent_config_file` calls, 1 `agent_rag_file` call. - **`src/config/request_context.rs`** — no changes. - **`src/config/app_config.rs`, `app_state.rs`** — no changes. - **`src/main.rs`** — added `use crate::config::paths;`, migrated `Config::log_config()`, `Config::list_roles(true)`, `Config::list_rags()`, `Config::list_macros()`. - **`src/function/mod.rs`** — added `use crate::config::paths;`, migrated ~25 callsites across `Config::config_dir`, `functions_dir`, `functions_bin_dir`, `global_tools_dir`, `agent_bin_dir`, `agent_data_dir`, `agent_functions_file`, `bash_prompt_utils_file`. Removed `Config` from the `use crate::{config::{...}}` block because it became unused. - **`src/repl/mod.rs`** — added `use crate::config::paths;`, migrated `Config::has_role(name)` and `Config::has_macro(name)`. - **`src/cli/completer.rs`** — added `use crate::config::paths;`, migrated `Config::list_roles(true)`, `Config::list_rags()`, `Config::list_macros()`. - **`src/utils/logs.rs`** — replaced `use crate::config::Config;` with `use crate::config::paths;` (Config was only used for `log_path`); migrated `Config::log_path()` call. - **`src/mcp/mod.rs`** — added `use crate::config::paths;`, migrated 3 `Config::mcp_config_file().display()` calls. - **`src/client/common.rs`** — added `use crate::config::paths;`, migrated `Config::local_models_override()`. Removed `Config` from the `config::{Config, GlobalConfig, Input}` import because it became unused. - **`src/client/oauth.rs`** — replaced `use crate::config::Config;` with `use crate::config::paths;` (Config was only used for `token_file`); migrated 2 `Config::token_file` calls. ### Module registration - **`src/config/mod.rs`** — added `pub(crate) mod paths;` in the module declaration block, alphabetically placed between `macros` and `prompts`. ## Key decisions ### 1. The deprecated forwarders lived for the whole migration but not beyond The plan said to keep `#[deprecated]` forwarders around while migrating callsites module-by-module. I followed that approach but collapsed the "migrate then delete" into a single step because the callsite migration was almost entirely mechanical — `ast-grep` with per-method patterns handled the bulk, and only a few edge cases (`Self::X` inside `&`-expressions, multi-line `format!` calls) required manual text edits. By the time all 33 methods had zero external callers, keeping the forwarders would have just generated dead_code warnings. The plan also said "then remove the deprecated methods" as a distinct phase, and that's exactly what happened — just contiguously with the migration rather than as a separate commit. The result is the same: no forwarders in the final tree, all callers routed through `paths::`. ### 2. `paths` is a `pub(crate)` module, not `pub` I registered the module as `pub(crate) mod paths;` so the functions are available anywhere in the crate via `crate::config::paths::X` but not re-exported as part of Loki's public API surface. This matches the plan's intent — these are internal implementation details that happen to have been static methods on `Config`. If anything external needs a config path in the future, the proper shape is probably to add it as a method on `AppConfig` (which goes through Step 3's global-read migration anyway) rather than exposing `paths` publicly. ### 3. `log_config` stays in `paths.rs` despite not being a path `log_config()` returns `(LevelFilter, Option)` — it reads environment variables to determine the log level plus falls back to `log_path()` for the file destination. Strictly speaking, it's not a "path" function, but: - It's a static no-`self` helper (the reason it's in Step 2) - It's used in exactly one place (`main.rs:446`) - Splitting it into its own module would add complexity for no benefit The plan also listed it in the migration table as belonging in `paths.rs`. I followed the plan. ### 4. `#![allow(dead_code)]` at module scope, not per-function I initially scoped the allow to the whole `paths.rs` module because during the mid-migration state, many functions had zero callers temporarily. I kept it at module scope rather than narrowing to individual functions as they became used again, because by the end of Step 2 all 33 functions have at least one real caller and the allow is effectively inert — but narrowing would mean tracking which functions are used vs not in every follow-up step. Module- level allow is set-and-forget. This is slightly looser than ideal. See "Follow-up" below. ### 5. `ast-grep` was the primary migration tool, with manual edits for awkward cases `ast-grep --pattern 'Config::method()'` and `--pattern 'Self::method()'` caught ~90% of the callsites cleanly. The remaining ~10% fell into two categories that `ast-grep` handled poorly: 1. **Calls wrapped in `.display()` or `.to_string_lossy()`.** Some ast-grep patterns matched these, others didn't — the behavior seemed inconsistent. When a pattern found 0 matches but grep showed real matches, I switched to plain text `Edit` for that cluster. 2. **`&Self::X()` reference expressions.** `ast-grep` appeared to not match `Self::X()` when it was the operand of a `&` reference, presumably because the parent node shape was different. Plain text `Edit` handled these without issue. These are tooling workarounds, not architectural concerns. The final tree has no `Config::X` or `Self::X` callers for any of the 33 migrated methods. ### 6. Removed `Config` import from three files that no longer needed it `src/function/mod.rs`, `src/client/common.rs`, `src/client/oauth.rs`, and `src/utils/logs.rs` all had `use crate::config::Config;` (or similar) imports that became unused after every call was migrated. I removed them. This is a minor cleanup but worth doing because: - Clippy flags unused imports as warnings - Leaving them in signals "this file might still need Config" which future migration steps would have to double-check ## Deviations from plan ### 1. `sync_models` is not in Step 2 The plan's Step 2 table listed `sync_models(url, abort)` as a migration target, but grep showed only `sync_models_url(&self) -> String` exists in the code. That's a `&self` method, so it belongs in Step 3 (global-read methods), not Step 2. I skipped it here and will pick it up in Step 3. The Step 2 actual count is 33 methods, not the 34 the plan's table implies. ### 2. Forwarders deleted contiguously, not in a separate sub-step See Key Decision #1. The plan described a two-phase approach ("leave forwarders, migrate callers module-by-module, then remove forwarders"). I compressed this into one pass because the migration was so mechanical there was no value in the intermediate state. ## Verification ### Compilation - `cargo check` — clean, **zero warnings, zero errors** - `cargo clippy` — clean ### Tests - `cargo test` — **63 passed, 0 failed** (same as Step 1 — no new tests were added because Step 2 is a pure code-move with no new behavior to test; the existing test suite verifies nothing regressed) ### Manual smoke test Not applicable — Step 2 is a pure code-move. The path computations are literally the same code at different call sites. If existing tests pass and nothing references Config's static methods anymore, there's nothing to manually verify beyond the compile. ### Callsite audit ``` cargo check 2>&1 | grep "Config::\(config_dir\|local_path\|...\)" ``` Returns zero matches. Every external `Config::method()` callsite for the 33 migrated methods has been converted to `paths::method()`. ## Handoff to next step ### What Step 3 can rely on Step 3 (migrate global-read methods to `AppConfig`) can rely on: - `src/config/paths.rs` exists and holds every static path helper plus `log_config`, `list_*`, `has_*`, and `local_models_override` - Zero `Config::config_dir()`, `Config::cache_path()`, etc. calls remain in the codebase - The `#[allow(dead_code)]` on `paths.rs` at module scope is safe to remove at any time now that all functions have callers - `AppConfig` (from Step 0) is still fully populated and ready to receive method migrations - The bridge from Step 1 (`Config::to_app_config`, `to_request_context`, `from_parts`) is unchanged and still works - `Config` struct has no more static methods except those that were kept because they DO take `&self` (`vault_password_file`, `messages_file`, `sessions_dir`, `session_file`, `rag_file`, `state`, etc.) - Deprecation forwarders are GONE — don't add them back ### What Step 3 should watch for - **`sync_models_url`** was listed in the Step 2 plan table as static but is actually `&self`. It's a Step 3 target (global-read). Pick it up there. - **The Step 3 target list** (from `PHASE-1-IMPLEMENTATION-PLAN.md`): `vault_password_file`, `editor`, `sync_models_url`, `light_theme`, `render_options`, `print_markdown`, `rag_template`, `select_functions`, `select_enabled_functions`, `select_enabled_mcp_servers`. These are all `&self` methods that only read serialized config state. - **The `vault_password_file` field on `AppConfig` is `pub(crate)`, not `pub`.** The accessor method on `AppConfig` will need to encapsulate the same fallback logic that the `Config` method has (see `src/config/mod.rs` — it falls back to `gman::config::Config::local_provider_password_file()`). - **`print_markdown` depends on `render_options`.** When migrating them to `AppConfig`, preserve the dependency chain. - **`select_functions` / `select_enabled_functions` / `select_enabled_mcp_servers` take a `&Role` parameter.** Their new signatures on `AppConfig` will be `&self, role: &Role` — make sure `Role` is importable in the `app_config.rs` module (it currently isn't). - **Strategy for the Step 3 migration:** same as Step 2 — create methods on `AppConfig`, add `#[deprecated]` forwarders on `Config`, migrate callsites with `ast-grep`, delete the forwarders. Should be quicker than Step 2 because the method count is smaller (10 vs 33) and the pattern is now well- established. ### What Step 3 should NOT do - Don't touch `paths.rs` — it's complete. - Don't touch `bridge.rs` — Step 3's migrations will still flow through the bridge's round-trip test correctly. - Don't try to migrate `current_model`, `extract_role`, `sysinfo`, or any of the `set_*` methods — those are "mixed" methods listed in Step 7, not Step 3. - Don't delete `Config` struct fields yet. Step 3 only moves *methods* that read fields; the fields themselves still exist on `Config` (and on `AppConfig`) in parallel until Step 10. ### Files to re-read at the start of Step 3 - `docs/PHASE-1-IMPLEMENTATION-PLAN.md` — Step 3 section (table of 10 global-read methods and their target signatures) - This notes file — specifically the "What Step 3 should watch for" section - `src/config/app_config.rs` — to see the current `AppConfig` shape and decide where to put new methods - The current `&self` methods on `Config` in `src/config/mod.rs` that are being migrated ## Follow-up (not blocking Step 3) ### 1. Narrow or remove `#![allow(dead_code)]` on `paths.rs` At Step 2's end, every function in `paths.rs` has real callers, so the module-level allow could be removed without producing warnings. I left it in because it's harmless and removes the need to add per-function allows during mid-migration states in later steps. Future cleanup pass can tighten this. ### 2. Consider renaming `paths.rs` if its scope grows `log_config`, `list_roles`, `list_rags`, `list_macros`, `has_role`, `has_macro`, and `local_models_override` aren't strictly "paths" but they're close enough that extracting them into a sibling module would be premature abstraction. If Steps 3+ add more non-path helpers to the same module, revisit this. ### 3. The `Config::config_dir` deletion removes one access point for env vars The `config_dir()` function was also the entry point for XDG- compatible config location discovery. Nothing about that changed — it still lives in `paths::config_dir()` — but if Step 4+ needs to reference the config directory from code that doesn't yet import `paths`, the import list will need updating. ## References - Phase 1 plan: `docs/PHASE-1-IMPLEMENTATION-PLAN.md` - Step 1 notes: `docs/implementation/PHASE-1-STEP-1-NOTES.md` - New file: `src/config/paths.rs` - Modified files (module registration + callsite migration): 14 files across `src/config/`, `src/function/`, `src/repl/`, `src/cli/`, `src/main.rs`, `src/utils/`, `src/mcp/`, `src/client/`