@martintrojer/mu 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -494,7 +494,7 @@ Each module is concrete and consumed today.
494
494
 
495
495
  | Module | Responsibility |
496
496
  | --------------------- | ----------------------------------------------------------------------------------------- |
497
- | `src/db.ts` | SQLite (better-sqlite3) connection, WAL mode, schema (14 tables + 3 views, **schema v7** — v5 surrogate-INTEGER-PK substrate, plus v6's 5 additive `archive_*` tables, minus v7's drop of `approvals`), default paths, `resolveWorkstreamId` (the SDK boundary's first leg). `openDb` refuses pre-v5 DBs loudly; v5+ DBs are brought to the current idempotent schema shape by `applySchema` (v6 additive tables, v7's `DROP TABLE IF EXISTS approvals`). |
497
+ | `src/db.ts` | SQLite (better-sqlite3) connection, WAL mode, schema (16 tables + 3 views, **schema v8** — v5 surrogate-INTEGER-PK substrate, v6's 5 additive `archive_*` tables, v7's drop of `approvals`, v8's additive `machine_identity` + `workstream_sync` sync substrate), default paths, `resolveWorkstreamId` (the SDK boundary's first leg). `openDb` refuses pre-v5 DBs loudly; v5+ DBs are brought to the current idempotent schema shape by `applySchema` (including v7's `DROP TABLE IF EXISTS approvals`) and `openDb` seeds `machine_identity` on open. |
498
498
  | `src/tmux.ts` | Single tmux executor wrapper, send protocol (bracketed-paste), pane validation |
499
499
  | `src/detect.ts` | Pi-only status detector (`busy` / `needs_input` / `idle` / `done`) |
500
500
  | `src/reconcile.ts` | Ghost prune + status detect + orphan surface; "reality wins" |
@@ -506,24 +506,26 @@ Each module is concrete and consumed today.
506
506
  | `src/tracks.ts` | Parallel-tracks union-find with diamond merge |
507
507
  | `src/staleness.ts` | Shared workspace staleness threshold (`WORKSPACE_STALE_THRESHOLD = 10`) and pure `isWorkspaceStale` predicate consumed by static state, the TUI Workspaces card, and dispatch-time warn/refuse checks. |
508
508
  | `src/workstream.ts` | ensureWorkstream / list / summarize / destroy / export (thin wrapper around the bucket renderer) |
509
- | `src/exporting.ts` | Unified bucket renderer for `mu workstream export` and `mu archive export`: per-task markdown + manifest.json (`bucketVersion: 2`); idempotent via per-file sha256; deleted-task preservation banner; refuses pre-0.3 single-source layouts |
510
- | `src/importing.ts` | Inverse of `src/exporting.ts`: parses a v0.3 bucket directory and rebuilds every source-ws as live tasks + edges + notes. Markdown-only (never reads .db); per-source-ws transactional; refuses silent merges into existing workstreams |
511
- | `src/archives.ts` | Archive SDK hub: re-exports the concrete `src/archives/` cluster so external imports keep using `./archives.js`; no implementation logic. |
512
- | `src/archives/*.ts` | Cohesive cluster for cross-workstream **archives** — feature complete (SDK + CLI verbs: `mu archive create / list / show / add / remove / delete`, plus `search` and `export` via the unified bucket renderer): `core.ts` (label validation, row types, typed archive errors, id resolution/summarise helpers), `query.ts` (`createArchive`, `listArchives`, `getArchive`, `listArchivedTasks`, `searchArchives`), `addremove.ts` (`addToArchive` idempotent at `(archive, source_workstream)`, `removeFromArchive`), `delete.ts` (`deleteArchive`). Backed by the v6 `archives` + `archived_tasks` + `archived_edges` + `archived_notes` + `archived_events` tables; archives outlive workstreams (TEXT `source_workstream` columns, no FK). Cluster files import neighbours/root substrate modules directly, never the `src/archives.ts` hub. |
509
+ | `src/exporting.ts` | Unified bucket renderer for `mu workstream export` and `mu archive export`: per-task markdown + manifest.json (`bucketVersion: 2`); idempotent via per-file sha256; deleted-task preservation banner; refuses pre-0.3 single-source layouts. Buckets are read-only artifacts for humans / git / docs, not a DB round-trip substrate. |
510
+ | `src/db-sync.ts` | Whole-machine DB sync SDK: `exportDb` (`VACUUM INTO` + manifest), `importDb` (per-workstream drift plan over `machine_identity` + `workstream_sync`; dry-run by default; `--force-source` parks divergence sidecars), manifest/schema validation, workstream copy/replace helpers, typed db-sync errors. |
511
+ | `src/db-sync-replay.ts` | Manual replay planner/applier for divergence sidecars parked by `mu db import --force-source`: selects missing tasks/notes/eligible edges, refuses `local_id` collisions with diverged content, dry-run by default. Re-exported by `src/db-sync.ts` for SDK callers. |
512
+ | `src/archives.ts` | Archive SDK hub: re-exports the concrete `src/archives/` cluster, including restore, so external imports keep using `./archives.js`; no implementation logic. |
513
+ | `src/archives/*.ts` | Cohesive cluster for cross-workstream **archives** — feature complete (SDK + CLI verbs: `mu archive create / list / show / add / restore / remove / delete`, plus `search` and read-only `export` via the unified bucket renderer): `core.ts` (label validation, row types, typed archive errors, id resolution/summarise helpers), `query.ts` (`createArchive`, `listArchives`, `getArchive`, `listArchivedTasks`, `searchArchives`), `addremove.ts` (`addToArchive` idempotent at `(archive, source_workstream)`, `removeFromArchive`), `restore.ts` (`restoreArchive` lossless un-archive into a fresh workstream), `delete.ts` (`deleteArchive`). Backed by the v6 `archives` + `archived_tasks` + `archived_edges` + `archived_notes` + `archived_events` tables; archives outlive workstreams (TEXT `source_workstream` columns, no FK). Cluster files import neighbours/root substrate modules directly, never the `src/archives.ts` hub. |
514
+ | `src/archives/restore.ts` | Lossless un-archive implementation: validates `--source` when an archive has multiple source workstreams, refuses `--as` collisions through workstream creation, snapshots before writing, copies archived tasks/edges/notes directly from `archived_*` rows, and emits an archive-restore event. Does not restore agents, workspace paths, or the live `agent_logs` stream. |
513
515
  | `src/logs.ts` | `agent_logs` SDK: appendLog / listLogs / latestSeq / emitEvent |
514
516
  | `src/vcs.ts` | VCS SDK hub: re-exports the concrete `src/vcs/` cluster so external imports keep using `./vcs.js`; no implementation logic. |
515
517
  | `src/vcs/*.ts` | Cohesive cluster of VCS backends: `types.ts` (`VcsBackend` interface, result shapes, typed workspace errors, show-output cap), `helpers.ts` (exec/probe/run/show/commit-summary parsing helpers), `git.ts`, `jj.ts`, `sl.ts`, and `none.ts` (one concrete backend per file), `index.ts` (detection precedence dispatcher: `jj root` → `sl root` → `git rev-parse --show-toplevel` → none; `backendByName`). Backend methods cover `commitsBehind(workspacePath, ref)` for staleness (no auto-fetch; pure observation), `recentCommits(projectRoot, limit)` + `showCommit(projectRoot, sha)` for the TUI Commits card/popup, and `isClean(workspacePath)` for `closeAgent`'s clean-workspace auto-free path. Cluster files import neighbours/root substrate modules directly, never the `src/vcs.ts` hub. |
516
518
  | `src/workspace.ts` | Workspace SDK hub: re-exports the concrete `src/workspace/` cluster so external imports keep using `./workspace.js`; no implementation logic. |
517
519
  | `src/workspace/*.ts` | Cohesive cluster for per-agent VCS workspaces (registry layer on top of `vcs.ts`): `core.ts` (row shapes, path helpers, typed workspace errors), `crud.ts` (create/get/list/free/refresh/commits/clean checks), `decorate.ts` (staleness + dirty decoration), `orphans.ts` (per-workstream and all-workstream orphan-dir detection), `recreate.ts` (free+create between-wave verb). Cluster files import neighbours/root substrate modules directly, never the `src/workspace.ts` hub. |
518
520
  | `src/snapshots.ts` | Snapshot SDK hub: re-exports the concrete `src/snapshots/` cluster so external imports keep using `./snapshots.js`; no implementation logic. |
519
- | `src/snapshots/*.ts` | Cohesive cluster for whole-DB snapshots (`VACUUM INTO`): `core.ts` (row shapes, typed snapshot/prune errors, GC env readers, paths, size/version helpers), `capture.ts` (capture/list/auto-GC), `restore.ts` (`mu undo` restore file-swap), `prune.ts` (manual prune/delete cleanup verbs). The `snapshots` table is schema v4 (carried forward unchanged through v5/v6/v7). Cluster files import neighbours/root substrate modules directly, never the `src/snapshots.ts` hub. |
521
+ | `src/snapshots/*.ts` | Cohesive cluster for whole-DB snapshots (`VACUUM INTO`): `core.ts` (row shapes, typed snapshot/prune errors, GC env readers, paths, size/version helpers), `capture.ts` (capture/list/auto-GC), `restore.ts` (`mu undo` restore file-swap), `prune.ts` (manual prune/delete cleanup verbs). The `snapshots` table is schema v4 (carried forward unchanged through v5/v6/v7/v8). Cluster files import neighbours/root substrate modules directly, never the `src/snapshots.ts` hub. |
520
522
  | `src/output.ts` | NextStep type + `printNextSteps` + `errorNextSteps` plumbing for self-documenting output |
521
523
  | `src/state.ts` | SDK seam for the `mu state` verb. `loadWorkstreamSnapshotFast(db, ws, opts?)` is the pure-SQL tier used by the TUI's 1s fast tick (tracks, task slices, workspace registry rows, workspace orphans, recent events; subprocess fields empty). `loadWorkstreamSnapshotSlow(db, ws, opts?)` is the subprocess tier (tmux-derived `view`, workspace dirty flags, recent project commits/backend, Doctor summary). `mergeSnapshotFastSlow` overlays the last slow result onto each fast result, and `loadWorkstreamSnapshot(db, ws, opts?)` stays as a back-compat wrapper that composes both tiers for static/non-TUI callers. Opt-in flags: `withDirty` (slow-tier dirty flag), `withDoctor` (Doctor summary), `withRecentCommits` (Commits card/popup), `withAllTasks` (legacy/full-snapshot all-task list; the TUI all-tasks popup can read SQLite directly while open). Plus pure derivation helpers: `agentStatusHistogram(agents)`, `summarizeOwnedTasks(owned)`, `roiBucket(impact, effortDays)`. |
522
524
  | `src/doctor-summary.ts` | TUI-friendly slice of `mu doctor`'s checks. `loadDoctorSummary(db, snapshot)` returns a `DoctorSummary` (`{ checks: DoctorCheck[], problemCount }`) using only synchronous DB pragmas + COUNT-shape SELECTs and snapshot-derived counts (ghosts / orphan panes / orphan workspace dirs) — cheap enough for the per-tick poll-loop the TUI's slot-9 Doctor card runs on. `loadDoctorChecks(db, snapshot)` is a thin wrapper that returns the full check array (OK + warn + fail) for the slot-9 Doctor popup, which renders every row rather than just the non-OK subset. Also home to the per-check remediation helpers `yankCommandForCheck(check)` (informational SELECT-shape verb to yank for the focused row, with a `# ...` comment fallback for schema-shape checks) and `remediationParagraph(check)` (multi-line prose explaining the failure shape) — both pure, both re-exported from `src/index.ts`, both consumed by the slot-9 popup's drill view but living next to `DoctorCheck` so adding a new check is a single touchpoint. The textual `mu doctor` verb (`src/cli/doctor.ts`) keeps its own renderer; this is the data seam consumed by the dashboard. |
523
525
  | `src/cli.ts` | commander entry; `buildProgram()` (re-exports `format`/`handle` symbols for back-compat with existing import sites). |
524
- | `src/cli/*.ts` | one file per verb-namespace; thin wrappers over the SDK; `--json` rendering for every read verb. Currently: `workstream.ts`, `agents.ts`, `tasks.ts`, `workspace.ts`, `log.ts`, `archive.ts`, `state.ts` (canonical static state card + explicit `--tui` back-compat dispatch; bare `mu` TTY routing lives in `src/cli.ts` so it can inspect the root argv/TTY seam), `tui-launch-focus.ts` (pure shared initial-tab focus ladder for bare `mu` and `mu state --tui`: `$MU_SESSION`, tmux session, cwd inside workspace, cwd at VCS-derived project root with latest-activity tie-break, tab 0), `snapshot.ts`, `sql.ts`, `doctor.ts`. Two non-verb cluster-mates carry the rendering + error-handling primitives that every verb wrapper imports: `format.ts` (table renderers, status colourers, `truncate`/`relTime`) and `handle.ts` (typed-error exit-code map + the `handle()` wrapper). Imports flow cluster → root (never the other way). |
526
+ | `src/cli/db.ts` | Thin commander/renderer for `mu db export / import / replay`: summary tables, dry-run vs apply Next steps, `--only-ws` repeated-or-comma parsing, and JSON envelopes over the `src/db-sync.ts` SDK. |
527
+ | `src/cli/*.ts` | one file per verb-namespace; thin wrappers over the SDK; `--json` rendering for every read verb. Currently: `workstream.ts`, `agents.ts`, `tasks.ts`, `workspace.ts`, `log.ts`, `archive.ts`, `db.ts` (whole-machine sync), `state.ts` (canonical static state card + explicit `--tui` back-compat dispatch; bare `mu` TTY routing lives in `src/cli.ts` so it can inspect the root argv/TTY seam), `tui-launch-focus.ts` (pure shared initial-tab focus ladder for bare `mu` and `mu state --tui`: `$MU_SESSION`, tmux session, cwd inside workspace, cwd at VCS-derived project root with latest-activity tie-break, tab 0), `snapshot.ts`, `sql.ts`, `doctor.ts`. Two non-verb cluster-mates carry the rendering + error-handling primitives that every verb wrapper imports: `format.ts` (table renderers, status colourers, `truncate`/`relTime`) and `handle.ts` (typed-error → exit-code map + the `handle()` wrapper). Imports flow cluster → root (never the other way). |
525
528
  | `src/cli/tui/*.tsx` | Cohesive cluster of the interactive ink-based TUI (`mu state --tui`). Lazy-imported by `src/cli/state.ts` so non-TUI verbs avoid the ink/react cost. Per-file: `index.ts` (runTui entrypoint; writes the alt-screen enter/exit sequences from `escapes.ts` around the ink render and enables/disables mouse mode in the same finally-guarded lifecycle), `escapes.ts` (pure ANSI escape constants `ALT_SCREEN_ENTER`/`ALT_SCREEN_EXIT` plus SGR mouse-mode enter/exit bytes — no ink/react imports so unit tests can assert exact bytes without booting a renderer), `mouse.ts` (tiny vendored SGR mouse layer: enable/disable helpers, stdin parser for `ESC[<button;x;y;M/m`, double-click detector, and `useMouse()` hook), `app.tsx` (root `<App>` with popup state machine + global keymap dispatch + footer + tick state + active-workstream-tab state per feat_tui_multi_workstream), `state.ts` (poll-loop hook `useDashboardSnapshot` split into a fast SQL-only interval controlled by `tickMs` and a hardcoded `SLOW_TICK_MS = 10_000` subprocess interval; cached slow fields are merged into every fast render, `r`/F5 triggers both intervals immediately, and workstream switches clear the slow cache then eager-fetch the new workstream; plus pure `snapshotKey`/`snapshotKeyString` re-render guard so the hook returns the SAME `data` reference across no-op ticks; `lastTickMs` lives in its own useState so its tick-rate display can update without dragging the cards along; plus `clampTick`/`fasterTick`/`slowerTick` constants), `keys.ts` (pure `dispatchGlobalKey` + `dispatchPopupKey` keymap dispatchers), `yank.ts` (clipboard probe + write: pbcopy/wl-copy/xclip/xsel/clip.exe + OSC-52 fallback), `list-row.tsx` (`<ListRow>` — the centralised non-selected row primitive every popup/card consumes per feat_centralize_list_row_render; owns four invariants in one place: outer `<Box width={contentWidth}>` pin, canonical `COL_GUTTER`-spaced cells, `wrap="truncate"` on the outer `<Text>`, and selected→`<CursorRow>` delegation. Per-cell colours pass in declaratively as a `colors` array sibling of `COLUMN_SPECS`. Replaces 18 near-identical hand-rolled row JSX blocks across `popups/*.tsx`+`cards/*.tsx`; the test/tui-card-render-width.test.ts invariant is now "every renderRow consumer routes through ListRow OR CursorRow" — enforced by static-source assertions so a future popup author can't drift the gutter, forget the width pin, or skip wrap=truncate), `titled-box.tsx` (rounded-border primitive with section header inset into the top border; optional `bottomLabel` prop insets a `+M more · Shift+N` truncation hint into the BOTTOM border line per feat_card_footer_inset, suppressing the inner Box's bottom edge — the geometry is shared with the top-border path via the pure `computeBorderRowDashes` helper), `layout.ts` (pure responsive-dashboard helpers: breakpoint-driven pair-aware card columns plus per-card row-budget allocation with min/max/chrome config; columns use slot-stable ordering, slot 0 trails, and the 2-column layout splits stream cards as bottom trailers to keep the all-cards view balanced), `columns.ts` (column-aligned row layout with protect/clip clipping policy; exposes `contentWidthFromCols(cols)` + `termColsForLayout()` helpers — every card/popup feeds the result as `layoutColumns(rows, specs, contentWidth)` so clip cells actually clip instead of overflowing the row to a second line per bug_tui_long_lines_overflow), `help.tsx` (? keymap overlay), `cards/{agents,tracks,ready,log,workspaces,inprogress,blocked,recent,commits,doctor}.tsx` + `cards/_placeholder.tsx` (`<CardPlaceholder>` — shared loading/empty body wrapper invoked as a function so the test walker still sees the underlying TitledBox/PaddedRows; collapses 20 near-identical 10-line `<TitledBox><PaddedRows><Text dimColor>...</Text></PaddedRows></TitledBox>` blocks across the 10 cards per review_tui_card_loading_empty_boilerplate) (10 dashboard glance cards; slot 0 is Commits, slot 5 promoted by feat_card_5_workspaces, slot 6 by feat_card_6_inprogress, slot 7 by feat_card_7_blocked, slot 8 is Recent, slot 9 by feat_card_9_doctor; DAG and all-tasks are keybind-only popup conventions, not cards), `popups/{dag,all-tasks,agents,tracks,ready,log,workspaces,inprogress,blocked,recent,commits,doctor}.tsx` (12 fullscreen drill-down popups; `dag.tsx` is keybind-only on `g` and renders the active workstream's full task-DAG forest; `all-tasks.tsx` is keybind-only on `t`, renders every task as a sortable/filterable list via the shared `use-status-filter.tsx`, and drills into `TaskDetailDrill`; `commits.tsx` is slot-0 via Shift+0 and drills into backend show output; slot-5 popup promoted by feat_popup_5_workspaces, slot-6 by feat_popup_6_inprogress, slot-7 by feat_popup_7_blocked, slot-8 by feat_popup_8_recent (yanks `mu task open <id>`); slot-9 by feat_popup_9_doctor (the Doctor drill is a small ad-hoc detail view via `DrillScrollView`, NOT TaskDetailDrill — rows are doctor checks rather than tasks). All reserved numeric popup slots are now filled), `popups/drill.tsx` (`DrillScrollView` — the scroll-list primitive every popup-drill body shares; re-exports `clampScrollTop` from `popups/scroll.ts` for back-compat), `popups/scroll.ts` (pure `applyCursor` + `applyScroll` + `clampScrollTop` + `isNavAction` — the centralised navigation primitive every popup + drill consumes per feat_centralize_scroll_navigation; replaces ~60 near-duplicate `case "moveDown"/"moveUp"/"jumpTop"/"jumpBottom"/"pageUp"/"pageDown"` switch arms across 9 popups so j/k/g/G/Ctrl-D/U/PgUp/PgDn behave identically in every list-mode AND every drill-mode; pure TS with no ink/react imports, covered by test/tui-scroll.test.ts), `popups/viewport.ts` (pure `popupViewport(rows, chromeOverride?)` + `POPUP_CHROME_ROWS` + `POPUP_VIEWPORT_FLOOR` — each popup reads `useStdout().rows` at render time and calls `popupViewport` to size the body slice; replaces the prior hardcoded `const VIEWPORT = 20` per bug_tui_popup_data_doesnt_fill so the row data inside a `flexGrow={1}` popup Shell actually fills the pane), `popups/task-detail.tsx` (`TaskDetailDrill` — the read-only task-notes leaf consumed by the Tasks popup drill AND by the Tracks-popup `drill → task-detail` chain; future task-list popups under feat_more_cards_umbrella plug in unchanged), `use-popup-filter.tsx` (shared `/` filter state-machine: pure `popupFilterReducer` + `usePopupFilter` hook + `applyFilter<T>(items, query, blobOf)` + `<FilterPrompt>`. Every list popup wires the hook in ~5 LOC and gets the full UX — incremental edit, Enter commit, Esc cancel, status-bar mode flip, no-matches fallback — for free; new card popups under feat_more_cards_umbrella MUST consume it rather than re-implement), `use-status-filter.tsx` (shared task-status toggle hook + `<StatusFilterStrip>` for task-list popups; default all-on, popup-local, mnemonic o/i/c/r/d toggles OPEN / IN_PROGRESS / CLOSED / REJECTED / DEFERRED, no persistence), `use-notes-drill.ts` (shared notes-drill memo — returns the `renderNotes(...)` body string for the focused task only when the popup is in drill mode; per task review_tui_task_popups_duplicated_template the byte-identical useMemo block deduped from all five task-list popups (Tasks/ready, In-progress, Blocked, Recent, All-tasks) so the next task-list popup is a one-line drop-in and the SQL+tick semantics stay in lockstep), `tab-strip.tsx` (`<TabStrip>` — multi-workstream tab switcher rendered above the cards when `<App>` is launched with N≥2 workstreams; bold/cyan + `▸ ` marker for the active tab, dim names + ` · ` separators for the rest, plus a `(Tab / Shift-Tab)` affordance hint; renders nothing for N=1 so the single-ws frame is byte-identical to the pre-multi-ws build; pure presentational — the active index lives in `<App>`, `Tab`/`Shift-Tab` keys come through `dispatchGlobalKey`'s `nextTab`/`prevTab` actions). **The ONLY place ink/react are imported** — enforced by ROADMAP pledge. |
526
- | `src/cli/*.ts` | one file per verb-namespace; thin wrappers over the SDK; `--json` rendering for every read verb. Currently: `workstream.ts`, `agents.ts`, `tasks.ts`, `workspace.ts`, `log.ts`, `archive.ts`, `state.ts` (canonical static state card + explicit `--tui` back-compat dispatch; bare `mu` TTY routing lives in `src/cli.ts` so it can inspect the root argv/TTY seam), `snapshot.ts`, `sql.ts`, `doctor.ts`. Two non-verb cluster-mates carry the rendering + error-handling primitives that every verb wrapper imports: `format.ts` (table renderers, status colourers, `truncate`/`relTime`) and `handle.ts` (typed-error → exit-code map + the `handle()` wrapper). Imports flow cluster → root (never the other way). |
527
529
  | `src/cli/tasks/*.ts` | sub-cluster of the `mu task` namespace; `tasks.ts` at the root re-exports only what callers outside the cluster import (`wireTaskCommands`, `cmdMyNext`/`cmdMyTasks`, `unescapeNoteText`). One file per concern: `queries.ts` (list/next/owned-by + the `cmdMyTasks` / `cmdMyNext` helpers that back `mu me tasks` / `mu me next`), `lifecycle.ts` (close/open/reject/defer + cascade preview), `edit.ts` (add/show/notes/note/update + helpers), `edges.ts` (block/unblock/reparent/delete), `claim.ts` (claim/release/wait), `tree.ts` (tree rendering), `wire.ts` (Commander glue). Each file < 600 LOC; the hub is < 35. |
528
530
  | `src/index.ts` | SDK entrypoint (re-exports) |
529
531
  | `skills/mu/SKILL.md` | Bundled skill teaching the LLM the model + verb list + jq pipelines |
@@ -558,8 +560,9 @@ each are deliberately small.
558
560
  | `VcsBackend` | Implementing `detect / createWorkspace / freeWorkspace / isClean / commitsBehind / rebaseTo / commitsSinceBase / recentCommits / showCommit` (~80–150 LOC; jj/sl/git/none are working examples) |
559
561
  | Per-CLI `Detector` | Adding patterns to `detectPiStatus` (vanilla pi `to interrupt)`; pi-meta + every TUI wrapper covered by Braille spinner glyph fallback `[\u2800-\u28FF]`) |
560
562
  | New typed verb | Add an SDK function in the relevant `src/*.ts`; add a `cmd<Verb>` to the matching `src/cli/<namespace>.ts` (or create a new namespace if the verb doesn't fit existing ones); wire one commander block in `src/cli.ts`'s `buildProgram()` (use `handle()` for the exit-code map; route through `printNextSteps` for self-documenting output) |
561
- | New schema migration| Bump `CURRENT_SCHEMA_VERSION` in `src/db.ts`; mirror the new shape in `CURRENT_SCHEMA`. Two of the three post-v5 bumps were script-free: v5 → v6 was purely additive (the existing CREATE-TABLE-IF-NOT-EXISTS pass picked up the new `archive_*` tables), and v6 → v7 was a destructive-but-idempotent in-place migration (a `DROP TABLE IF EXISTS approvals` block in `applySchema`). Reach for a one-shot migration script only when the change can't be expressed that way (the v4 → v5 surrogate-PK substrate switch was the canonical example; restore from git history if you need to see the shape). The loud-fail hook in `openDb` rejects pre-current DBs with `SchemaTooOldError` (exit code 4) and a migration instruction. |
563
+ | New schema migration| Bump `CURRENT_SCHEMA_VERSION` in `src/db.ts`; mirror the new shape in `CURRENT_SCHEMA`. Three of the four post-v5 bumps were script-free: v5 → v6 was purely additive (the existing CREATE-TABLE-IF-NOT-EXISTS pass picked up the new `archive_*` tables), v6 → v7 was a destructive-but-idempotent in-place migration (a `DROP TABLE IF EXISTS approvals` block in `applySchema`), and v7 → v8 is additive (`machine_identity`, `workstream_sync`, plus the `openDb` seed for `machine_identity`). Reach for a one-shot migration script only when the change can't be expressed that way (the v4 → v5 surrogate-PK substrate switch was the canonical example; restore from git history if you need to see the shape). The loud-fail hook in `openDb` rejects pre-current DBs with `SchemaTooOldError` (exit code 4) and a migration instruction. |
562
564
  | Snapshot hook | Add `await captureSnapshot(db, 'verb-name', workstream)` at the top of any new destructive verb (one-liner; GC + restore behaviour automatic) |
565
+ | Cross-machine sync | `machine_identity` gives each state directory a durable uuid; `workstream_sync.last_known_peer_seqs` records per-workstream peer progress. `mu db import` compares source `latestSeq`, local `latestSeq`, and the last-seen peer seq to classify the five cases: `IDENTICAL` / `FAST_FORWARD` / `LOCAL_AHEAD` / `CONFLICT` / `IMPORT`. Conflicts are sharp: refuse by default, or `--force-source` after parking the whole local workstream into a divergence sidecar for later `mu db replay`. |
563
566
 
564
567
  ## Surrogate-PK + SDK-boundary discipline (load-bearing)
565
568
 
package/docs/ROADMAP.md CHANGED
@@ -57,6 +57,84 @@ above:
57
57
 
58
58
  ---
59
59
 
60
+ ## Shipped
61
+
62
+ ### Multi-machine sync (db export/import + archive restore) — shipped in v0.4.1
63
+
64
+ Shipped in v0.4.1. The design note remains here as the historical
65
+ promotion record and to make the local-first boundary explicit.
66
+
67
+ Problem: one user wants to move a workstream between two machines
68
+ (laptop ↔ devserver) over multi-day stretches without losing the task
69
+ DAG, notes, archives, or activity log. Task owners are intentionally
70
+ machine-local and are not imported. The hard operating rule
71
+ is **no concurrent edits to the same workstream on two machines**;
72
+ other workstreams may continue locally on either machine. The current
73
+ markdown bucket round-trip is intentionally human-readable but too
74
+ lossy for this job (no full event log, drift on re-import), and raw
75
+ SQLite copying has no machine identity or drift guard.
76
+
77
+ Sketch: make the safe, explicit DB-file handoff a typed CLI surface,
78
+ not a daemon. `mu db export <file>` writes a SQLite copy plus a tiny
79
+ manifest (source machine id, per-workstream latest log seq, mu version,
80
+ schema version). `mu db import <file>` compares that manifest against
81
+ local `machine_identity` / `workstream_sync` rows, defaults to a
82
+ dry-run preview, then applies only when the caller passes `--apply`.
83
+ Fast-forward cases import cleanly. Divergence refuses by default;
84
+ `--force-source` replaces the whole workstream from the source file,
85
+ but first parks the losing local state under
86
+ `<state-dir>/divergence/<ws>-<ts>.db` so nothing is silently lost.
87
+ `mu db replay` is the later manual recovery verb for inspecting or
88
+ re-applying parked sidecar state; it is not automatic merge.
89
+
90
+ Directional verb map (target state):
91
+
92
+ | direction | verb |
93
+ | ---------------------------------------- | ------------------------------- |
94
+ | workstream → archive | `mu archive add` (existing) |
95
+ | archive → workstream | `mu archive restore` (shipped v0.4.1) |
96
+ | workstream → bucket markdown (read-only) | `mu workstream export` (existing) |
97
+ | archive → bucket markdown (read-only) | `mu archive export` (existing) |
98
+ | db → file (whole-machine sync) | `mu db export` (shipped v0.4.1) |
99
+ | file → db (whole-machine sync) | `mu db import` (shipped v0.4.1) |
100
+
101
+ `mu archive restore <label> --as <new-ws> [--source <orig-ws>]`
102
+ restores directly from the `archived_*` tables into a new workstream,
103
+ losslessly and without a markdown bucket round-trip. It refuses if
104
+ `--as` collides and auto-snapshots before writing. With those typed
105
+ surfaces shipped, `mu workstream import` was removed; bucket exports
106
+ remain read-only artifacts for humans and git, not the load-bearing
107
+ DB round-trip path.
108
+
109
+ Schema call-out: this is schema **v8**. Add `machine_identity` (one
110
+ row, generated once per state directory) and `workstream_sync`
111
+ (per-workstream last-seen peer sequence map). Do not require identical
112
+ `workstreams.id` values across machines; import is keyed by
113
+ workstream name and rewires local task/edge ids inside the target DB.
114
+ A clean-machine import is just the "source workstream not local"
115
+ branch.
116
+
117
+ Promotion criteria:
118
+
119
+ 1. **Proven friction.** At least two real workflows hit the laptop ↔
120
+ devserver handoff problem or the lossy bucket-import workaround.
121
+ 2. **No pillar refactor.** Fits the existing SQLite + typed-verb +
122
+ snapshot substrate; no tmux, VCS, or task-DAG redesign.
123
+ 3. **Bounded scope.** At least one useful subset fits in <300 LOC
124
+ (`mu archive restore` or `mu db export` + manifest), and the rest
125
+ decomposes into small typed verbs.
126
+
127
+ Anti-feature alignment: no daemon, watcher, live sync, remote backend,
128
+ config file, conflict UI, or row-level merge. The user owns transport
129
+ (`scp`, `rsync`, removable disk, etc.). Machine identity is generated
130
+ and stored in SQLite, not configured. Conflict handling is sharp and
131
+ whole-workstream: refuse, or `--force-source` after parking the loser
132
+ sidecar. This narrows the old "cross-machine sync" rejection to mean
133
+ live/automatic synchronization; explicit file export/import earned
134
+ promotion without violating the local-first pillar.
135
+
136
+ ---
137
+
60
138
  ## Possible — small additions with an obvious shape
61
139
 
62
140
  These have a clear design but haven't yet hit promotion criterion
@@ -157,8 +235,9 @@ reasoning per item.
157
235
  integration needs one transactional surface.
158
236
  - **`TaskSurface` adapter abstraction** — the built-in graph IS
159
237
  the killer feature.
160
- - **Cross-machine state sync** — local-first SQLite; layer
161
- syncthing on top if you want it.
238
+ - **Live cross-machine state sync** — local-first SQLite. Explicit
239
+ DB-file export/import shipped in v0.4.1, but no watcher, daemon,
240
+ remote backend, or live row merge.
162
241
  - **HTTP API on top of SQLite** — write your own RPC if you need
163
242
  one.
164
243
  - **A "hosted" mu** — your machine is the deployment.
@@ -1,26 +1,27 @@
1
1
  # mu — Usage Guide
2
2
 
3
- A practical, copy-pasteable tour of mu (current main; v0.4-track).
3
+ A practical, copy-pasteable tour of mu (current main; v0.4.1).
4
4
  Everything below works against the built CLI. Terms are canonical
5
5
  — see [VOCABULARY.md](VOCABULARY.md) for definitions; the complete
6
6
  current verb list is in `## CLI — complete verb list` of
7
7
  [skills/mu/SKILL.md](../skills/mu/SKILL.md).
8
8
 
9
- > **Status:** v0.4 wave (pre-1.0). ~60 typed verbs across 8
9
+ > **Status:** v0.4.1 (pre-1.0). ~65 typed verbs across 9
10
10
  > namespaces (`workstream`, `agent`, `task`, `workspace`, `log`,
11
- > `snapshot`, `archive`, `me`) plus bare top-level verbs
12
- > (`state`, `doctor`, `sql`, `undo`, `adopt`). Every verb accepts
13
- > `--json` (one allow-listed exception, `mu agent attach`),
14
- > per-agent VCS workspaces (jj/sl/git/none), activity log with
15
- > `--tail` subscription, bare `mu` TTY dashboard, canonical static
16
- > state card (`mu state` default / `--tui` render modes), whole-DB
17
- > snapshots auto-captured before destructive verbs +
18
- > `mu undo` / `mu snapshot {list,show}`, evidence on lifecycle
19
- > verbs, schema v7 (v5 surrogate INTEGER PKs + per-workstream
20
- > UNIQUE on operator-facing names; v6 added the `archive_*`
21
- > family additively; v7 dropped the dead `approvals` table).
11
+ > `snapshot`, `archive`, `db`, `me`) plus bare top-level verbs
12
+ > (`state`, `doctor`, `sql`, `undo`). Every verb accepts `--json`
13
+ > (one allow-listed exception, `mu agent attach`), per-agent VCS
14
+ > workspaces (jj/sl/git/none), activity log with `--tail`
15
+ > subscription, bare `mu` TTY dashboard, canonical static state card
16
+ > (`mu state` default / `--tui` render modes), whole-DB snapshots
17
+ > auto-captured before destructive verbs + `mu undo` /
18
+ > `mu snapshot {list,show}`, evidence on lifecycle verbs, schema v8
19
+ > (v5 surrogate INTEGER PKs + per-workstream UNIQUE on
20
+ > operator-facing names; v6 added the `archive_*` family additively;
21
+ > v7 dropped the dead `approvals` table; v8 adds `machine_identity`
22
+ > and `workstream_sync` for db sync).
22
23
  > See [CHANGELOG.md](../CHANGELOG.md) for the release entry,
23
- > and [§ Not in 0.4.0](#whats-not-in-040-and-how-to-work-around-it)
24
+ > and [§ Not in 0.4.1](#whats-not-in-041-and-how-to-work-around-it)
24
25
  > at the bottom for the gaps that still need workarounds.
25
26
 
26
27
  *If anything below disagrees with `mu --help`, trust `mu --help`.*
@@ -46,9 +47,10 @@ current verb list is in `## CLI — complete verb list` of
46
47
  14. [Recovery scenarios](#14-recovery-scenarios)
47
48
  15. [Cleanup](#15-cleanup)
48
49
  15.5. [Archives — cross-workstream preservation](#155-archives--cross-workstream-preservation-of-task-graphs)
50
+ 15.6. [Multi-machine sync](#156-multi-machine-sync)
49
51
  16. [One-shot demo script](#16-one-shot-demo-script)
50
52
  17. [Mental model in three sentences](#mental-model-in-three-sentences)
51
- 18. [What's NOT in 0.4.0](#whats-not-in-040-and-how-to-work-around-it)
53
+ 18. [What's NOT in 0.4.1](#whats-not-in-041-and-how-to-work-around-it)
52
54
  19. [Where to go from here](#where-to-go-from-here)
53
55
 
54
56
  ---
@@ -1162,8 +1164,9 @@ verbs don't cover: ad-hoc joins, manual recovery, exploring schema.
1162
1164
  The schema is 8 core tables (`workstreams`, `agents`, `tasks`,
1163
1165
  `task_edges`, `task_notes`, `agent_logs`, `vcs_workspaces`,
1164
1166
  `snapshots`), 5 archive tables (`archives`, `archived_tasks`,
1165
- `archived_edges`, `archived_notes`, `archived_events`), 1 meta table
1166
- (`schema_version`), plus three views (`ready`, `blocked`, `goals`):
1167
+ `archived_edges`, `archived_notes`, `archived_events`), 2 meta tables
1168
+ (`schema_version`, `machine_identity`), 1 sync table
1169
+ (`workstream_sync`), plus three views (`ready`, `blocked`, `goals`):
1167
1170
 
1168
1171
  ```bash
1169
1172
  mu sql "SELECT name FROM sqlite_master WHERE type IN ('table','view') ORDER BY type, name"
@@ -1725,75 +1728,20 @@ Markdown only by design — no HTML/PDF, no embedded VCS, no
1725
1728
  cross-workstream merge. Operators can pandoc / `git init`
1726
1729
  themselves.
1727
1730
 
1728
- ### Cross-machine + collab `mu workstream import`
1731
+ ### Bucket exports are read-only artifacts
1729
1732
 
1730
- The export above plus `mu workstream import <bucket-dir>` is the
1731
- cross-machine + collaboration story. Push the bucket directory to
1732
- git on machine A; pull it on machine B (or share it with a
1733
- teammate); `mu workstream import` rebuilds the workstream + every
1734
- task + edge + note locally.
1733
+ Bucket exports (`mu workstream export` and `mu archive export`) are
1734
+ now **read-only** artifacts for humans / git / docs. They are still
1735
+ excellent for grep, code review, project handoff, and historical
1736
+ write-ups, but they are no longer a load-bearing DB round-trip path.
1735
1737
 
1736
- ```bash
1737
- # Machine A — author
1738
- mu workstream export -w auth-refactor --out exports/auth
1739
- (cd exports/auth && git init && git add . && git commit -m 'auth snapshot')
1740
- git push origin main
1741
-
1742
- # Machine B — pull + rehydrate
1743
- git pull
1744
- mu workstream import exports/auth # → workstream `auth-refactor`
1745
- mu workstream import exports/auth --workstream auth-v2 # rename on import
1746
- mu workstream import exports/auth --dry-run # walk + parse + report; no DB writes
1747
- mu workstream import exports/auth --json # machine-readable per-source-ws result
1738
+ Use the typed surfaces for recovery and movement:
1748
1739
 
1749
- # Partial bucket import — multi-source bucket, but you only want
1750
- # one (or a subset) restored. Two equivalent forms:
1751
- mu workstream import exports/mu/roadmap-v0-2 # Form 1 per-source-ws subdir path
1752
- mu workstream import exports/mu --source-ws roadmap-v0-2 # Form 2 bucket + filter
1753
- mu workstream import exports/mu --source-ws auth,ui # Form 2 X+Y, leave Z behind
1754
- mu workstream import exports/mu --source-ws auth --source-ws ui # repeat OR comma-separate; or both
1755
- ```
1756
-
1757
- Key properties:
1758
-
1759
- - **Markdown-only.** `.db` files are never imported (binary +
1760
- machine-specific). `mu undo` + snapshot files cover the
1761
- same-machine case; this verb covers cross-machine + collab.
1762
- - **Per-source-ws transactional.** Each source-ws subdirectory is
1763
- imported in its own SQLite transaction. A failure in source A
1764
- rolls back A; sibling source B is unaffected.
1765
- - **Refuses silent merges.** If the target workstream already
1766
- exists in the DB, the import errors with
1767
- `WorkstreamAlreadyExistsError`. Recourse:
1768
- `--workstream <new-name>` (single-source buckets only) or
1769
- destroy the existing workstream first.
1770
- - **Owners reset.** Agents aren't exported, so the imported tasks
1771
- are unowned. The original owner name survives in the markdown
1772
- frontmatter — that's the audit trail.
1773
- - **Tombstones skipped.** Files starting with the
1774
- `> **Deleted from DB on …**` banner (preserved by re-export of
1775
- a deleted task) are counted as `tombstones_skipped` and not
1776
- re-inserted.
1777
- - **Forward edge refs are deferred.** `blocked_by` / `blocks`
1778
- arrays are validated against the bucket's id-set up front, then
1779
- inserted after every task in the source-ws is created.
1780
- - **Partial import.** Multi-source buckets accept either a
1781
- per-source-ws subdir path (auto-detected via
1782
- `README.md` + `INDEX.md` + `tasks/` + a parent
1783
- `manifest.json` listing the subdir as a source) OR a
1784
- `--source-ws <names...>` filter on the bucket root
1785
- (variadic per `cli_audit_plurality_uniformity`: repeat,
1786
- comma-separate, or both). The two forms are equivalent for
1787
- single-source restores. `--workstream <new-name>` is allowed
1788
- whenever the resolved source-ws list has exactly one entry
1789
- (Form 1; or Form 2 with a single name); rejected for
1790
- multi-source filters. Passing `--source-ws` against a Form 1
1791
- per-source-ws subdir is refused (the subdir already implies one
1792
- source). A `--source-ws` name not in the bucket manifest raises
1793
- `ImportSourceNotInBucketError` (exit 4) and lists the valid
1794
- names. `--source-ws ',,'` (canonicalises to zero names) is a
1795
- `UsageError` (exit 2) so a typo doesn't silently fall back to
1796
- importing the entire bucket.
1740
+ | Need | Verb |
1741
+ | ---- | ---- |
1742
+ | Lossless un-archive | `mu archive restore <label> --as <new-ws> [--source <orig-ws>]` |
1743
+ | Laptop devserver handoff | `mu db export <file>` + `mu db import <file>` |
1744
+ | Manual recovery from a parked conflict | `mu db replay <sidecar>` |
1797
1745
 
1798
1746
  ---
1799
1747
 
@@ -1816,7 +1764,8 @@ mu archive add v0-3-wave -w roadmap-v0-3 --destroy # cascade: archive THEN des
1816
1764
  mu archive list # label | tasks | sources | created | last_added
1817
1765
  mu archive show v0-3-wave # detail card + per-source-workstream summary
1818
1766
  mu archive search 'oauth' [--label v0-3-wave] # LIKE-search archived titles + note content (--limit N, --json)
1819
- mu archive export v0-3-wave --out exports/v0-3-wave # render every source-ws to a bucket directory (markdown)
1767
+ mu archive restore v0-3-wave --as restored-auth --source auth-refactor
1768
+ mu archive export v0-3-wave --out exports/v0-3-wave # read-only markdown bucket for humans/git/docs
1820
1769
  ```
1821
1770
 
1822
1771
  Key properties:
@@ -1837,6 +1786,12 @@ Key properties:
1837
1786
  - **Outlives the source.** `archived_tasks.source_workstream` is
1838
1787
  TEXT (not an FK), so the source workstream can be destroyed and
1839
1788
  the archive's snapshot of it stays queryable forever.
1789
+ - **Lossless un-archive.** `mu archive restore <label> --as <new-ws>
1790
+ [--source <orig-ws>]` copies tasks, edges, and notes directly from
1791
+ `archived_*` tables into a fresh workstream. It refuses if `--as`
1792
+ collides and snapshots before writing. Archives do not snapshot live
1793
+ panes or the live event log, so agents, workspace paths, and
1794
+ `agent_logs` are not restored.
1840
1795
  - **Reversible.** `mu archive delete <label> --yes` captures a
1841
1796
  snapshot first; `mu undo --yes` brings the whole archive back.
1842
1797
  `mu archive remove <label> -w <ws>` is the surgical version
@@ -1881,9 +1836,9 @@ mu archive add mu-v0-3 -w mufeedback-v03 --destroy
1881
1836
  - **No "default" / auto-archive.** `mu workstream destroy` does
1882
1837
  NOT auto-add to a fallback bucket. Either you picked a label
1883
1838
  deliberately or you didn't want one.
1884
- - **No re-import.** The archive IS the workstream's afterlife.
1885
- If you need an archived task back as live work, copy it via
1886
- `mu sql` into a fresh workstream + `mu task add`.
1839
+ - **No bucket re-import.** The archive IS the workstream's afterlife.
1840
+ If you need an archived source workstream back as live work, use
1841
+ `mu archive restore <label> --as <new-ws> [--source <orig-ws>]`.
1887
1842
  - **No archive→archive merge / rename.** Operator-managed via
1888
1843
  `mu sql` if it ever matters.
1889
1844
  - **Snapshots vs archives are separate concerns.** Snapshots are
@@ -1893,6 +1848,71 @@ mu archive add mu-v0-3 -w mufeedback-v03 --destroy
1893
1848
 
1894
1849
  ---
1895
1850
 
1851
+ ## 15.6 Multi-machine sync
1852
+
1853
+ Use `mu db {export,import,replay}` when one user alternates a
1854
+ workstream between two machines (for example laptop ↔ devserver) over
1855
+ multi-day stretches. You own the transport: `rsync`, `scp`, Dropbox,
1856
+ git-lfs, USB, whatever moves a SQLite file plus its manifest.
1857
+
1858
+ **Hard rule / user contract:** do not edit the same workstream on two
1859
+ machines concurrently. Other workstreams may keep moving locally, but
1860
+ for one workstream, finish or release in-flight claims before export:
1861
+ `mu agent list -w <ws>` shows current owners. `mu db import` does not
1862
+ carry owners because `owner_id` points at the machine-local `agents`
1863
+ table.
1864
+
1865
+ ```bash
1866
+ # Machine A — export the whole DB copy + ~/Dropbox/mu.db.manifest.json
1867
+ mu db export ~/Dropbox/mu.db --force
1868
+ # ship file (rsync / scp / Dropbox / git-lfs / USB)
1869
+
1870
+ # Machine B — preview first, then commit
1871
+ mu db import ~/Dropbox/mu.db # dry-run preview
1872
+ mu db import ~/Dropbox/mu.db --apply # commits FAST_FORWARD / IMPORT rows
1873
+ ```
1874
+
1875
+ Dry-run output is a per-workstream decision table:
1876
+
1877
+ ```
1878
+ workstream decision delta
1879
+ ----------- ------------ -------------------------------
1880
+ auth FAST_FORWARD source 42, local 39, last_synced 39
1881
+ docs IDENTICAL source 12, local 12, last_synced 12
1882
+ local-only LOCAL_AHEAD source 0, local 7, re-export from this machine
1883
+ experiment CONFLICT source 55, local 58, needs --force-source
1884
+ ```
1885
+
1886
+ (The actual CLI also prints the numeric columns separately:
1887
+ `source_seq`, `local_seq`, `last_synced`, and `needs`.)
1888
+
1889
+ Five case branches exist: `IDENTICAL` / `FAST_FORWARD` /
1890
+ `LOCAL_AHEAD` / `CONFLICT` / `IMPORT` (source-only or clean-machine
1891
+ import). `LOCAL_AHEAD` means the incoming file is stale for that
1892
+ workstream; re-export from this machine instead of applying it.
1893
+ `CONFLICT` means both sides advanced since the last sync and mu
1894
+ refuses by default.
1895
+
1896
+ Recovery from an accidental concurrent edit is intentionally sharp:
1897
+
1898
+ ```bash
1899
+ mu db import ~/Dropbox/mu.db --apply --force-source
1900
+ # prints a parked loser like:
1901
+ # <state-dir>/divergence/auth-2026-05-14T10:00:00.000Z-a1b2c3d4.db
1902
+
1903
+ mu db replay <state-dir>/divergence/auth-2026-05-14T10:00:00.000Z-a1b2c3d4.db
1904
+ mu db replay <state-dir>/divergence/auth-2026-05-14T10:00:00.000Z-a1b2c3d4.db --task local_fix --apply
1905
+ mu db replay <state-dir>/divergence/auth-2026-05-14T10:00:00.000Z-a1b2c3d4.db --all --apply
1906
+ ```
1907
+
1908
+ `--force-source` replaces the whole local workstream from the source
1909
+ file, but first parks the local divergent state as a divergence
1910
+ sidecar. `mu db replay` is the manual cherry-pick tool for that
1911
+ sidecar; it is dry-run by default, idempotent, and refuses when the
1912
+ same `local_id` exists locally with diverged content.
1913
+
1914
+ ---
1915
+
1896
1916
  ## 16. One-shot demo script
1897
1917
 
1898
1918
  Copy-pasteable, end-to-end. Wipes any prior `~/.local/state/mu/mu.db`.
@@ -1951,9 +1971,9 @@ service of those three.
1951
1971
 
1952
1972
  ---
1953
1973
 
1954
- ## What's NOT in 0.4.0 (and how to work around it)
1974
+ ## What's NOT in 0.4.1 (and how to work around it)
1955
1975
 
1956
- <a id="whats-not-in-040-and-how-to-work-around-it"></a>
1976
+ <a id="whats-not-in-050-and-how-to-work-around-it"></a>
1957
1977
 
1958
1978
  The full roadmap with promotion criteria lives in
1959
1979
  [ROADMAP.md](ROADMAP.md). The short list of gaps you might hit
@@ -56,8 +56,11 @@ defined here, fix the doc. If you need a new term, add it here first.
56
56
  | **detector** | Per-CLI pattern matcher for busy/permission/ready. Today mu has one (`detectPiStatus` in `src/detect.ts`); covers vanilla pi + any TUI wrapper that uses Braille spinner glyphs. Other CLIs spawned via `--cli <other>` may misclassify; trust scrollback over the emoji. | "matcher", "parser" |
57
57
  | **snapshot** | A whole-DB backup (`<state-dir>/snapshots/<id>.db`) auto-captured before each destructive verb (workstream destroy, agent close, task close/reject/defer/release/delete, workspace free). Indexed by the `snapshots` table; restore via `mu undo`. | "checkpoint", "backup" |
58
58
  | **prune** | Verb: bulk-drop rows from the snapshots collection per a policy (`mu snapshot prune` with `--keep-last`, `--older-than <D>d`, `--stale-version`, `--all`, or the bare GC-policy form). Sibling of the auto-GC that runs on every capture; the explicit verb is for the dogfood case where the auto-GC's count + age caps need an operator-driven supplement (e.g. "drop every snapshot whose schema_version is now stale"). Surgical single-row removal is `mu snapshot delete <id>`. | "reap", "sweep" (overloaded by workstream-destroy --empty) |
59
- | **export** | A directory of plain markdown files produced by `mu workstream export` (one `.md` per task + `INDEX.md` + `README.md` + `manifest.json`). Survives `mu workstream destroy` (auto-run pre-destroy to `<state-dir>/exports/<ws>-<ts>/` unless `--no-export`). Idempotent: re-export against the same dir rewrites only changed files; deleted tasks are preserved with a banner. Markdown-only by design — no HTML/PDF, no embedded VCS. The inverse is **import** (markdown only; never `.db`). | "dump", "snapshot" (snapshot is the binary `.db`) |
60
- | **import** | The inverse of **export**: `mu workstream import <bucket-dir>` walks a v0.3 bucket directory and rebuilds every source-ws subdir as live tasks + edges + notes in the DB. Markdown-only by design (cross-machine `.db` is `mu undo` + snapshots). Per-source-ws transactional; refuses to merge silently into an existing workstream (use `--workstream <name>` for single-source rename, or destroy the existing one first). Owners reset to NULL on import (agents aren't restored); the original owner name survives in the markdown frontmatter. | "rehydrate", "restore" (restore = `mu undo`) |
59
+ | **machine_id** | Per-state-directory uuid seeded on first `openDb` and stored in `machine_identity`. Identifies one mu DB across `mu db export` / `mu db import`; users do not configure it. | "device id", "host id" |
60
+ | **db sync** | The `mu db {export, import, replay}` cluster of verbs: whole-DB SQLite copy with manifest, per-workstream drift-detecting import, and manual replay from parked divergence sidecars. Explicit file handoff only; not live synchronization. | "live sync", "replication", "collab" |
61
+ | **divergence sidecar** | SQLite file at `<state-dir>/divergence/<ws>-<ts>.db` parked by `mu db import --force-source` before clobbering local divergent state. Later inspected or cherry-picked via `mu db replay`. | "conflict backup", "loser DB" |
62
+ | **export** | A directory of plain markdown files produced by `mu workstream export` (one `.md` per task + `INDEX.md` + `README.md` + `manifest.json`). Survives `mu workstream destroy` (auto-run pre-destroy to `<state-dir>/exports/<ws>-<ts>/` unless `--no-export`). Idempotent: re-export against the same dir rewrites only changed files; deleted tasks are preserved with a banner. Markdown-only by design — no HTML/PDF, no embedded VCS. Exports are now read-only artifacts for humans / git / docs; the lossless movement paths are **db sync** and `mu archive restore`. | "dump", "snapshot" (snapshot is the binary `.db`) |
63
+ | **import** | Avoid as a generic noun unless naming `mu db import`. The removed `mu workstream import` bucket→DB round-trip was replaced by **db sync** for cross-machine handoff and `mu archive restore` for un-archive. | "rehydrate", "restore" (restore has specific meanings) |
61
64
  | **archive** | An operator-named bucket of preserved task graphs (rows in `archives` + `archived_tasks` + `archived_edges` + `archived_notes` + `archived_events`). Cross-workstream and additive: one archive may accumulate snapshots from many workstreams under the same label. Outlives every source workstream; `archived_tasks.source_workstream` is intentionally TEXT (not an FK) so destroyed-workstream attribution survives. Distinct from a **snapshot** (binary whole-DB backup for `mu undo`) and an **export** (markdown files on disk). | "backup", "vault" |
62
65
  | **archived task** | A row in `archived_tasks`: a snapshot of a `tasks` row at archive time. Pins `status`, `impact`, `effort_days`, `owner_name`, and the original `created_at`/`updated_at` for retrospect ordering. The `(archive_id, source_workstream, original_local_id)` composite UNIQUE makes `mu archive add` idempotent at the (archive, workstream) granularity. | "closed task" (status-orthogonal) |
63
66
  | **archive label** | The operator-facing TEXT name of an **archive**. Globally unique across the machine (NOT per-workstream — archives outlive workstreams). Shape: `/^[a-z][a-z0-9_-]{0,63}$/` (wider than workstream names because labels often encode workstream + date + purpose, e.g. `auth-2026-q1`). | "archive name" (in code; `label` only) |
@@ -235,7 +238,19 @@ For worked examples of each verb, see
235
238
  [USAGE_GUIDE.md](USAGE_GUIDE.md).
236
239
 
237
240
  This document is a *vocabulary* doc; it doesn't try to be a verb
238
- reference too.
241
+ reference too. Rows here exist to keep names canonical, not to replace
242
+ `--help`.
243
+
244
+ | Operation | Canonical meaning |
245
+ | --------- | ----------------- |
246
+ | `mu db export <file>` | Whole-DB SQLite copy via `VACUUM INTO` plus `<file>.manifest.json` (`machineId`, `schemaVersion`, per-workstream `latestSeq`). |
247
+ | `mu db import <file>` | Drift-detecting per-workstream import from an exported DB. Dry-run by default; `--apply` commits; five case branches: `IDENTICAL` / `FAST_FORWARD` / `LOCAL_AHEAD` / `CONFLICT` / `IMPORT`. |
248
+ | `mu db replay <sidecar>` | Manual cherry-pick of tasks, notes, and eligible edges from a divergence sidecar parked by `mu db import --force-source`. |
249
+ | `mu archive restore <label> --as <new-ws> [--source <orig-ws>]` | Lossless un-archive from `archived_*` rows into a fresh workstream. |
250
+
251
+ Removed operation: `mu workstream import`. Use `mu db import` for
252
+ cross-machine sync and `mu archive restore` for un-archive. Bucket
253
+ exports remain read-only artifacts.
239
254
 
240
255
  ---
241
256
 
@@ -308,6 +323,9 @@ XDG-Base-Directory-Spec compliant. The state directory resolves as:
308
323
  `mu snapshot show <id>`). Default colocation: snapshots live
309
324
  next to the live DB, so per-test isolation works without env
310
325
  gymnastics.
326
+ - `<state-dir>/divergence/<workstream>-<timestamp>-<suffix>.db` —
327
+ divergence sidecars parked by `mu db import --force-source` before
328
+ clobbering local state. Replay selected rows with `mu db replay`.
311
329
  - mu does NOT consult any agent-template directory. If pi-subagents
312
330
  is installed, its `~/.pi/agent/agents/` and `.pi/agents/` paths
313
331
  are pi-subagents' concern — not mu's.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@martintrojer/mu",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "A persistent, observable crew of pi agents running in one tmux session per workstream, coordinated through a built-in task DAG.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -37,15 +37,9 @@
37
37
  }
38
38
  },
39
39
  "bin": {
40
- "mu": "dist/cli.js"
40
+ "mu": "./dist/cli.js"
41
41
  },
42
- "files": [
43
- "dist",
44
- "skills",
45
- "docs",
46
- "README.md",
47
- "AGENTS.md"
48
- ],
42
+ "files": ["dist", "skills", "docs", "README.md", "AGENTS.md"],
49
43
  "scripts": {
50
44
  "build": "tsup",
51
45
  "dev": "tsup --watch",
@@ -204,7 +204,8 @@ git cherry-pick "$sha" && npm test
204
204
  ## CLI overview (only gotchas; use `--help` for full syntax)
205
205
 
206
206
  - **Workstream:** `init`, `list`, `destroy` (auto-snapshot;
207
- `--archive <label>` preserves graph), `export`, `import`.
207
+ `--archive <label>` preserves graph), `export` (read-only
208
+ markdown bucket for humans/git/docs).
208
209
  - **Agents:** `spawn` (`--workspace`, `--role read-only`,
209
210
  `--command`), `send`, `read`, `show`, `list`, `close`, `free`,
210
211
  `kick`, `adopt <pane-id|title>` for orphan panes.
@@ -224,8 +225,12 @@ git cherry-pick "$sha" && npm test
224
225
  restores DB only, not tmux/workspace dirs. No redo; each restore
225
226
  takes a pre-restore snapshot.
226
227
  - **Archives:** `create`, `list`, `show`, `add <label> -w <ws>
227
- [--destroy]`, `remove`, `delete`, `search`, `export`. Labels are
228
- global.
228
+ [--destroy]`, `restore <label> --as <new-ws> [--source <orig-ws>]`,
229
+ `remove`, `delete`, `search`, `export` (read-only bucket). Labels
230
+ are global.
231
+ - **DB sync:** `mu db export <file>`, `mu db import <file>` (dry-run;
232
+ `--apply` commits; `--force-source` parks conflicts),
233
+ `mu db replay <sidecar>` (dry-run; `--task <id>` / `--all --apply`).
229
234
  - **State/TUI:** bare `mu` opens the all-workstream TUI on a TTY;
230
235
  agents/scripts use `mu state --json`. `mu state --tui` is
231
236
  read-only, yanks commands, `?` shows keys, `/` filters popups,