docs(01-safety-foundation): create phase plan
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
---
|
||||
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>
|
||||
Reference in New Issue
Block a user