7.5 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 01-safety-foundation | 01 | execute | 1 |
|
true |
|
|
Purpose: Config and CLI must work before the terminal enters raw mode, so errors can be printed normally. Login shell detection determines whether 'q' to quit is suppressed later.
Output: src/config.rs with Config struct and load_config(), updated Cargo.toml with all Phase 1 deps, and early startup wiring in src/main.rs.
<execution_context> @/Users/ruohki/.claude/get-shit-done/workflows/execute-plan.md @/Users/ruohki/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/01-safety-foundation/01-RESEARCH.md Task 1: Add Phase 1 dependencies and implement config + CLI Cargo.toml, src/config.rs, src/main.rs **Cargo.toml** — Add all Phase 1 dependencies alongside the existing `ratatui = "0.30.0"`: ```toml signal-hook = "0.4.3" toml = "1.0.3" serde = { version = "1.0", features = ["derive"] } clap = { version = "4.5", features = ["derive"] } ```src/config.rs — Create with:
-
Configstruct with#[derive(Deserialize, Debug)]and#[serde(deny_unknown_fields)]:vault_path: PathBufwith#[serde(default = "default_vault_path")]— defaults toPathBuf::from("./vault")theme: Stringwith#[serde(default = "default_theme")]— defaults to"default".to_string()- Implement
DefaultforConfig
-
ConfigErrorenum with variants:ReadError(std::io::Error)— file exists but can't be readParseError(toml::de::Error)— TOML syntax or unknown fieldsVaultNotFound(PathBuf)— vault_path directory doesn't exist
-
load_config(path: &std::path::Path) -> Result<Config, ConfigError>:- If file doesn't exist, return
Ok(Config::default()) - Read file, parse with
toml::from_str() - Validate vault_path exists as a directory (resolve relative paths against the config file's parent directory, NOT the cwd — this is important because the config lives next to the binary)
- Return
Err(ConfigError::VaultNotFound(...))if vault dir missing
- If file doesn't exist, return
-
resolve_config_path(cli_config: Option<&std::path::Path>) -> PathBuf:- If CLI provided a path, use it
- Otherwise, use
std::env::current_exe().parent() / "bbs.toml"
-
print_config_error(err: &ConfigError):- Format BBS-themed error messages to stderr. Examples:
- ParseError:
"SYSTEM ERROR: Configuration file corrupted. SysOp intervention required.\nDetail: {toml error message}" - VaultNotFound:
"SYSTEM ERROR: Vault directory not found at '{path}'. SysOp must verify vault_path in bbs.toml." - ReadError:
"SYSTEM ERROR: Cannot read configuration file. Check file permissions."
- ParseError:
- These print with
eprintln!()since terminal is not yet in raw mode
- Format BBS-themed error messages to stderr. Examples:
-
Clistruct with clap derive:#[derive(clap::Parser, Debug)]#[command(name = "bbs-md", about = "BBS-style markdown vault reader")]config: Option<PathBuf>field with#[arg(long = "config", short = 'c', value_name = "FILE")]
-
detect_login_shell() -> bool:- Check
std::env::args_os().next()— if it starts with '-', return true
- Check
-
parse_cli() -> Cli:- Collect
std::env::args_os(), strip leading dash from first element if present - Call
Cli::parse_from(args)with the cleaned args
- Collect
src/main.rs — Replace the hello world with the early startup path:
mod config;
fn main() {
// 1. Detect login shell BEFORE stripping argv[0]
let is_login_shell = config::detect_login_shell();
// 2. Parse CLI (strips dash from argv[0] internally)
let cli = config::parse_cli();
// 3. Resolve config path and load config
let config_path = config::resolve_config_path(cli.config.as_deref());
let app_config = match config::load_config(&config_path) {
Ok(c) => c,
Err(e) => {
config::print_config_error(&e);
std::process::exit(1);
}
};
// Phase 1 placeholder: print loaded config for verification
// This will be replaced by terminal init + event loop in Plan 03
println!("Config loaded: {:?}", app_config);
println!("Login shell: {}", is_login_shell);
}
Declare mod config; in main.rs. The placeholder println! lines will be replaced in Plan 03 when the event loop is wired.
Run cargo build from the project root — must compile without errors.
Run cargo run — should print "Config loaded: Config { vault_path: ... }" with defaults (since no bbs.toml exists yet).
Create a test bbs.toml next to the binary with vault_path = "./vault" and theme = "retro", create a ./vault directory, run the binary — config should load successfully.
Create a bbs.toml with an unknown key like bogus = true — app should print BBS-themed error and exit with code 1.
cargo build succeeds. Config loads from bbs.toml with strict parsing, defaults work when file is missing, unknown keys produce BBS-themed errors, missing vault directory produces a friendly error, --config flag overrides config path, and login shell detection works.
<success_criteria>
- Config struct deserializes bbs.toml with deny_unknown_fields
- Missing config file gracefully falls back to defaults
- All error messages use BBS-themed tone
- CLI parsing works with and without login shell dash prefix
- All Phase 1 dependencies are in Cargo.toml </success_criteria>