feat: ADR-0006 §8 steps 1-2 — --no-undo flag + snapshot ring module
Step 1 (Cargo + CLI): - add the `backup` feature to rusqlite (online backup API) - `--no-undo` flag (test-first) + help-banner entry Step 2 (snapshot store, src/undo.rs): - SnapshotStore: a persisted undo ring + redo stack under <project>/.snapshots/ (index.yaml + per-snapshot payload dirs) - hybrid whole-project snapshot: db via backup API + project.yaml / data/*.csv copied as files; restore is text-first, db-last (ADR-0015 §6 commit-db-last) - stage/finalize/discard, undo/redo (each snapshots current to keep the inverse possible), N=50 eviction, redo-cleared-on-new-work, orphan/staging cleanup, monotonic ids - 12 Tier-1 tests; adds a crate-visible persistence::utc_iso8601_now No worker wiring yet (step 3). 1674 passed / 0 failed / 1 ignored; clippy clean.
This commit is contained in:
+33
@@ -29,6 +29,12 @@ pub struct Args {
|
||||
/// `--help` / `-h`: print usage to stdout and exit. The
|
||||
/// runtime checks this flag before doing any other work.
|
||||
pub help: bool,
|
||||
/// `--no-undo`: disable the auto-snapshot / undo machinery for
|
||||
/// this run (ADR-0006 Amendment 1). When set, no snapshots are
|
||||
/// taken — zero per-command overhead — and `undo` / `redo`
|
||||
/// report that undo is turned off. The escape hatch for small
|
||||
/// hardware where per-command snapshotting is too heavy.
|
||||
pub no_undo: bool,
|
||||
}
|
||||
|
||||
/// Usage banner printed by `--help`.
|
||||
@@ -109,6 +115,7 @@ impl Args {
|
||||
let mut project_path: Option<PathBuf> = None;
|
||||
let mut resume = false;
|
||||
let mut help = false;
|
||||
let mut no_undo = false;
|
||||
let mut iter = iter.into_iter().map(Into::into);
|
||||
while let Some(arg) = iter.next() {
|
||||
match arg.as_str() {
|
||||
@@ -118,6 +125,9 @@ impl Args {
|
||||
"--resume" => {
|
||||
resume = true;
|
||||
}
|
||||
"--no-undo" => {
|
||||
no_undo = true;
|
||||
}
|
||||
"--theme" => {
|
||||
let value = iter.next().ok_or(ArgsError::MissingValue("theme"))?;
|
||||
theme = match value.as_str() {
|
||||
@@ -164,6 +174,7 @@ impl Args {
|
||||
project_path,
|
||||
resume,
|
||||
help,
|
||||
no_undo,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -300,6 +311,28 @@ mod tests {
|
||||
assert!(matches!(err, ArgsError::ResumeWithPath), "got: {err:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_undo_flag_parses() {
|
||||
let args = Args::parse(["--no-undo"]).unwrap();
|
||||
assert!(args.no_undo);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_undo_defaults_off() {
|
||||
let args = Args::parse(std::iter::empty::<&str>()).unwrap();
|
||||
assert!(!args.no_undo, "undo is enabled unless --no-undo is given");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_undo_coexists_with_positional_path() {
|
||||
let args = Args::parse(["--no-undo", "/home/me/MyProject"]).unwrap();
|
||||
assert!(args.no_undo);
|
||||
assert_eq!(
|
||||
args.project_path.as_deref(),
|
||||
Some(std::path::Path::new("/home/me/MyProject"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_double_dash_flag_errors_even_with_positional() {
|
||||
// Make sure the path-vs-flag distinction is robust:
|
||||
|
||||
Reference in New Issue
Block a user