Iteration 6: --resume + persistent input history + migration scaffold
Closes out track 2's ADR-0015 backlog. * `--resume` CLI flag (L1a, ADR-0015 §7) opens the most- recently-used project, tracked in <data-root>/last_project. Mutually exclusive with a positional <project-path>; errors cleanly to stderr (above the shell prompt) on missing file or stale recorded path. last_project is rewritten on every successful project open (startup, load, new, save as, import). * Persistent input history (I2-persist, ADR-0015 §12). On project open, the in-memory navigable history is hydrated from the tail of history.log (capped at the in-memory cap). ProjectSwitched gains a `history_entries` payload field; App::seed_history is the entry point. Pipes inside source text round-trip via splitn(3); unknown escape sequences are passed through literally. * Migration framework scaffold (F3, ADR-0015 §9). New persistence::migrations module with MigratorRegistry + migrate_to_latest + ensure_project_yaml_migrated. Empty in v1 (production registry has no migrators); the loader runs through it on every project open and is exercised by tests with a fake v1→v2 migrator. Writes project.yaml.v<N>.bak before any migrator runs; verifies each step bumps the version field. Refreshes docs/requirements.md (A1 / I2 / F3 / E1 / L1a / test baseline) and adds docs/handoff/20260508-handoff-3.md covering both Iter 5 and Iter 6. Total tests: 408 passing, 0 failing, 0 skipped (up from 345 at handoff-2). Clippy clean.
This commit is contained in:
@@ -0,0 +1,429 @@
|
||||
# Session handoff — 2026-05-08 (3)
|
||||
|
||||
Third handover. Continues track 2's ADR-0015 work: this
|
||||
session completed Iterations 5 and 6 (export/import,
|
||||
`--resume`, persistent input-history hydration, migration
|
||||
framework scaffold), closing out the remaining track-2
|
||||
backlog from the previous handoff.
|
||||
|
||||
## State at handoff
|
||||
|
||||
**Branch:** `main`. Working tree dirty (this handoff doc +
|
||||
the iteration changes). The track-2 commits since handoff-2
|
||||
are pending the user's commit approval.
|
||||
|
||||
**Tests:** 408 passing, 0 failing, 0 skipped (up from 345 at
|
||||
the previous handoff). Breakdown:
|
||||
|
||||
```
|
||||
unit (lib) 295 (272 + 23 new)
|
||||
project_lifecycle.rs 0
|
||||
walking_skeleton.rs 5
|
||||
iteration2_persistence.rs 6
|
||||
iteration3_rebuild.rs 9
|
||||
iteration4a_rebuild_command.rs 17
|
||||
iteration4b_lifecycle_commands.rs 27
|
||||
iteration5_export_import.rs 14 (new)
|
||||
iteration6_resume_history.rs 15 (new)
|
||||
---
|
||||
408 total
|
||||
```
|
||||
|
||||
**Clippy:** clean with the nursery lint group enabled.
|
||||
|
||||
**Release build:** ~6.9MB single binary (up from 6.3MB at
|
||||
handoff-2; the increase is the `zip` + `flate2` + `zlib-rs`
|
||||
chain pulled in for export/import).
|
||||
|
||||
## What's implemented (delta vs. handoff-2)
|
||||
|
||||
### Iteration 5 — `export` / `import` (ADR-0015 §11 +
|
||||
### ADR-0007 amendment 1)
|
||||
|
||||
**`export [<path>]` command:**
|
||||
|
||||
- Available in both modes per ADR-0003. Wired through
|
||||
`Action::Export` and a new `spawn_export` task in the
|
||||
runtime that does the zip work on a `spawn_blocking`
|
||||
thread.
|
||||
- Default output: `<data-root>/YYYYMMDD-<projectname>-export-NN.zip`
|
||||
where `NN` is a non-clobbering two-digit sequence found by
|
||||
`archive::next_export_sequence` — caps at 99 same-day
|
||||
exports per project, returns
|
||||
`ExportSequenceExhausted` past that.
|
||||
- `export <path>` overrides the default. Relative paths
|
||||
resolve under the active data root (the user's stated
|
||||
preference: "use the data-dir as the target folder, so the
|
||||
export file sits 'next to' the project folder, not inside
|
||||
it"); absolute paths are used verbatim. Refuses if the
|
||||
final zip path already exists.
|
||||
- **Zip layout:** the project's directory name is preserved
|
||||
as the single top-level folder inside the archive, so
|
||||
`unzip foo.zip` produces `<projectname>/project.yaml` etc.
|
||||
rather than scattering files. This is what makes the
|
||||
round-trip `export → import` pleasant: the recipient
|
||||
doesn't need to know the original name; it lives in the
|
||||
zip's structure.
|
||||
- **Excluded** from the zip: `playground.db`,
|
||||
`history.log`, `.rdbms-playground.lock`, any `*.tmp`
|
||||
files, any `project.yaml.v*.bak` files. `.gitignore` IS
|
||||
included (sensible default for the recipient).
|
||||
|
||||
**`import <zip> [as <target>]` command:**
|
||||
|
||||
- Grammar: separator is the literal ` as ` (space-as-space),
|
||||
so a zip path containing the substring "as" without
|
||||
surrounding spaces is treated as a path, not a syntax
|
||||
marker.
|
||||
- Default target: the zip's single top-level folder
|
||||
(verified by `archive::inspect_zip`). Relative `<target>`
|
||||
resolves under `<data-root>/projects/`; absolute paths
|
||||
used verbatim.
|
||||
- **Collision behaviour:** if the resolved relative target
|
||||
already exists, the basename auto-suffixes `-02`, `-03`, …
|
||||
up to `-99`. This is a deliberate deviation from the §2
|
||||
collision rule (which refuses), recorded as an amendment
|
||||
to ADR-0015 §11. Rationale: round-tripping zips
|
||||
(export → email → import → re-export → re-import) is a
|
||||
normal workflow and forcing `as <target>` for every
|
||||
collision is unnecessary friction. Absolute paths are NOT
|
||||
auto-suffixed — the user's explicit `as <abs-path>` is
|
||||
honored exactly or refused on collision.
|
||||
- After unpack: the runtime opens the new project and runs
|
||||
`rebuild_from_text` to materialize `playground.db` from
|
||||
YAML+CSV. `history.log` starts empty (it was excluded
|
||||
from the zip).
|
||||
- Switches to operating on the new project via the existing
|
||||
`perform_switch` + `SwitchRequest::Import` path, which
|
||||
means the unmodified-temp cleanup machinery from
|
||||
Iteration 4b also applies — the previous fresh-launch
|
||||
temp gets auto-deleted via
|
||||
`safely_delete_temp_project`.
|
||||
|
||||
**Path-traversal protection** in `archive::extract_into`:
|
||||
|
||||
- `entry.enclosed_name()` rejects `..` segments and
|
||||
absolute paths.
|
||||
- The resolved extraction path is re-validated to start
|
||||
with `target_dir` (defence-in-depth).
|
||||
- Top-folder match is enforced (the inspection step
|
||||
recorded the single top-level folder; extraction refuses
|
||||
any entry under a different top folder).
|
||||
|
||||
**Module:** `src/archive.rs` (new, ~480 lines + 11 unit
|
||||
tests). Public API: `default_export_filename`,
|
||||
`next_export_sequence`, `export_project`, `inspect_zip`,
|
||||
`resolve_import_target`, `extract_into`, plus
|
||||
`ZipInspection` and `ArchiveError`. Dep added: `zip = "5"`
|
||||
with `default-features = false, features = ["deflate"]`.
|
||||
|
||||
### Iteration 6 — `--resume` + persistent input history +
|
||||
### migration framework
|
||||
|
||||
**`--resume` CLI flag (L1a, ADR-0015 §7):**
|
||||
|
||||
- Reads `<data-root>/last_project` (a single-line file
|
||||
containing the absolute project path).
|
||||
- Mutually exclusive with a positional `<project-path>`
|
||||
(`ArgsError::ResumeWithPath`).
|
||||
- Errors cleanly via stderr (printed BEFORE the alternate
|
||||
screen is entered, so the message lands directly above
|
||||
the shell prompt) if:
|
||||
- `last_project` is missing → "no previous project
|
||||
recorded under …".
|
||||
- `last_project` points at a path that no longer exists
|
||||
→ "recorded project … no longer exists".
|
||||
- No silent fallback to a fresh temp.
|
||||
- `last_project` is rewritten on every successful project
|
||||
open: startup (resume / positional path / fresh temp),
|
||||
`load`, `new`, `save as`, `import`. Atomic write via
|
||||
temp + rename.
|
||||
- Helpers: `project::read_last_project`,
|
||||
`project::write_last_project`. Both round-trip through
|
||||
disk and handle the missing-data-root case (the runtime's
|
||||
first launch).
|
||||
|
||||
**Persistent input history (I2-persist, ADR-0015 §12):**
|
||||
|
||||
- On project open (initial in `run()` and on every switch in
|
||||
`handle_project_switch`), the in-memory navigable input
|
||||
history is hydrated from the tail of the project's
|
||||
`history.log`, capped at the same 1000-entry in-memory cap.
|
||||
- `App::seed_history(entries: Vec<String>)` is the
|
||||
hydration entry point; `Persistence::read_recent_history`
|
||||
is the loader (calls `history::read_recent_sources`).
|
||||
- The hydration is delivered through
|
||||
`AppEvent::ProjectSwitched { history_entries, .. }` for
|
||||
switch flows (since `App` is owned by `run_loop`); for
|
||||
the startup flow it's called inline.
|
||||
- Up/Down recall jumps to the most-recent seeded entry
|
||||
first, matching the in-session navigation semantics.
|
||||
- Format-tolerant parser: `<ts>|ok|<source>` lines are
|
||||
parsed via `splitn(3, '|')` so pipes inside the source
|
||||
are preserved; unknown escape sequences in the source
|
||||
are passed through literally.
|
||||
|
||||
**Migration framework scaffold (F3, ADR-0015 §9):**
|
||||
|
||||
- New module `src/persistence/migrations.rs`.
|
||||
- `MigratorRegistry` is an ordered list of `MigrateFn`
|
||||
function pointers, indexed by source version.
|
||||
`production()` returns an empty registry (latest_version
|
||||
= 1). New versions register their migrators here.
|
||||
- `migrate_to_latest(body, registry, project_path)`:
|
||||
1. Reads the `version:` field via a tiny `serde_yml`
|
||||
wire type (`VersionOnly { version: u32 }`).
|
||||
2. If `file_version == latest`: returns body unchanged
|
||||
with `migrated_from = None`.
|
||||
3. If `file_version > latest`: errors out
|
||||
(`NewerThanSupported`).
|
||||
4. Otherwise: writes
|
||||
`<project_path>/project.yaml.v<file_version>.bak`,
|
||||
runs each migrator in sequence, and verifies each
|
||||
step bumped the `version:` field (catches forgetful
|
||||
migrators).
|
||||
- `ensure_project_yaml_migrated(project_path, registry)`
|
||||
is the runtime-facing wrapper that pairs migration with
|
||||
the read/write IO.
|
||||
- Wired into `runtime::run()` and
|
||||
`runtime::perform_switch()` so every project open runs
|
||||
through the (currently no-op) migration step before the
|
||||
database opens.
|
||||
- Tests inject a fake v1→v2 migrator to exercise the
|
||||
registry plumbing, the `.bak` write, the
|
||||
forgot-to-bump-version check, the
|
||||
newer-than-supported guard, and a propagated migrator
|
||||
error.
|
||||
|
||||
## ADR / docs updates
|
||||
|
||||
- **ADR-0015 §11** — amended to record the export zip
|
||||
layout (top-level folder = project name) and the
|
||||
import auto-suffix collision behaviour (deviates from
|
||||
§2's refuse-on-collision rule for `save` / `save as`).
|
||||
- **`docs/requirements.md`** — A1 / I2 / F3 / E1 / L1a
|
||||
flipped to `[x]` with implementation notes; test
|
||||
baseline updated to 408 passing.
|
||||
- **`CLAUDE.md`** — not touched this session; the rules
|
||||
are unchanged. The repo-layout map there is slightly
|
||||
out-of-date (no mention of `archive.rs` or
|
||||
`persistence/migrations.rs`) — a quick fix-up is fair
|
||||
game for the next session.
|
||||
|
||||
## Repository layout (delta vs. handoff-2)
|
||||
|
||||
```
|
||||
src/
|
||||
archive.rs — new (Iteration 5)
|
||||
persistence/
|
||||
mod.rs — read_recent_history added
|
||||
migrations.rs — new (Iteration 6 / F3)
|
||||
project/
|
||||
mod.rs — read_last_project +
|
||||
write_last_project added
|
||||
cli.rs — --resume flag + ResumeWithPath
|
||||
error variant
|
||||
app.rs — export/import dispatch in
|
||||
submit(); seed_history;
|
||||
ExportSucceeded/Failed event
|
||||
handlers; ProjectSwitched
|
||||
carries history_entries
|
||||
action.rs — Action::Export, Action::Import
|
||||
event.rs — AppEvent::ExportSucceeded /
|
||||
ExportFailed; ProjectSwitched
|
||||
+ history_entries
|
||||
runtime.rs — spawn_export, do_export,
|
||||
resolve_import_destination,
|
||||
read_history_seed,
|
||||
SwitchRequest::Import,
|
||||
--resume / last_project /
|
||||
migration wiring
|
||||
tests/
|
||||
iteration5_export_import.rs — new (Iteration 5)
|
||||
iteration6_resume_history.rs — new (Iteration 6)
|
||||
```
|
||||
|
||||
## Sharp edges and subtleties (delta vs. handoff-2)
|
||||
|
||||
The previous handoff's sharp edges all still apply. New ones:
|
||||
|
||||
- **`Action::Export` runs on a tokio `spawn_blocking` task,
|
||||
not the db worker.** Export writes the zip directly from
|
||||
disk; auto-save guarantees the project's text sources are
|
||||
current. The `history.log` entry for the `export` command
|
||||
is appended synchronously from the dispatching arm BEFORE
|
||||
the spawn (so the user-issued command lands in history
|
||||
even if the export task itself fails).
|
||||
- **`SwitchRequest::Import` runs `inspect_zip` BEFORE
|
||||
dropping the current project.** A failed inspection (zip
|
||||
not a project, multiple top folders, traversal entry,
|
||||
etc.) leaves the user where they were. The actual
|
||||
extraction also runs before the drop. Only after
|
||||
extraction succeeds do we drop and reopen.
|
||||
- **`ProjectSwitched` is now a 3-field event.** Tests that
|
||||
construct it directly need the extra `history_entries:
|
||||
Vec::new()` field. Iteration-4b had one such test;
|
||||
updated.
|
||||
- **Migration runs inside `perform_switch` AFTER the lock
|
||||
on the new project is acquired but BEFORE the database
|
||||
opens.** Order matters: a migration that mutates
|
||||
`project.yaml` while another process holds the lock
|
||||
would corrupt the file; doing the migration after our
|
||||
own lock is held prevents that.
|
||||
- **`migrate_to_latest` writes the `.bak` BEFORE running
|
||||
any migrator.** If a migrator panics or returns an
|
||||
error mid-chain, the `.bak` is the only intact copy of
|
||||
the original. The runtime currently does not auto-restore
|
||||
on failure — that's part of "future work" once a real
|
||||
migrator lands.
|
||||
- **`--resume` errors print to stderr BEFORE the terminal
|
||||
is set up.** If the user is debugging by reading
|
||||
`--log-file`, the resume error is in the shell, not the
|
||||
log.
|
||||
- **`last_project` write failures are non-fatal** (logged
|
||||
via `tracing::warn`). Rationale: a failed write here
|
||||
surfaces on the *next* `--resume` attempt with a clear
|
||||
message, which is preferable to refusing to launch the
|
||||
app over a stat / chmod hiccup.
|
||||
- **The `zip` crate features are restricted** to
|
||||
`default-features = false, features = ["deflate"]` to
|
||||
hold the binary-size cost down. A future cipher /
|
||||
compression demand can revisit.
|
||||
|
||||
## Pending — proposed next moves (in order)
|
||||
|
||||
Track-2's iteration backlog is now empty; ADR-0015 ships
|
||||
the runtime as designed. The remaining items are the
|
||||
deferred features called out in handoff-2's "Other deferred
|
||||
items" list:
|
||||
|
||||
### 1. Complex WHERE expressions (C5a)
|
||||
|
||||
AND/OR/comparison operators/LIKE in UPDATE/DELETE/show-data
|
||||
filters. The natural progression from DSL fluency into
|
||||
real SQL. Needs a small ADR for the operator subset.
|
||||
|
||||
### 2. Indexes (C3 partial) + EXPLAIN QUERY PLAN (QA1)
|
||||
|
||||
Strong teaching demo. `add index <name> on <T>(<col>)` /
|
||||
`drop index <name>`, plus rendering the `EXPLAIN QUERY PLAN`
|
||||
output as an annotated tree (QA2 covers the tree rendering
|
||||
specifics in its own ADR).
|
||||
|
||||
### 3. Column drops/renames/type changes (B2 / C2 partial)
|
||||
|
||||
The `rebuild_table` primitive already exists (ADR-0013).
|
||||
The grammar additions and metadata updates are
|
||||
straightforward; the work is mostly tests covering the
|
||||
data-preservation invariants.
|
||||
|
||||
### 4. Friendly error layer (H1)
|
||||
|
||||
Translate raw SQLite messages to learner-friendly
|
||||
equivalents. Partial today (FK errors are enriched both
|
||||
ways); full SQL → English translation is the open work.
|
||||
|
||||
### 5. `replay` (U4)
|
||||
|
||||
The `history.log` format is already replay-compatible.
|
||||
`replay <path>` runs commands from a `history.log` or
|
||||
`.commands` file. The framework lands here; the U-series
|
||||
items (snapshot/undo/redo, ADR-0006) follow.
|
||||
|
||||
### 6. CI (TT5)
|
||||
|
||||
Test infrastructure is in place; the GitHub Actions
|
||||
workflow file (or equivalent) is not.
|
||||
|
||||
### 7. Bigger UX work
|
||||
|
||||
V4 session log + Markdown export, S2 indexes in the items
|
||||
list, V1/V2 pretty rendering, H1a strong syntax-help. All
|
||||
have their entries in `docs/requirements.md` and remain
|
||||
deferred behind their respective ADRs.
|
||||
|
||||
## How to take over
|
||||
|
||||
1. Read this file.
|
||||
2. Read `CLAUDE.md` for the working-style rules.
|
||||
3. Read `docs/requirements.md` for granular progress.
|
||||
4. Skim `docs/adr/README.md`; read ADR-0015 in full
|
||||
(especially §11 with the import-collision amendment)
|
||||
if you'll touch the project storage runtime, the
|
||||
archive module, or the migration framework.
|
||||
5. Run `cargo test` to confirm the 408-test green
|
||||
baseline.
|
||||
6. `cargo run --release -- --help` to see the updated CLI
|
||||
banner.
|
||||
|
||||
### End-to-end smoke test
|
||||
|
||||
Verifies export, import, --resume, and persistent history.
|
||||
Same data-dir flag throughout so the test is contained.
|
||||
|
||||
```
|
||||
# Set up: launch under a clean data dir.
|
||||
$ rm -rf /tmp/rdbms-iter5-iter6-smoke
|
||||
$ rdbms-playground --data-dir /tmp/rdbms-iter5-iter6-smoke
|
||||
|
||||
# Inside the app:
|
||||
help -- new commands listed
|
||||
create table Customers with pk id:serial
|
||||
add column Customers: Name (text)
|
||||
insert into Customers ('Alice')
|
||||
insert into Customers ('Bob')
|
||||
save -- name it "MyOrders"
|
||||
export -- writes
|
||||
-- /tmp/rdbms-iter5-iter6-smoke/
|
||||
-- 20260508-MyOrders-export-01.zip
|
||||
quit
|
||||
|
||||
# Verify the zip on disk:
|
||||
$ unzip -l /tmp/rdbms-iter5-iter6-smoke/*.zip
|
||||
# Should show:
|
||||
# MyOrders/project.yaml
|
||||
# MyOrders/data/Customers.csv
|
||||
# MyOrders/.gitignore
|
||||
# and NOT:
|
||||
# MyOrders/playground.db
|
||||
# MyOrders/history.log
|
||||
|
||||
# Re-open via --resume and verify history is hydrated:
|
||||
$ rdbms-playground --data-dir /tmp/rdbms-iter5-iter6-smoke --resume
|
||||
# Up arrow should walk back through the export, save,
|
||||
# inserts, add column, create table — all from the previous
|
||||
# session.
|
||||
|
||||
# Inside the app:
|
||||
import /tmp/rdbms-iter5-iter6-smoke/20260508-MyOrders-export-01.zip
|
||||
-- creates MyOrders-02
|
||||
-- (auto-suffix because
|
||||
-- MyOrders already exists),
|
||||
-- switches to it,
|
||||
-- rebuilds .db from text.
|
||||
show data Customers -- 'Alice' and 'Bob' present.
|
||||
quit
|
||||
|
||||
# Final clean-up:
|
||||
$ rm -rf /tmp/rdbms-iter5-iter6-smoke
|
||||
```
|
||||
|
||||
If anything in that sequence fails, something is wrong.
|
||||
|
||||
### Manual spot-checks worth running
|
||||
|
||||
- `--resume` with a missing `last_project` → stderr message.
|
||||
- `--resume` with a stale recorded path → stderr message.
|
||||
- `--resume <path>` (combined with positional) →
|
||||
`ResumeWithPath` error from arg parsing.
|
||||
- `export` with no current data dir created yet (rare;
|
||||
data root resolution still works).
|
||||
- `import <zip-with-multiple-top-folders>` →
|
||||
`MultipleTopFolders` error in the output panel.
|
||||
- `import <random.zip>` (no project.yaml in it) →
|
||||
`NotAProjectArchive` error.
|
||||
- After the scaffold migration framework: hand-edit a
|
||||
project's `project.yaml` to `version: 99`, restart →
|
||||
`migrate project.yaml` context error in the run-time
|
||||
startup error path. (Or: write a real v1→v2 migrator
|
||||
and watch it execute.)
|
||||
+41
-25
@@ -26,10 +26,10 @@ repo is pushed).
|
||||
|
||||
## Test baseline
|
||||
|
||||
No test suite exists yet — the repo currently contains only
|
||||
docs. The baseline is therefore "0 passing, 0 failing, 0
|
||||
skipped." Subsequent phases establish the suite and measure
|
||||
against it.
|
||||
After Iterations 5 + 6 (export/import + --resume + persistent
|
||||
input history + migration scaffold): **408 passing, 0 failing,
|
||||
0 skipped** (`cargo test`). Clippy clean with the nursery
|
||||
lint group enabled.
|
||||
|
||||
---
|
||||
|
||||
@@ -72,13 +72,12 @@ against it.
|
||||
keys (and for ergonomics in command-driven workflows). Likely
|
||||
followed by Ctrl-W (delete previous word), Ctrl-K (delete to
|
||||
end), Ctrl-U (delete to start). Pending.
|
||||
- [ ] **I2** Persistent navigable input history (project-scoped,
|
||||
with a global rolling history also available).
|
||||
*(Progress: in-memory navigable history is implemented; the
|
||||
on-disk record is `history.log` (Iteration 2). What's still
|
||||
missing for I2 is hydrating the navigable history from
|
||||
`history.log` on project open — Iteration 6. Global rolling
|
||||
history deferred per OOS-6 / N4.)*
|
||||
- [x] **I2** Persistent navigable input history (project-scoped).
|
||||
*(Implemented across Iterations 2 + 6: per-command append to
|
||||
`history.log` (Iter 2); on project open, the in-memory
|
||||
navigable history is hydrated from the tail of
|
||||
`history.log` up to the same in-memory cap (Iter 6). Global
|
||||
rolling history is out of scope per OOS-6 / N4.)*
|
||||
- [ ] **I3** Tab completion for app commands, DSL keywords, table
|
||||
names, column names, and SQL keywords.
|
||||
- [ ] **I4** Syntax highlighting for both the DSL and SQL.
|
||||
@@ -104,10 +103,10 @@ against it.
|
||||
`rebuild`, `export`, `import`, `seed`, `replay`, `undo`,
|
||||
`redo`, `mode`, `help`, `hint`, `quit`.
|
||||
*(Progress: `quit`/`q`, `mode simple|advanced`, `help` (basic
|
||||
listing), `save`, `save as`, `load`, `new`, `rebuild` all
|
||||
implemented (Iteration 4). `export` / `import` land in track
|
||||
2's Iteration 5; `seed` in the seeding iteration; `replay` /
|
||||
`undo` / `redo` in the U-series; `hint` with H2.)*
|
||||
listing), `save`, `save as`, `load`, `new`, `rebuild`,
|
||||
`export`, `import` all implemented (Iterations 4 + 5). `seed`
|
||||
in the seeding iteration; `replay` / `undo` / `redo` in the
|
||||
U-series; `hint` with H2.)*
|
||||
|
||||
## DSL data commands
|
||||
|
||||
@@ -277,8 +276,16 @@ against it.
|
||||
in each new project (Iteration 1). Per ADR-0007 amendment
|
||||
1, `history.log` is NOT in the template — user decides
|
||||
whether to commit it.
|
||||
- [ ] **F3** Migration framework — pending Iteration 6.
|
||||
Scaffold (no migrators yet) is the v1 deliverable.
|
||||
- [x] **F3** Migration framework scaffold (Iteration 6).
|
||||
`MigratorRegistry` + `migrate_to_latest` +
|
||||
`ensure_project_yaml_migrated` are wired into every project
|
||||
open; no migrators registered in v1 (the production
|
||||
registry is empty). The framework is exercised by tests
|
||||
that inject a fake v1→v2 migrator: registry plumbing,
|
||||
`.v<N>.bak` backup, version-bump sanity check, and
|
||||
newer-than-supported / malformed-version errors are all
|
||||
covered. The first real migrator (when v2 ships) is a
|
||||
one-file change.
|
||||
|
||||
## Undo and replay (per ADR-0006)
|
||||
|
||||
@@ -295,9 +302,15 @@ against it.
|
||||
|
||||
## Sharing and export (per ADR-0007)
|
||||
|
||||
- [ ] **E1** `export` produces a zip excluding `playground.db`;
|
||||
default filename `YYYYMMDD-<projectname>-export-NN.zip` with a
|
||||
non-clobbering two-digit sequence.
|
||||
- [x] **E1** `export` produces a zip excluding `playground.db`
|
||||
AND `history.log` (per ADR-0007 amendment 1); default
|
||||
filename `YYYYMMDD-<projectname>-export-NN.zip` with a
|
||||
non-clobbering two-digit sequence under the active data root
|
||||
(Iteration 5). The zip preserves the project's directory
|
||||
name as a single top-level folder. `import <zip> [as <t>]`
|
||||
is the inverse: derive target name from the zip's top
|
||||
folder, auto-suffix `-NN` on collision (ADR-0015 §11
|
||||
amendment), rebuild from text on open.
|
||||
- [ ] **E2** User documentation includes sharing recipes for
|
||||
git, email, and direct file transfer.
|
||||
|
||||
@@ -347,11 +360,14 @@ against it.
|
||||
- [x] **L1** Load a project via a positional CLI argument
|
||||
(Iteration 1). Plus `--data-dir` to override the data root
|
||||
and `--help` / `-h` for the usage banner.
|
||||
- [ ] **L1a** `--resume` CLI flag opens the most recently used
|
||||
project (path tracked in `<data-root>/last_project`). Errors
|
||||
cleanly if no previous project exists or the recorded path is
|
||||
gone; mutually exclusive with a positional path argument
|
||||
(ADR-0015 §7). Pending Iteration 6.
|
||||
- [x] **L1a** `--resume` CLI flag opens the most recently used
|
||||
project (path tracked in `<data-root>/last_project`).
|
||||
Iteration 6: errors cleanly with a stderr banner above the
|
||||
shell prompt if no previous project is recorded or the
|
||||
recorded path is gone — no silent fallback; mutually
|
||||
exclusive with a positional path argument (ADR-0015 §7).
|
||||
`last_project` is rewritten on every successful project
|
||||
open (startup, load, new, save as, import).
|
||||
- [~] **L2** Submit a command alongside project load — deferred,
|
||||
not v1.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user