185 lines
7.5 KiB
Markdown
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>
|