Files
bbs-md/.planning/phases/01-safety-foundation/01-01-PLAN.md
T

185 lines
7.5 KiB
Markdown

---
phase: 01-safety-foundation
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- Cargo.toml
- src/config.rs
- src/main.rs
autonomous: true
requirements:
- CONF-01
- SHEL-02
must_haves:
truths:
- "App reads vault_path and theme from bbs.toml when the file exists"
- "App uses sensible defaults (vault=./vault/, theme=default) when bbs.toml is missing"
- "App rejects unknown keys in bbs.toml with a BBS-themed error message"
- "App shows a friendly error and exits when vault path does not exist"
- "App accepts --config flag to specify an alternate config path"
- "App detects login shell mode when argv[0] starts with a dash"
- "App strips the leading dash from argv[0] before clap parses arguments"
artifacts:
- path: "src/config.rs"
provides: "Config struct, load_config(), config path resolution, BBS error formatting"
contains: "deny_unknown_fields"
- path: "Cargo.toml"
provides: "Dependencies: toml, serde, clap, signal-hook"
contains: "signal-hook"
key_links:
- from: "src/main.rs"
to: "src/config.rs"
via: "load_config() call before terminal init"
pattern: "load_config"
- from: "src/main.rs"
to: "clap::Parser"
via: "Cli::parse_from with stripped argv[0]"
pattern: "parse_from"
---
<objective>
Add all Phase 1 dependencies to Cargo.toml, implement config loading from bbs.toml with strict TOML parsing and BBS-themed error messages, implement CLI argument parsing with --config flag, and detect login shell mode from argv[0]. Wire the early startup path in main.rs (everything before terminal initialization).
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`.
</objective>
<execution_context>
@/Users/ruohki/.claude/get-shit-done/workflows/execute-plan.md
@/Users/ruohki/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/01-safety-foundation/01-RESEARCH.md
</context>
<tasks>
<task type="auto">
<name>Task 1: Add Phase 1 dependencies and implement config + CLI</name>
<files>Cargo.toml, src/config.rs, src/main.rs</files>
<action>
**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:
1. `Config` struct with `#[derive(Deserialize, Debug)]` and `#[serde(deny_unknown_fields)]`:
- `vault_path: PathBuf` with `#[serde(default = "default_vault_path")]` — defaults to `PathBuf::from("./vault")`
- `theme: String` with `#[serde(default = "default_theme")]` — defaults to `"default".to_string()`
- Implement `Default` for `Config`
2. `ConfigError` enum with variants:
- `ReadError(std::io::Error)` — file exists but can't be read
- `ParseError(toml::de::Error)` — TOML syntax or unknown fields
- `VaultNotFound(PathBuf)` — vault_path directory doesn't exist
3. `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
4. `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"`
5. `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."`
- These print with `eprintln!()` since terminal is not yet in raw mode
6. `Cli` struct 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")]`
7. `detect_login_shell() -> bool`:
- Check `std::env::args_os().next()` — if it starts with '-', return true
8. `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
**src/main.rs** — Replace the hello world with the early startup path:
```rust
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.
</action>
<verify>
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.
</verify>
<done>
`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.
</done>
</task>
</tasks>
<verification>
1. `cargo build` compiles without errors or warnings
2. Running without bbs.toml uses defaults (vault=./vault, theme=default)
3. Running with valid bbs.toml loads specified values
4. Unknown TOML keys produce BBS-themed error output
5. Nonexistent vault_path produces friendly error and exit(1)
6. `--config /path/to/custom.toml` overrides default config location
7. Login shell detection returns true when argv[0] starts with dash
</verification>
<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>
<output>
After completion, create `.planning/phases/01-safety-foundation/01-01-SUMMARY.md`
</output>