@martintrojer/mu 0.3.2 → 0.4.0
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 +181 -42
- package/README.md +63 -22
- package/dist/cli.js +13348 -5236
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1428 -1369
- package/dist/index.js +4002 -3110
- package/dist/index.js.map +1 -1
- package/docs/ARCHITECTURE.md +296 -56
- package/docs/HANDOVER.md +461 -0
- package/docs/ROADMAP.md +120 -498
- package/docs/USAGE_GUIDE.md +445 -143
- package/docs/VISION.md +48 -4
- package/docs/VOCABULARY.md +18 -5
- package/docs/img/tui-dashboard.png +0 -0
- package/package.json +12 -6
- package/skills/mu/SKILL.md +273 -469
package/docs/VISION.md
CHANGED
|
@@ -316,10 +316,11 @@ speaking for another.
|
|
|
316
316
|
tests. Verification is the caller's job. (mu may grow optional
|
|
317
317
|
verifying-runners later if friction surfaces; today it's an
|
|
318
318
|
audit-trail discipline, not enforcement.)
|
|
319
|
-
- **
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
319
|
+
- **DB-undoable, not substrate-undoable.** Destructive verbs
|
|
320
|
+
auto-capture whole-DB snapshots and `mu undo --yes` restores the
|
|
321
|
+
registry. That does not replay killed tmux panes or recreate freed
|
|
322
|
+
workspace directories; after a restore, reconciliation reports
|
|
323
|
+
ghosts/orphans and the caller decides what to re-spawn or adopt.
|
|
323
324
|
|
|
324
325
|
---
|
|
325
326
|
|
|
@@ -354,6 +355,49 @@ speaking for another.
|
|
|
354
355
|
(SQLite hooks, fs.watch) are a future ask if anyone hits the
|
|
355
356
|
latency cliff.
|
|
356
357
|
|
|
358
|
+
7. **Every invocation is short-lived — except for two named
|
|
359
|
+
interactive readers.** mu is a CLI: each verb starts, mutates
|
|
360
|
+
or reads, prints, and exits. There is no daemon, no resident
|
|
361
|
+
state outside SQLite, no background process. Two verbs are
|
|
362
|
+
deliberately and narrowly exempt because they are interactive
|
|
363
|
+
*readers*, not background workers:
|
|
364
|
+
|
|
365
|
+
- `mu log --tail` — polls SQLite once per second; emits NDJSON
|
|
366
|
+
until SIGINT or the parent closes stdin.
|
|
367
|
+
- the TUI dashboard — rendered by `mu state --tui` explicitly or
|
|
368
|
+
by bare `mu` when stdout is attached to a TTY, until the user
|
|
369
|
+
quits with `q`/`Ctrl-C`. Plain `mu state` keeps the static-card
|
|
370
|
+
behaviour; non-TTY bare `mu` prints help instead of entering Ink.
|
|
371
|
+
|
|
372
|
+
Both share the same shape, and the shape is the predicate that
|
|
373
|
+
bounds the exception:
|
|
374
|
+
|
|
375
|
+
- **Interactive, not a daemon.** The process is owned by a
|
|
376
|
+
human (or a parent script) and dies the moment that owner
|
|
377
|
+
ends it. Nothing keeps it alive across sessions; nothing
|
|
378
|
+
restarts it.
|
|
379
|
+
- **Read-only against SQLite.** Neither verb writes. The TUI's
|
|
380
|
+
act-intents (claim, close, send, ...) yank the canonical
|
|
381
|
+
`mu <verb>` command into the clipboard and exit the
|
|
382
|
+
dashboard; every mutation still lands through a fresh
|
|
383
|
+
short-lived CLI invocation.
|
|
384
|
+
- **No resources beyond stdout, stdin, and a poll timer.** No
|
|
385
|
+
sockets, no file watches, no subscriptions to external
|
|
386
|
+
services, no spawned subprocesses, no inter-process state
|
|
387
|
+
beyond the SQLite reads any other CLI invocation already
|
|
388
|
+
does.
|
|
389
|
+
- **Human-TTY gated, with static/script fallbacks.** The TUI mode
|
|
390
|
+
activates when `--tui` is passed to `mu state` or when bare `mu`
|
|
391
|
+
sees `process.stdout.isTTY === true`. Default `mu state` prints
|
|
392
|
+
the static card. Non-interactive callers (pipes, CI, `--json`, or
|
|
393
|
+
`MU_NO_TUI=1`) never enter the TUI.
|
|
394
|
+
|
|
395
|
+
This is not a precedent for any other long-lived process. The
|
|
396
|
+
anti-feature pledges in [ROADMAP.md](ROADMAP.md) ("no daemon,
|
|
397
|
+
watcher, or background process beyond what tmux / SQLite give
|
|
398
|
+
us") remain in force; a third member of this exception class
|
|
399
|
+
would need its own promotion.
|
|
400
|
+
|
|
357
401
|
---
|
|
358
402
|
|
|
359
403
|
## What looking at a prior multi-agent runtime taught us
|
package/docs/VOCABULARY.md
CHANGED
|
@@ -49,7 +49,7 @@ defined here, fix the doc. If you need a new term, add it here first.
|
|
|
49
49
|
| **one-shot** | Agent that exists for a single task and then terminates | "ephemeral", "transient" |
|
|
50
50
|
| **workspace** | A VCS-isolated checkout (jj workspace / sl worktree / git worktree / cp) | "branch" (it has one but isn't one), "checkout" (only for `none` backend) |
|
|
51
51
|
| **workspace orphan** | A directory under `<state-dir>/workspaces/<workstream>/` with no row in `vcs_workspaces`. Blocks subsequent `--workspace` spawns. Surfaced by `mu workspace orphans -w X` and `mu state -w X`. | "stray dir", "leftover workspace" |
|
|
52
|
-
| **stale workspace** | A workspace whose `parent_ref` is N commits behind the project's default branch HEAD (per the workspace's local refs cache). Rendered as a color-coded `behind` column (green ≤2, yellow 3–9, red ≥10) in `mu workspace list` and `mu state`; ≥10 triggers a one-line warn in `mu state
|
|
52
|
+
| **stale workspace** | A workspace whose `parent_ref` is N commits behind the project's default branch HEAD (per the workspace's local refs cache). Rendered as a color-coded `behind` column (green ≤2, yellow 3–9, red ≥10) in `mu workspace list` and `mu state`; ≥10 triggers a one-line warn in `mu state`, `mu task claim --for`, and `mu agent send` (or refusal on the two dispatch verbs with `--strict-staleness`). Pure observation — mu never auto-fetches. | "out of date", "drifting" |
|
|
53
53
|
| **refresh** | `mu workspace refresh <agent>` — rebase the agent's workspace onto a fresh base (default = backend's tracked main; `--from <ref>` overrides) WITHOUT touching the agent or pane. Refuses on dirty WC; surfaces conflicts as exit 5 with a resolve-in-place hint. The `none` backend errors (no VCS to rebase). | "recycle", "reset" (overloaded) |
|
|
54
54
|
| **recreate** | `mu workspace recreate <agent>` — free + create the agent's workspace in one shot. The between-wave "prep this worker for the next dispatch" verb. Reuses the previous backend unless `--backend` overrides; bases on current main unless `--from <ref>` overrides. Refuses on dirty WC the same way `free` does; `--force` discards the dirty edits (lossy). Sibling of **refresh**: refresh PRESERVES the worker's commits (rebases them onto fresh main); recreate THROWS THEM AWAY. | "recycle", "reset" (overloaded), "free+create" (only in commit messages) |
|
|
55
55
|
| **backend** | Implementation of `AgentBackend` or `VcsBackend` | "driver", "provider" |
|
|
@@ -72,6 +72,19 @@ defined here, fix the doc. If you need a new term, add it here first.
|
|
|
72
72
|
| **reconcile** | Verb: re-derive registry rows from substrate reality (tmux). Always runs in `mu agent list` and `mu doctor`. | "sync", "refresh" |
|
|
73
73
|
| **adopt** | Verb (`mu agent adopt`): register an existing tmux pane as a managed **agent**. The inverse of `mu agent list`'s 'orphan' state. Pane must be in the workstream's tmux session. | "import", "absorb" |
|
|
74
74
|
| **pi-subagents** | A different package by Nico Bailon for in-pi focused delegation. Mu and pi-subagents are complementary, not competing. | conflating with mu |
|
|
75
|
+
| **TUI** | The interactive ink-based dashboard launched by bare `mu` in a TTY or explicitly by `mu state --tui`. Lives in `src/cli/tui/`. Read-only against SQLite (yanks, never executes). | "GUI", "interactive mode" |
|
|
76
|
+
| **dashboard** | The TUI's main screen — the grid of cards above the status bar. | "home screen", "main view" |
|
|
77
|
+
| **card** | A glanceable summary tile on the dashboard, identified by its toggle digit (0-9). Wrapped in a TitledBox. | "panel", "section" (overloaded) |
|
|
78
|
+
| **popup** | A fullscreen drill-down opened with `Shift+0`-`Shift+9` or a keybind-only shortcut such as `g` for DAG; single-popup invariant. Closed with `Esc`/`q`. | "modal", "dialog", "detail view" |
|
|
79
|
+
| **TitledBox** | The `<TitledBox>` component (`src/cli/tui/titled-box.tsx`) that renders a rounded border with the section header inset into the top border line. The visual primitive used by every card / popup / help overlay. | "header box", "box" (alone) |
|
|
80
|
+
| **tick** | The TUI's periodic data refresh (default 1s; `+/-/=` adjusts). Owned by a single `setInterval` in `<App>`. | "poll", "refresh" (verb sense), "frame" |
|
|
81
|
+
| **yank** | Copy the canonical `mu` command for the focused row to the clipboard. Bound to `y` in every popup. | "copy", "export command" |
|
|
82
|
+
| **footer** | The persistent bottom line on the dashboard showing the last yank. Cleared with `c`. | "status line" (reserved for status bar), "toast" |
|
|
83
|
+
| **toast** | Transient in-popup message (e.g. "tick floor 100ms" when `+` hits floor). | "notification", "banner" |
|
|
84
|
+
| **act-intent** | The conceptual action a `y` keypress would trigger. **Never executed by the TUI** — the user runs the yanked command in their shell. The R1 read-only contract: model drives the CLI. | "command intent", "action proposal" |
|
|
85
|
+
| **help overlay** | The `?` / `F1` modal showing the global + per-popup keymap. Same TitledBox family as cards/popups. | "keys", "cheat sheet" |
|
|
86
|
+
| **glanceable** | Design property of cards: readable at a glance, no cursor, no row interaction. The contract is "never exhaustive" — long lists clip with `+M more` hint pointing at the popup. | "compact", "summary" (use the noun form for the data, the adjective for the property) |
|
|
87
|
+
| **drill-down** | Design property of popups: full-screen, focused, scrollable, filterable. The exhaustive view a card promises in its `+M more` hint. | "detail view", "expansion" |
|
|
75
88
|
|
|
76
89
|
---
|
|
77
90
|
|
|
@@ -137,10 +150,10 @@ cache; `mu agent list` reconciles on every call.
|
|
|
137
150
|
| Verb | Effect |
|
|
138
151
|
| --------------------- | --------------------------------------------------------------------------- |
|
|
139
152
|
| `mu agent free alice` | Sets `alice.status = 'free'`. Agent stays alive. Means "I'm done with you for now; you're available." |
|
|
140
|
-
| `mu release feature_a`| Clears
|
|
153
|
+
| `mu task release feature_a`| Clears the task owner for `feature_a`. The agent who claimed it is unaffected. |
|
|
141
154
|
| `mu agent close alice` | Terminates alice's pane and removes from registry. Destructive. |
|
|
142
155
|
| `mu agent kick alice` | Signals (default SIGINT) the foreground process group of alice's pane TTY. For wedged tool subprocesses (`find /`, busy-wait); the wrapping CLI itself is untouched. Refuses when the foreground IS the wrapping CLI. |
|
|
143
|
-
|
|
|
156
|
+
| *(none)* | There is no detach verb. Use tmux detach to leave a workstream attached session without killing panes. |
|
|
144
157
|
|
|
145
158
|
**Don't conflate `free` and `release`.** Free is about the *agent*;
|
|
146
159
|
release is about the *task*.
|
|
@@ -152,7 +165,7 @@ release is about the *task*.
|
|
|
152
165
|
| `mu task add <id> ...` | Creates a new OPEN task |
|
|
153
166
|
| `mu task close/open/reject/defer <id>` | Lifecycle transition |
|
|
154
167
|
| `mu task claim <task> [--for <agent>]` | Atomic: sets `owner`, flips status to `IN_PROGRESS` |
|
|
155
|
-
| `mu release <task>`
|
|
168
|
+
| `mu task release <task>` | Clears `owner`. Auto-flips `IN_PROGRESS` → `OPEN` (so the task re-enters the ready set); other statuses preserved. `--reopen` forces `OPEN` from `CLOSED`/`REJECTED`/`DEFERRED` |
|
|
156
169
|
| `mu task note <task> "..."` | Appends to `task_notes`. Never edits prior notes. |
|
|
157
170
|
| `mu task notes <task> [--tail N \| --since <iso> \| --since-claim]` | List notes (oldest first). `--tail N` (alias `--last N`) prints last N; `--since <iso>` filters by `created_at`; `--since-claim` auto-resolves to the most recent `task claim` event timestamp. `--since` and `--since-claim` are mutually exclusive. |
|
|
158
171
|
|
|
@@ -348,5 +361,5 @@ documented as "workstream id" in column comments.
|
|
|
348
361
|
it duplicated the canonical-terms table at the top of this file,
|
|
349
362
|
drifted out of sync, and carried entries for rejected features
|
|
350
363
|
(capability, agent-frontmatter `persistent: false`, the JS DSL,
|
|
351
|
-
the
|
|
364
|
+
the operation-registry idea). The table is the single source.
|
|
352
365
|
For deeper background, follow the links the table rows carry. -->
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@martintrojer/mu",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
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"
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"pi-package"
|
|
27
27
|
],
|
|
28
28
|
"engines": {
|
|
29
|
-
"node": ">=20
|
|
29
|
+
"node": ">=20 <=24"
|
|
30
30
|
},
|
|
31
31
|
"main": "./dist/index.js",
|
|
32
32
|
"types": "./dist/index.d.ts",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
}
|
|
38
38
|
},
|
|
39
39
|
"bin": {
|
|
40
|
-
"mu": "
|
|
40
|
+
"mu": "dist/cli.js"
|
|
41
41
|
},
|
|
42
42
|
"files": [
|
|
43
43
|
"dist",
|
|
@@ -50,25 +50,31 @@
|
|
|
50
50
|
"build": "tsup",
|
|
51
51
|
"dev": "tsup --watch",
|
|
52
52
|
"test": "vitest run",
|
|
53
|
+
"test:fast": "vitest run --config vitest.fast.config.ts",
|
|
53
54
|
"test:watch": "vitest",
|
|
55
|
+
"test:watch:fast": "vitest --config vitest.fast.config.ts",
|
|
54
56
|
"lint": "biome check src test",
|
|
55
57
|
"lint:fix": "biome check --write src test",
|
|
56
58
|
"format": "biome format --write src test",
|
|
57
|
-
"typecheck": "tsc --noEmit",
|
|
58
|
-
"prepare": "npm run build"
|
|
59
|
+
"typecheck": "tsc --noEmit && tsc -p tsconfig.test.json --noEmit",
|
|
60
|
+
"prepare": "npm run build",
|
|
61
|
+
"test:stress": "tools/test-stress.sh"
|
|
59
62
|
},
|
|
60
63
|
"dependencies": {
|
|
61
64
|
"better-sqlite3": "^11.8.0",
|
|
62
65
|
"cli-table3": "^0.6.5",
|
|
63
66
|
"commander": "^14.0.0",
|
|
64
67
|
"execa": "^9.5.2",
|
|
68
|
+
"ink": "^5.0.0",
|
|
65
69
|
"picocolors": "^1.1.1",
|
|
66
|
-
"
|
|
70
|
+
"react": "^18.0.0",
|
|
71
|
+
"string-width": "^4.2.3"
|
|
67
72
|
},
|
|
68
73
|
"devDependencies": {
|
|
69
74
|
"@biomejs/biome": "^1.9.4",
|
|
70
75
|
"@types/better-sqlite3": "^7.6.12",
|
|
71
76
|
"@types/node": "^22.10.7",
|
|
77
|
+
"@types/react": "^18.0.0",
|
|
72
78
|
"tsup": "^8.3.5",
|
|
73
79
|
"typescript": "^5.7.3",
|
|
74
80
|
"vitest": "^2.1.8"
|