@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.
- package/AGENTS.md +19 -14
- package/README.md +28 -14
- package/dist/cli.js +1428 -728
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +193 -85
- package/dist/index.js +987 -554
- package/dist/index.js.map +1 -1
- package/docs/ARCHITECTURE.md +12 -9
- package/docs/ROADMAP.md +81 -2
- package/docs/USAGE_GUIDE.md +109 -89
- package/docs/VOCABULARY.md +21 -3
- package/package.json +3 -9
- package/skills/mu/SKILL.md +8 -3
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -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 (
|
|
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/
|
|
511
|
-
| `src/
|
|
512
|
-
| `src/archives
|
|
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
|
|
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`.
|
|
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
|
-
- **
|
|
161
|
-
|
|
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.
|
package/docs/USAGE_GUIDE.md
CHANGED
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
# mu — Usage Guide
|
|
2
2
|
|
|
3
|
-
A practical, copy-pasteable tour of mu (current main; v0.4
|
|
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
|
|
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
|
|
13
|
-
>
|
|
14
|
-
>
|
|
15
|
-
>
|
|
16
|
-
>
|
|
17
|
-
>
|
|
18
|
-
> `mu
|
|
19
|
-
>
|
|
20
|
-
>
|
|
21
|
-
>
|
|
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.
|
|
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.
|
|
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`),
|
|
1166
|
-
(`schema_version`),
|
|
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
|
-
###
|
|
1731
|
+
### Bucket exports are read-only artifacts
|
|
1729
1732
|
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
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
|
|
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
|
|
1886
|
-
`mu
|
|
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.
|
|
1974
|
+
## What's NOT in 0.4.1 (and how to work around it)
|
|
1955
1975
|
|
|
1956
|
-
<a id="whats-not-in-
|
|
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
|
package/docs/VOCABULARY.md
CHANGED
|
@@ -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
|
-
| **
|
|
60
|
-
| **
|
|
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.
|
|
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",
|
package/skills/mu/SKILL.md
CHANGED
|
@@ -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
|
|
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]`, `
|
|
228
|
-
|
|
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,
|