@mu-crew/mu 3.13.3 → 4.1.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 +73 -35
- package/README.md +71 -57
- package/dist/chunk-4ULY3ZKO.js +4482 -0
- package/dist/chunk-4ULY3ZKO.js.map +1 -0
- package/dist/chunk-7M634JMD.js +375 -0
- package/dist/chunk-7M634JMD.js.map +1 -0
- package/dist/{chunk-3BARPJLY.js → chunk-PZ5GOCWH.js} +16 -2
- package/dist/chunk-PZ5GOCWH.js.map +1 -0
- package/dist/chunk-SS7NQSSZ.js +3660 -0
- package/dist/chunk-SS7NQSSZ.js.map +1 -0
- package/dist/chunk-UCZPJX7K.js +14253 -0
- package/dist/chunk-UCZPJX7K.js.map +1 -0
- package/dist/{cli-CKOMHBXV.js → cli-TV3N47AZ.js} +7317 -4593
- package/dist/cli-TV3N47AZ.js.map +1 -0
- package/dist/cli.js +2 -3
- package/dist/cli.js.map +1 -1
- package/dist/errors-GRONKWFS.js +65 -0
- package/dist/extension/mu-pi.js +1632 -837
- package/dist/extension/mu-pi.js.map +1 -1
- package/dist/index.d.ts +4067 -2561
- package/dist/index.js +614 -297
- package/dist/index.js.map +1 -1
- package/dist/tui-FUBQ326D.js +1848 -0
- package/dist/tui-FUBQ326D.js.map +1 -0
- package/docs/ARCHITECTURE.md +118 -66
- package/docs/VOCABULARY.md +113 -49
- package/docs/guide/README.md +3 -3
- package/docs/guide/backends.md +8 -4
- package/docs/guide/cleanup.md +69 -33
- package/docs/guide/delegate.md +149 -66
- package/docs/guide/dispatch.md +148 -53
- package/docs/guide/getting-started.md +37 -44
- package/docs/guide/other-agent-clis.md +102 -0
- package/docs/guide/recovery.md +31 -50
- package/docs/guide/remote.md +122 -45
- package/docs/guide/sql.md +104 -24
- package/docs/guide/stop-a-worker.md +30 -37
- package/docs/guide/sync.md +11 -3
- package/docs/guide/upgrade.md +93 -43
- package/docs/reference/env.md +24 -32
- package/docs/reference/naming.md +14 -7
- package/docs/reference/surface-contract.md +153 -0
- package/package.json +33 -11
- package/prompts/mu-debug.md +3 -1
- package/prompts/mu-refute.md +2 -1
- package/prompts/mu-research.md +3 -1
- package/prompts/mu-review.md +2 -1
- package/prompts/mu-rules-audit.md +5 -4
- package/prompts/mu-sweep.md +2 -1
- package/prompts/mu-tournament.md +4 -2
- package/prompts/mu-until.md +3 -1
- package/prompts/ultrathink.md +1 -1
- package/skills/mu/SKILL.md +60 -291
- package/skills/mu/recipes/adversarial-review.md +12 -103
- package/skills/mu/recipes/brief.md +14 -61
- package/skills/mu/recipes/course-correct.md +23 -0
- package/skills/mu/recipes/findings.md +17 -111
- package/skills/mu/recipes/models.md +17 -92
- package/skills/mu/recipes/move.md +14 -0
- package/skills/mu/recipes/orchestrator-loop.md +20 -119
- package/skills/mu/recipes/recovery.md +8 -29
- package/skills/mu/recipes/remote-workers.md +17 -318
- package/skills/mu/recipes/tasks-or-calls.md +15 -78
- package/skills/mu/recipes/test-health.md +19 -77
- package/skills/mu/recipes/worker.md +17 -24
- package/dist/chunk-45KQLMEL.js +0 -2908
- package/dist/chunk-45KQLMEL.js.map +0 -1
- package/dist/chunk-G434JKV2.js +0 -641
- package/dist/chunk-G434JKV2.js.map +0 -1
- package/dist/chunk-KUGUFBWH.js +0 -12527
- package/dist/chunk-KUGUFBWH.js.map +0 -1
- package/dist/chunk-QITLNZEM.js +0 -87
- package/dist/chunk-QITLNZEM.js.map +0 -1
- package/dist/chunk-VIEYQMQA.js +0 -393
- package/dist/chunk-VIEYQMQA.js.map +0 -1
- package/dist/chunk-WI6AWCMK.js +0 -19
- package/dist/chunk-WI6AWCMK.js.map +0 -1
- package/dist/cli-CKOMHBXV.js.map +0 -1
- package/dist/devtools-LS4ZEPWM.js +0 -3772
- package/dist/devtools-LS4ZEPWM.js.map +0 -1
- package/dist/tui-LFJ5P4Y2.js +0 -40956
- package/dist/tui-LFJ5P4Y2.js.map +0 -1
- package/dist/tui-sync-worker.js +0 -17
- package/dist/tui-sync-worker.js.map +0 -1
- package/dist/yank-XDOONQSL.js +0 -13
- package/dist/yank-XDOONQSL.js.map +0 -1
- package/docs/ROADMAP.md +0 -123
- package/docs/VISION.md +0 -298
- package/docs/architecture/control-socket.md +0 -142
- package/docs/architecture/dag.md +0 -117
- package/docs/architecture/mux.md +0 -167
- package/docs/architecture/ops-log.md +0 -160
- package/docs/architecture/sdk.md +0 -187
- package/docs/architecture/sync.md +0 -159
- package/docs/architecture/tui.md +0 -165
- package/docs/guide/tui.md +0 -98
- package/docs/history/CHANGELOG-pre-3.md +0 -4866
- package/docs/img/tui-dashboard.png +0 -0
- package/docs/specs/2026-09-28-agent-state-from-murmur.md +0 -333
- package/docs/specs/2026-09-29-task-substates.md +0 -247
- package/docs/specs/2026-10-03-keep-driving-nudge.md +0 -163
- package/docs/specs/2026-10-21-fast-and-slow-gates.md +0 -229
- package/skills/mu/recipes/backlog-triage.md +0 -45
- package/skills/mu/recipes/codemode-driver.md +0 -118
- package/skills/mu/recipes/deep-research.md +0 -69
- package/skills/mu/recipes/drift-audit.md +0 -75
- package/skills/mu/recipes/fan-out.md +0 -42
- package/skills/mu/recipes/hypothesis-panel.md +0 -48
- package/skills/mu/recipes/long-run.md +0 -32
- package/skills/mu/recipes/loop-until-done.md +0 -45
- package/skills/mu/recipes/plan.md +0 -67
- package/skills/mu/recipes/refute.md +0 -55
- package/skills/mu/recipes/review-panel.md +0 -65
- package/skills/mu/recipes/rules-audit.md +0 -89
- package/skills/mu/recipes/tournament.md +0 -44
- package/skills/mu/recipes/ultrathink.md +0 -86
- package/skills/mu/recipes/watcher.md +0 -28
- package/skills/mu/recipes/waves.md +0 -21
- /package/dist/{chunk-3BARPJLY.js.map → errors-GRONKWFS.js.map} +0 -0
package/docs/architecture/dag.md
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
# The task DAG
|
|
2
|
-
|
|
3
|
-
mu coordinates work through a directed acyclic graph of tasks. Without
|
|
4
|
-
it mu is only an agent runner. Overview:
|
|
5
|
-
[ARCHITECTURE.md](../ARCHITECTURE.md). Terms:
|
|
6
|
-
[VOCABULARY.md](../VOCABULARY.md).
|
|
7
|
-
|
|
8
|
-
## Model
|
|
9
|
-
|
|
10
|
-
- **Tasks** are nodes with mandatory `impact` (1-100) and
|
|
11
|
-
`effort_days`. `ROI = impact / effort` drives prioritization.
|
|
12
|
-
- **One edge type: `blocks`.** `A → B` means A must close before B can
|
|
13
|
-
start. More edge types would make "can this start?" ambiguous.
|
|
14
|
-
- **No hidden edges.** Every edge comes from a verb the caller ran (`block`,
|
|
15
|
-
`add --blocked-by`, `reparent --blocked-by`); no write adds edges
|
|
16
|
-
on its own or wires a task to a top goal. A hidden edge makes phantom
|
|
17
|
-
blockers and false cycles, and `unblock` appears to do nothing.
|
|
18
|
-
- **Status lifecycle:** `OPEN → IN_PROGRESS → CLOSED`. Status alone
|
|
19
|
-
decides edge satisfaction: any `CLOSED` task satisfies its blockers.
|
|
20
|
-
- **Substate** qualifies status and never touches edges:
|
|
21
|
-
`OPEN/todo|parked|triage`, `IN_PROGRESS/active`,
|
|
22
|
-
`CLOSED/done|rejected|wontfix|duplicate|superseded`. The rule is
|
|
23
|
-
"store intent, derive graph facts": ready and blocked stay derived
|
|
24
|
-
from edges. `OPEN/parked` and `OPEN/triage` are excluded from `ready`
|
|
25
|
-
but stay in `goals`; `claim` refuses both without `--force`.
|
|
26
|
-
- **Notes** are append-only per task. They survive LLM context loss
|
|
27
|
-
and agent restarts, which fixes context loss at the task level
|
|
28
|
-
rather than the agent level.
|
|
29
|
-
|
|
30
|
-
## Substate integrity
|
|
31
|
-
|
|
32
|
-
The legal pairs live in the `task_substates` lookup table, seeded from
|
|
33
|
-
code on every open (machine-local, never synced). `tasks` has a
|
|
34
|
-
composite FK `(status, substate) → task_substates`,
|
|
35
|
-
`DEFERRABLE INITIALLY DEFERRED`. That FK is the only guard on the pair.
|
|
36
|
-
|
|
37
|
-
SQLite checks it at COMMIT, so apply's one-field-at-a-time UPDATEs
|
|
38
|
-
pass as long as the pair is legal when the transaction ends. That is
|
|
39
|
-
why `applyOp` wraps each task put in `db.transaction`. In autocommit
|
|
40
|
-
the first per-field UPDATE would fail the FK.
|
|
41
|
-
|
|
42
|
-
Every lifecycle write sets `status` and `substate` in one UPDATE, so
|
|
43
|
-
one op carries both under one HLC. A pair can still split across ops:
|
|
44
|
-
a status-only op from a v10 peer, an unknown substate from a newer
|
|
45
|
-
peer, or a concurrent close and park on two machines. Before commit,
|
|
46
|
-
`repairTaskPair` (`src/apply.ts`) sets the substate to the one implied
|
|
47
|
-
by the newest op in the log that writes a substate, resolved against
|
|
48
|
-
the row's current status. If that pair is illegal, it falls back to
|
|
49
|
-
the status default.
|
|
50
|
-
|
|
51
|
-
Substate is therefore not per-field LWW. Reading the log instead of
|
|
52
|
-
the row makes the result a pure function of the op set plus the row's
|
|
53
|
-
status, so every peer converges whatever the arrival order. For the
|
|
54
|
-
same reason the repair records no op: each peer computes the identical
|
|
55
|
-
repair from the identical log.
|
|
56
|
-
|
|
57
|
-
## Built-in views
|
|
58
|
-
|
|
59
|
-
| View | Returns |
|
|
60
|
-
| --- | --- |
|
|
61
|
-
| `ready` | `OPEN/todo` tasks with no unresolved blockers: work that can start now |
|
|
62
|
-
| `blocked` | OPEN tasks waiting on something |
|
|
63
|
-
| `goals` | tasks with no dependents: graph endpoints |
|
|
64
|
-
|
|
65
|
-
Agents and humans query these views directly through `mu sql`. There
|
|
66
|
-
is no separate query layer.
|
|
67
|
-
|
|
68
|
-
## Parallel tracks
|
|
69
|
-
|
|
70
|
-
The Tracks section of `mu state` and bare `mu` runs union-find
|
|
71
|
-
(`src/tracks.ts`) to find independent subtrees that different agents
|
|
72
|
-
can work in parallel.
|
|
73
|
-
|
|
74
|
-
Diamonds merge. If two roots share a prerequisite, they collapse into
|
|
75
|
-
one track, so two agents never collide on the shared dependency:
|
|
76
|
-
|
|
77
|
-
```
|
|
78
|
-
Independent (2 tracks): Diamond (1 merged track):
|
|
79
|
-
|
|
80
|
-
goal_a goal_b goal_a goal_b ← Spawn 2 agents
|
|
81
|
-
| | \ /
|
|
82
|
-
task_a task_b shared ← Spawn 1 (would
|
|
83
|
-
| | | collide otherwise)
|
|
84
|
-
leaf_a leaf_b leaf
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
The graph algorithm decides, not the LLM.
|
|
88
|
-
|
|
89
|
-
## Claim protocol
|
|
90
|
-
|
|
91
|
-
`mu task claim <task>` resolves the claimer, then atomically sets
|
|
92
|
-
`tasks.owner`, flips `status` to `IN_PROGRESS`, and records a
|
|
93
|
-
`task.claim` op carrying the resolved `actor`. The claim is a CAS in
|
|
94
|
-
SQLite, so two agents cannot claim the same task.
|
|
95
|
-
|
|
96
|
-
Identity resolution (`resolveWorkerIdentity`, `src/tasks/claim.ts`) is
|
|
97
|
-
a two-rung ladder:
|
|
98
|
-
|
|
99
|
-
1. **`$MU_AGENT_NAME`**, injected into the pane's environment at spawn.
|
|
100
|
-
It is backend-independent.
|
|
101
|
-
2. **`backend.currentAgentName()`**, the mux fallback. On tmux it reads
|
|
102
|
-
the pane title (`display-message -p '#{pane_title}'`), never the
|
|
103
|
-
window name, because one window can hold several agents. On herdr
|
|
104
|
-
the name is registered with the mux and looked up by pane id.
|
|
105
|
-
|
|
106
|
-
The fallback exists for **adopted** panes (`mu agent adopt`), which
|
|
107
|
-
predate the env injection and carry only a title. The agent does not
|
|
108
|
-
need to know its own name.
|
|
109
|
-
|
|
110
|
-
## Scoped subtree views
|
|
111
|
-
|
|
112
|
-
`mu task tree <id>` and the task queries show the part of the graph
|
|
113
|
-
reachable from a task. Scoping is a `WHERE` clause, which makes
|
|
114
|
-
recursive delegation work: a sub-orchestrator inspects its slice
|
|
115
|
-
without an LLM inferring the scope. `src/dag.ts` (`loadFullDag`,
|
|
116
|
-
`renderForest`, `renderTaskTree`) serves both `mu task tree` and the
|
|
117
|
-
TUI DAG popup.
|
package/docs/architecture/mux.md
DELETED
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
# Mux topology, backends and reconciliation
|
|
2
|
-
|
|
3
|
-
mu runs agents in multiplexer panes, tmux or herdr, behind one
|
|
4
|
-
`MuxBackend` interface. Overview: [ARCHITECTURE.md](../ARCHITECTURE.md).
|
|
5
|
-
|
|
6
|
-
## One mux session per workstream
|
|
7
|
-
|
|
8
|
-
One workstream is one mux session and one `workstream_id` partition in
|
|
9
|
-
`~/.local/state/mu/mu.db`. Workstreams on one machine are independent
|
|
10
|
-
mux sessions.
|
|
11
|
-
|
|
12
|
-
| mu term | tmux | herdr |
|
|
13
|
-
| --- | --- | --- |
|
|
14
|
-
| **mux session** | session | workspace |
|
|
15
|
-
| **window** | window | tab |
|
|
16
|
-
| **pane** | pane (`%15`) | pane (`w1:p1`) |
|
|
17
|
-
|
|
18
|
-
herdr's own "session" is server-level (one socket), which is the wrong
|
|
19
|
-
granularity, so a herdr workspace plays the mux-session role.
|
|
20
|
-
|
|
21
|
-
```
|
|
22
|
-
tmux session: mu-auth-refactor (one mu workstream)
|
|
23
|
-
┌───────────────────────────────────────────────────────────┐
|
|
24
|
-
│ Window: Backend Window: Review │
|
|
25
|
-
│ ┌──────────┐ ┌──────────┐ ┌────────────────────────┐ │
|
|
26
|
-
│ │ worker-1 │ │ worker-2 │ │ reviewer-1 │ │
|
|
27
|
-
│ │ (pi) │ │ (pi) │ │ (pi, role=read-only) │ │
|
|
28
|
-
│ └──────────┘ └──────────┘ └────────────────────────┘ │
|
|
29
|
-
│ Window: mu-orchestrator │
|
|
30
|
-
│ ┌────────────────────────────────────────────────────┐ │
|
|
31
|
-
│ │ pi (you, with mu extension loaded) │ │
|
|
32
|
-
│ └────────────────────────────────────────────────────┘ │
|
|
33
|
-
└───────────────────────────────────────────────────────────┘
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
- The first `mu agent spawn` creates the session if needed. Name it
|
|
37
|
-
with `mu workstream init <name>` or `MU_SESSION=<name>`.
|
|
38
|
-
- `tmux attach -t mu-<workstream>` shows the whole crew (herdr:
|
|
39
|
-
`herdr workspace focus <id>`). Killing the session kills the
|
|
40
|
-
workstream, with no leaked panes, and the crew survives a closed
|
|
41
|
-
laptop.
|
|
42
|
-
- `mu agent list` is scoped to one workstream; `-w` picks another.
|
|
43
|
-
- `mu doctor` warns about orphan panes, ghost rows, and agents whose
|
|
44
|
-
mux session is gone.
|
|
45
|
-
|
|
46
|
-
**Window vs pane.** Each agent gets its own window, named by `--tab`
|
|
47
|
-
(default: the agent name). Agents sharing a `--tab` value share a
|
|
48
|
-
window. Identity never depends on the window: each pane gets
|
|
49
|
-
`$MU_AGENT_NAME` plus a **pane title** fallback. The title carries
|
|
50
|
-
durable context only (agent name and owned tasks), re-pushed by
|
|
51
|
-
`composeAgentTitle` after each relevant change. mu enables the top
|
|
52
|
-
border and leaves `pane-border-format` to the user's tmux config. The
|
|
53
|
-
canonical tmux protocol is the comment block at the top of
|
|
54
|
-
`src/mux/tmux.ts`.
|
|
55
|
-
|
|
56
|
-
## The `activeMux()` seam
|
|
57
|
-
|
|
58
|
-
Every call site reaches its multiplexer through
|
|
59
|
-
`(await activeMux()).<method>()`. Nothing outside `src/mux/` names a
|
|
60
|
-
backend. `src/mux/detect.ts` picks one: `MU_MUX` → `HERDR_ENV` →
|
|
61
|
-
`$TMUX` → `PATH`. An unknown `MU_MUX` value fails the invocation
|
|
62
|
-
instead of falling back to tmux.
|
|
63
|
-
|
|
64
|
-
Each call site has decided what "no reachable multiplexer" means:
|
|
65
|
-
|
|
66
|
-
| | On `NoMultiplexerError` | Examples |
|
|
67
|
-
| --- | --- | --- |
|
|
68
|
-
| **Load-bearing** | propagate; `handle()` maps it to exit 5 | spawn, send, read, kill, adopt, kick, reconcile, session create and destroy |
|
|
69
|
-
| **Best-effort** | `try`/`catch`, degrade | actor identity, pane titles, pane borders, workstream listings, liveness polls, TUI attach |
|
|
70
|
-
|
|
71
|
-
`resolveWorkerIdentity()` in `src/tasks/claim.ts` is the canonical
|
|
72
|
-
best-effort shape. `reconcile()` is load-bearing on purpose: treating
|
|
73
|
-
an unreachable mux as "zero panes" would prune every agent as a ghost
|
|
74
|
-
and reap its in-progress tasks.
|
|
75
|
-
|
|
76
|
-
Three concerns belong to the backend, because each would otherwise
|
|
77
|
-
hardcode a tmux string a herdr user sees:
|
|
78
|
-
|
|
79
|
-
- `attachHint()` and `attachCommands()`: the printed recipe and the
|
|
80
|
-
TUI's executed argv.
|
|
81
|
-
- `healthCheck()`: version and env facts as data; `mu doctor` renders.
|
|
82
|
-
- `paneNotFoundNextSteps()`: borrowed by `PaneNotFoundError` from the
|
|
83
|
-
backend that raised it.
|
|
84
|
-
|
|
85
|
-
`src/tmux.ts` survives as a re-export for tmux-only concerns: the
|
|
86
|
-
`MU_TMUX_SOCKET` test-isolation seam and the shared `sleep` /
|
|
87
|
-
`setSleepForTests` poll seam.
|
|
88
|
-
|
|
89
|
-
## Spawn: create-and-run vs create-then-start
|
|
90
|
-
|
|
91
|
-
tmux creates a pane and runs a command in one call. herdr has no
|
|
92
|
-
create-and-run form: its creation verbs start a plain shell, and
|
|
93
|
-
`agent start` needs an existing pane at its prompt. mu expresses this
|
|
94
|
-
as a capability, not a name check:
|
|
95
|
-
|
|
96
|
-
| | tmux | herdr |
|
|
97
|
-
| --- | --- | --- |
|
|
98
|
-
| `NewWindowOptions.command` | carries the command | refused if non-empty |
|
|
99
|
-
| `startAgentInPane()` | absent | implemented |
|
|
100
|
-
| after start | mu checks pane life and startup errors | the mux returns once the agent is ready |
|
|
101
|
-
|
|
102
|
-
`spawnAgent` branches on `mux.startAgentInPane !== undefined`.
|
|
103
|
-
|
|
104
|
-
- **tmux.** After `MU_SPAWN_LIVENESS_MS` (default 1500, `0` skips) mu
|
|
105
|
-
verifies the pane survived and scans its tail for provider or auth
|
|
106
|
-
startup errors. mu does not wait for murmur to claim the pane: state
|
|
107
|
-
reads before the first claim report `unknown`. A pi agent skips the
|
|
108
|
-
fixed wait: mu checks the pane on every handshake tick (a dead pane
|
|
109
|
-
rolls back at once) and scans for startup errors when the handshake
|
|
110
|
-
ends. After an ok handshake the scan skips shell exec-failure lines
|
|
111
|
-
(`command not found`, `No such file or directory`): pi demonstrably
|
|
112
|
-
started, and a resumed session can show such lines as old tool output.
|
|
113
|
-
- **herdr.** mu creates a bare pane and calls `startAgentInPane`, which
|
|
114
|
-
subsumes both checks.
|
|
115
|
-
- **pi, either backend.** After the agent starts, mu polls its control
|
|
116
|
-
socket until the extension answers (`MU_SPAWN_CTL_MS`, default 30s;
|
|
117
|
-
`--no-ctl` or `0` skips). A timeout is reported but not rolled back:
|
|
118
|
-
the pane and pi are fine, only control is missing. See
|
|
119
|
-
[control-socket.md](control-socket.md).
|
|
120
|
-
|
|
121
|
-
A creation verb refuses a command it cannot honour. A dropped command
|
|
122
|
-
would leave an empty shell that mu believes hosts an agent. The
|
|
123
|
-
refusal and every failure before the handshake route through
|
|
124
|
-
`rollbackSpawn`, so no agent row survives for an empty pane.
|
|
125
|
-
|
|
126
|
-
Spawns serialize per mux session (`src/agents/spawn-lock.ts`, over
|
|
127
|
-
`src/file-lock.ts`) only around the topology check-then-act and the
|
|
128
|
-
row insert. Agent start and the readiness waits sit outside the lock,
|
|
129
|
-
so a parallel fan-out still runs in parallel.
|
|
130
|
-
|
|
131
|
-
## Reconciliation
|
|
132
|
-
|
|
133
|
-
`mu agent list` reconciles the registry against the mux before
|
|
134
|
-
returning. `src/reconcile.ts` is the only implementation, and
|
|
135
|
-
`mu doctor` calls the same routine.
|
|
136
|
-
|
|
137
|
-
1. **Prune ghosts.** Delete each `agents` row whose `pane_id` no longer
|
|
138
|
-
exists.
|
|
139
|
-
2. **Surface orphans.** List panes in the workstream's session that
|
|
140
|
-
have no row but whose title looks like an agent name. mu never
|
|
141
|
-
adopts them; the user runs `mu agent adopt <pane> [--name X]`.
|
|
142
|
-
|
|
143
|
-
The mux is the source of truth for which panes exist.
|
|
144
|
-
|
|
145
|
-
## Agent state
|
|
146
|
-
|
|
147
|
-
Runtime state is read on demand and never persisted.
|
|
148
|
-
`src/agent-state.ts` picks one source per agent:
|
|
149
|
-
|
|
150
|
-
| Agent | Source |
|
|
151
|
-
| --- | --- |
|
|
152
|
-
| pi (`expectsCtl`) | its control socket, probed in parallel for all pi agents |
|
|
153
|
-
| other CLI on herdr | herdr's native `paneStatus()` |
|
|
154
|
-
| other CLI on tmux | murmur: one `list-panes -a` reads `@murmur_pane_state` / `@murmur_pane_since`; agents without a local option fall back to `murmur status --json`, cached 10s |
|
|
155
|
-
|
|
156
|
-
- ctl `idle` maps to `needs_input`. murmur maps `working` → `busy`,
|
|
157
|
-
`blocked` → `needs_permission`, and `idle` / `done` / `crashed` →
|
|
158
|
-
`needs_input`. herdr supplies mu's states directly. See murmur's
|
|
159
|
-
[stable contract](https://github.com/mu-crew/murmur/blob/main/ARCHITECTURE.md#contract).
|
|
160
|
-
- An absent reading is `unknown` with a reason: `ctl missing`,
|
|
161
|
-
`ctl refused`, `murmur not installed`, `murmur pi extension not
|
|
162
|
-
linked`, `murmur has no row`, `remote snapshot stale`, `herdr reports
|
|
163
|
-
no state`, or `pane gone`.
|
|
164
|
-
- `src/mux/input-timing.ts` reads pane text only to decide when tmux
|
|
165
|
-
input can be submitted on the paste path. It does not report state.
|
|
166
|
-
- `agents.status` is deprecated. Inserts write `spawning` to satisfy the
|
|
167
|
-
schema; nothing reads it as runtime state.
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
# The ops log
|
|
2
|
-
|
|
3
|
-
mu keeps one append-only table, `ops`. History, undo, sync, rebuild and
|
|
4
|
-
drift detection are all queries or replays over it. Overview:
|
|
5
|
-
[ARCHITECTURE.md](../ARCHITECTURE.md).
|
|
6
|
-
|
|
7
|
-
## Capture is a trigger
|
|
8
|
-
|
|
9
|
-
Every INSERT, UPDATE and DELETE on a portable table (`workstreams`,
|
|
10
|
-
`tasks`, `task_edges`, `task_notes`) is recorded as an **op** by a
|
|
11
|
-
SQLite trigger, in the same transaction as the mutation. No SDK
|
|
12
|
-
function decides to record history, so no call site can forget to, and
|
|
13
|
-
the log cannot drift from the data, even on power loss.
|
|
14
|
-
|
|
15
|
-
The tables are the materialized view; the log is the record. Reads hit
|
|
16
|
-
the tables as ordinary indexed SQL. Nothing replays the log to answer
|
|
17
|
-
a query. The log is what the tables are derived from and can be
|
|
18
|
-
rebuilt from.
|
|
19
|
-
|
|
20
|
-
```
|
|
21
|
-
mu task close t3
|
|
22
|
-
│
|
|
23
|
-
▼
|
|
24
|
-
UPDATE tasks SET status=... ──┬──► tables (what reads see)
|
|
25
|
-
└──► ops (what everything else reads)
|
|
26
|
-
one transaction
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
| Feature | Over the log |
|
|
30
|
-
| --- | --- |
|
|
31
|
-
| history | `mu log` is a typed reader over `ops` (`src/logs.ts`) |
|
|
32
|
-
| undo | `mu undo <group>` emits the inverse ops for one group |
|
|
33
|
-
| sync | each machine appends its own ops to a JSONL segment and applies each peer's from a watermark ([sync.md](sync.md)) |
|
|
34
|
-
| rebuild | `mu rebuild <file>` replays the whole log into a new DB |
|
|
35
|
-
| drift | `mu doctor --deep` rebuilds into a temp DB and diffs it against the live tables |
|
|
36
|
-
|
|
37
|
-
**Ops are semantic partial updates.** An UPDATE op carries only the
|
|
38
|
-
columns that changed. That makes per-field merge free: two machines
|
|
39
|
-
editing different fields of one task both keep their edit. A full-row
|
|
40
|
-
payload would silently regress this to row-level last-writer-wins.
|
|
41
|
-
|
|
42
|
-
**The cost:** one capture bug breaks history, undo and sync at once,
|
|
43
|
-
silently. Hence the two drift tiers below.
|
|
44
|
-
|
|
45
|
-
**Ordering** across machines comes from the **HLC** (`src/hlc.ts`) on
|
|
46
|
-
every op: a hybrid logical clock serialized as sortable TEXT
|
|
47
|
-
`<wall_ms:15>.<counter:6>.<machine_id>`, so bytewise `ORDER BY hlc` is
|
|
48
|
-
causal order. Clock state lives in `machine_identity`.
|
|
49
|
-
|
|
50
|
-
**Machine-local tables** (`agents`, `vcs_workspaces`) have no capture
|
|
51
|
-
triggers. Their contents are pane ids and absolute paths, which mean
|
|
52
|
-
nothing on another machine. Their changes are recorded as log-only
|
|
53
|
-
events through `emitEvent`. `appendLog` writes operator prose
|
|
54
|
-
(`mu log "text"`).
|
|
55
|
-
|
|
56
|
-
## Trigger mechanics
|
|
57
|
-
|
|
58
|
-
- **Capture triggers are TEMP triggers**, reinstalled per connection
|
|
59
|
-
by `src/capture.ts`. SQLite refuses a main-schema trigger that
|
|
60
|
-
references the temp `_op_ctx` table. Two consequences follow:
|
|
61
|
-
- DELETE keys are captured inline, with the parent stashing its
|
|
62
|
-
natural key in `_op_dying` first. FK CASCADE fires child triggers
|
|
63
|
-
after the parent is gone.
|
|
64
|
-
- The HLC is minted in SQL, because a trigger cannot call into JS.
|
|
65
|
-
- **Op context** (`src/op-context.ts`): `withOpContext(db, {intent,
|
|
66
|
-
actor, group}, fn)` labels every op in a scope and restores in a
|
|
67
|
-
`finally`. Nested scopes inherit the group, which puts a cascade
|
|
68
|
-
under one `mu undo`. `withCaptureSuppressed` is the echo guard used
|
|
69
|
-
by the apply path.
|
|
70
|
-
- An importer must synthesize **ops, not rows**. A direct INSERT is
|
|
71
|
-
invisible to sync and reported as drift.
|
|
72
|
-
|
|
73
|
-
## Apply and merge
|
|
74
|
-
|
|
75
|
-
`src/apply.ts` is capture's counterpart: given one op, local or from a
|
|
76
|
-
peer, it makes the tables reflect it.
|
|
77
|
-
|
|
78
|
-
- **Merge is per entity.** Notes are grow-only sets. Tasks and
|
|
79
|
-
workstreams merge per field by HLC. Edges are an LWW-element-set.
|
|
80
|
-
- A **tombstone** is an ordinary op with an HLC, so out-of-order
|
|
81
|
-
arrival is a comparison and resurrection falls out.
|
|
82
|
-
- Never use `json_patch`: RFC 7396 reads a null member as
|
|
83
|
-
delete-the-key.
|
|
84
|
-
- Task `status` and `substate` are repaired as a pair, not merged per
|
|
85
|
-
field. See [dag.md](dag.md#substate-integrity).
|
|
86
|
-
|
|
87
|
-
## Undo
|
|
88
|
-
|
|
89
|
-
`src/undo.ts` derives the inverse ops for one `group_id` from log
|
|
90
|
-
provenance. It refuses a superseded group (exit 4; `--force`
|
|
91
|
-
overrides). Bare `mu undo` lists undoable groups (`-n` widens), a group
|
|
92
|
-
prefix previews, and `--yes` applies.
|
|
93
|
-
|
|
94
|
-
- **Undo and restore write through the tables**, so capture records
|
|
95
|
-
them like any other write.
|
|
96
|
-
- `restore` records and applies under the **same** HLC. `applyOp`
|
|
97
|
-
excludes an op's own HLC from provenance. A fresher HLC would make
|
|
98
|
-
the row outrank the op and lose every field to an insert default.
|
|
99
|
-
- **Note tombstones are self-describing only when they must be.** Every
|
|
100
|
-
other `del` carries `'{}'`, because its key plus earlier puts describe
|
|
101
|
-
the row. A note's key embeds its rowid (`<ws>/<task>#<id>`), and a
|
|
102
|
-
rebuild or reprojection reassigns rowids, so the puts can sit under an
|
|
103
|
-
old key (drift-641). The `task_notes` delete trigger records `OLD.*`
|
|
104
|
-
only when no put under the current key is in the log, and `'{}'`
|
|
105
|
-
otherwise; `planUndo` folds the puts first and falls back to the
|
|
106
|
-
tombstone payload, and `applyOp` deletes by the payload's content
|
|
107
|
-
identity, falling back to the put under the same key. For the same reason `src/drift.ts` matches notes on
|
|
108
|
-
the task-key prefix.
|
|
109
|
-
|
|
110
|
-
## Rewriting history (local only)
|
|
111
|
-
|
|
112
|
-
The log is append-only except for two operator verbs in `src/compact.ts`,
|
|
113
|
-
both dry-run by default, both backing up beside the DB and running the
|
|
114
|
-
drift check after:
|
|
115
|
-
|
|
116
|
-
- **`mu db compact`** blanks note tombstones a put under the same key
|
|
117
|
-
already explains: the slim form capture writes now, applied to older
|
|
118
|
-
rows. Undo and sync unaffected.
|
|
119
|
-
- **`mu db forget <workstream...>`** deletes every op of named
|
|
120
|
-
torn-down workstreams (keys `<ws>` and `<ws>/...`). Their rows are
|
|
121
|
-
already gone from the tables, so rebuild and drift stay consistent;
|
|
122
|
-
`mu undo` of those teardowns is gone on purpose.
|
|
123
|
-
|
|
124
|
-
Neither touches sync segments. A peer is not affected: watermarks only
|
|
125
|
-
read forward, so a peer never re-sends forgotten history.
|
|
126
|
-
|
|
127
|
-
## Rebuild
|
|
128
|
-
|
|
129
|
-
`src/rebuild.ts` (`rebuildInto`) replays the whole log into a new DB
|
|
130
|
-
file through `applyOp`. The verb prints counts, supports `--json`,
|
|
131
|
-
suggests an `mv` swap, and warns that `agents` and `vcs_workspaces`
|
|
132
|
-
cannot be reconstructed.
|
|
133
|
-
|
|
134
|
-
Rebuild is not ingest:
|
|
135
|
-
|
|
136
|
-
- Ingest filters to `SYNCED_ENTITIES`. Rebuild replays everything, so
|
|
137
|
-
log-only entities are copied verbatim, or `mu log` comes back empty.
|
|
138
|
-
- `src/legacy-ops.ts` classifies historical log-only intents whose
|
|
139
|
-
entity looks projectable. Rebuild copies them without projection;
|
|
140
|
-
flush never emits them.
|
|
141
|
-
- `machine_identity` carries across whole: id, hostname and HLC clock.
|
|
142
|
-
Otherwise the rebuilt DB is a different peer minting HLCs below
|
|
143
|
-
every replayed op.
|
|
144
|
-
|
|
145
|
-
## Drift
|
|
146
|
-
|
|
147
|
-
`src/drift.ts` has two tiers:
|
|
148
|
-
|
|
149
|
-
| Tier | Runs | Checks |
|
|
150
|
-
| --- | --- | --- |
|
|
151
|
-
| cheap (`checkCheapDriftInvariant`) | default `mu doctor` | every live row has at least one op naming its key |
|
|
152
|
-
| deep (`checkDrift`) | `mu doctor --deep` | rebuilds into a temp DB and diffs field by field |
|
|
153
|
-
|
|
154
|
-
The deep diff matches rows by natural key and excludes `owner_id`,
|
|
155
|
-
because a rebuild always has NULL owners. `driftRemediation` prints the
|
|
156
|
-
fix.
|
|
157
|
-
|
|
158
|
-
For disaster recovery, `mu db backup <file>` writes one scp-able file
|
|
159
|
-
through `VACUUM INTO`. It never overwrites; it is the copy
|
|
160
|
-
`SchemaTooOldError` tells you to take.
|
package/docs/architecture/sdk.md
DELETED
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
# CLI and SDK surface
|
|
2
|
-
|
|
3
|
-
Every user-visible operation is a typed SDK function plus a thin
|
|
4
|
-
Commander wrapper. This page covers the boundary rules, errors, tests
|
|
5
|
-
and packaging. Overview: [ARCHITECTURE.md](../ARCHITECTURE.md).
|
|
6
|
-
|
|
7
|
-
## One surface, two callers
|
|
8
|
-
|
|
9
|
-
`src/cli.ts` (`buildProgram`) and the namespace files under `src/cli/`
|
|
10
|
-
are the verb surface: no generated registry, no DSL, no separate
|
|
11
|
-
operation schema. Programmatic callers import the same functions from
|
|
12
|
-
`src/index.ts`. Agents and scripts compose verbs with `--json`.
|
|
13
|
-
|
|
14
|
-
A verb's path:
|
|
15
|
-
|
|
16
|
-
1. The Commander block calls a `cmd<Verb>` wrapped in `handle()`.
|
|
17
|
-
2. `handle()` opens the DB, runs ambient sync ingest
|
|
18
|
-
([sync.md](sync.md#the-ambient-hook)), and calls the SDK function.
|
|
19
|
-
3. The SDK function opens `db.transaction(fn)()` for multi-statement
|
|
20
|
-
writes. Agent ops call the mux (and jj, sl or git for workspaces);
|
|
21
|
-
task ops are pure SQL. Read paths reconcile against the mux.
|
|
22
|
-
4. Capture triggers record ops in the same transaction
|
|
23
|
-
([ops-log.md](ops-log.md)). Machine-local changes go through
|
|
24
|
-
`emitEvent`. `mu log --tail` sees them on its next 1-second poll.
|
|
25
|
-
5. On error the transaction rolls back and the typed error reaches
|
|
26
|
-
`handle()`, which maps it to an exit code. Flush runs on both paths.
|
|
27
|
-
|
|
28
|
-
Output is self-documenting: `src/output.ts` (`printNextSteps`,
|
|
29
|
-
`errorNextSteps`) prints `Next:` hints, quoted by `src/shell-quote.ts`
|
|
30
|
-
so they paste cleanly.
|
|
31
|
-
|
|
32
|
-
## Surrogate-PK and SDK-boundary discipline
|
|
33
|
-
|
|
34
|
-
Every entity table has this shape:
|
|
35
|
-
|
|
36
|
-
```
|
|
37
|
-
(
|
|
38
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT, -- surrogate; internal
|
|
39
|
-
<scope_id> INTEGER NOT NULL REFERENCES <parent>(id) ON DELETE CASCADE,
|
|
40
|
-
<name> TEXT NOT NULL, -- operator-facing; mutable
|
|
41
|
-
-- ... domain attributes
|
|
42
|
-
UNIQUE (<scope_id>, <name>) -- per-scope unique
|
|
43
|
-
)
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
FKs reference `<parent>_id` (INTEGER), never the TEXT name. The name is
|
|
47
|
-
an attribute: searchable, displayable, cheap to rename. The surrogate
|
|
48
|
-
id is the identity.
|
|
49
|
-
|
|
50
|
-
**TEXT by design:**
|
|
51
|
-
|
|
52
|
-
- the workstream's own `name` (it is the mux session name; globally
|
|
53
|
-
unique);
|
|
54
|
-
- `task_notes.author` and `ops.actor` (free-text labels such as
|
|
55
|
-
`"orchestrator"`);
|
|
56
|
-
- `ops.entity` (open enum; a new entity needs no migration);
|
|
57
|
-
- `agents.cli` (a new CLI needs no schema change);
|
|
58
|
-
- every column of `ops`. The log is FK-free and addresses rows by
|
|
59
|
-
natural key, so an op outlives its row and its workstream.
|
|
60
|
-
|
|
61
|
-
**The boundary rule** matches REST: the external API uses business
|
|
62
|
-
identifiers, the internal layer uses primary keys.
|
|
63
|
-
|
|
64
|
-
> **Public SDK functions take operator-facing names.**
|
|
65
|
-
> **Internal helpers take surrogate ids.**
|
|
66
|
-
> **Resolution happens at the public-function entry, exactly once.**
|
|
67
|
-
|
|
68
|
-
```ts
|
|
69
|
-
// PUBLIC: takes operator-facing names
|
|
70
|
-
export function claimTask(
|
|
71
|
-
db: Db,
|
|
72
|
-
workstream: string,
|
|
73
|
-
localId: string,
|
|
74
|
-
opts?: ClaimOptions,
|
|
75
|
-
): ClaimResult {
|
|
76
|
-
const wsId = resolveWorkstreamId(db, workstream);
|
|
77
|
-
const taskId = tryResolveTaskId(db, wsId, localId);
|
|
78
|
-
if (taskId === null) throw new TaskNotFoundError(localId);
|
|
79
|
-
const agentId = resolveCurrentAgentId(db, wsId);
|
|
80
|
-
return claimTaskById(db, taskId, agentId, opts);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// INTERNAL: takes surrogate ids; never re-resolves
|
|
84
|
-
function claimTaskById(db, taskId, agentId, opts): ClaimResult { ... }
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
Resolving once gives three things: no double resolution; no
|
|
88
|
-
workstream context needed below the boundary, because the FKs make
|
|
89
|
-
scope implicit; and one place for error mapping.
|
|
90
|
-
`WorkstreamNotFoundError` comes from `resolveWorkstreamId` in
|
|
91
|
-
`src/db.ts`. `TaskNotFoundError` and `AgentNotFoundError` are raised by
|
|
92
|
-
callers wrapping a `tryResolve*` null, so exit 3 holds whichever lookup
|
|
93
|
-
missed.
|
|
94
|
-
|
|
95
|
-
**Workstream scoping is mandatory at the CLI boundary.** TEXT names
|
|
96
|
-
(`tasks.local_id`, `agents.name`) are unique per workstream, so the
|
|
97
|
-
same name can exist in two. Every public function taking such a name
|
|
98
|
-
also takes the workstream; internal SQL filters by
|
|
99
|
-
`(workstream_id, name)`.
|
|
100
|
-
|
|
101
|
-
**`--json` keeps operator-facing names.** Surrogate ids never appear in
|
|
102
|
-
`--json`, error payloads, log lines or exports. Exposing them would
|
|
103
|
-
bring back a global namespace.
|
|
104
|
-
|
|
105
|
-
## State of truth
|
|
106
|
-
|
|
107
|
-
- The DB (`~/.local/state/mu/mu.db`, or `MU_DB_PATH`) is canonical.
|
|
108
|
-
Everything else is a cache, including pane titles.
|
|
109
|
-
- Writes go through the typed SDK functions, which validate, transact
|
|
110
|
-
and reconcile. Op capture is the triggers' job.
|
|
111
|
-
- In-memory state lives only for one command's connection.
|
|
112
|
-
- Multiple `mu` processes share the file safely through SQLite WAL.
|
|
113
|
-
|
|
114
|
-
## Errors
|
|
115
|
-
|
|
116
|
-
Errors are typed classes per layer (`src/agents/errors.ts`,
|
|
117
|
-
`src/tasks/errors.ts`, `MuxError` with `TmuxError` / `HerdrError`, and
|
|
118
|
-
so on). Nothing swallows them. `src/cli/handle.ts` maps them to exit
|
|
119
|
-
codes:
|
|
120
|
-
|
|
121
|
-
| Code | Meaning |
|
|
122
|
-
| --- | --- |
|
|
123
|
-
| 0 | success |
|
|
124
|
-
| 1 | generic error |
|
|
125
|
-
| 2 | usage error (Commander's default) |
|
|
126
|
-
| 3 | not found (agent, task, workspace) |
|
|
127
|
-
| 4 | conflict (name collision, double claim, dirty tree, schema too old or new, outdated extension) |
|
|
128
|
-
| 5 | substrate unavailable or unsafe (no multiplexer, mux or VCS failure, DB locked, ops-log drift, a `mu doctor` FAIL row, timeout) |
|
|
129
|
-
| 6 | `mu task wait`: the reaper reopened a watched task (its pane died) |
|
|
130
|
-
| 7 | `mu task wait`: a watched task stalled (`--on-stall exit`) |
|
|
131
|
-
|
|
132
|
-
Errors carry structured context (operation, target, attempted action)
|
|
133
|
-
so `mu doctor` can show them readably.
|
|
134
|
-
|
|
135
|
-
## Testing layers
|
|
136
|
-
|
|
137
|
-
Fast tier (`npm run test:fast`) excludes `*.integration.test.ts` and
|
|
138
|
-
`*.smoke.test.ts`. It uses real SQLite in per-test temp DBs and a
|
|
139
|
-
mocked mux (`installMux()` from `test/_mux.ts`). Integration tests use
|
|
140
|
-
real tmux or herdr, and real git, jj or sl in `os.tmpdir()`; jj and sl
|
|
141
|
-
tests skip when the binary is missing. Conventions live in
|
|
142
|
-
[AGENTS.md](../../AGENTS.md) and `test/README.md`.
|
|
143
|
-
|
|
144
|
-
| Area | What the tests pin |
|
|
145
|
-
| --- | --- |
|
|
146
|
-
| `src/capture.ts`, `src/op-context.ts` | payloads hold only changed columns (asserted by key count); echo suppression; cascade grouping; FK-CASCADE tombstone keys; the null-intent fail-safe |
|
|
147
|
-
| `src/apply.ts` | injected HLCs, no sleeps: field convergence in both orders; same-field LWW determinism; all four put and del orderings; resurrection; grow-only notes; the set-to-NULL trap; `owner_id` stripping; replay idempotence |
|
|
148
|
-
| `src/rebuild.ts` | round trip row by row per portable table (a count-only check passes on a garbage rebuild); tombstones; byte-identical ops; `machine_id` and clock carried |
|
|
149
|
-
| `src/drift.ts`, `src/fleet-hazards.ts` | drift planted the way a capture bug would (`withCaptureSuppressed` plus a direct write), with equal weight on false positives |
|
|
150
|
-
| `src/dormant.ts` | `updated_at` written directly; weighted to the boundary between `finished` and `abandoned` |
|
|
151
|
-
| `src/disk-recon.ts` | a per-test `MU_STATE_DIR` with the on-disk shape built by hand, both directions |
|
|
152
|
-
| `src/segments.ts`, `src/sync.ts` | two temp DBs and one shared dir; each robustness layer; machine-local ops never reaching a file |
|
|
153
|
-
| `src/hlc.ts` | an injected clock: backward jumps, stalled milliseconds, concurrent minting |
|
|
154
|
-
| `sync-session.integration` | 8 rounds of two machines: byte-identical portable content, no `--deep` drift, a quiet round adds zero ops |
|
|
155
|
-
| `test/acceptance.integration.test.ts` | the end-to-end gate: 10 tasks, 3 agents |
|
|
156
|
-
|
|
157
|
-
`npm run test:stress` repeats the suite with per-run timeouts,
|
|
158
|
-
optionally in parallel (`MU_TEST_STRESS_MODE=parallel`).
|
|
159
|
-
|
|
160
|
-
Two fixture traps:
|
|
161
|
-
|
|
162
|
-
- A convergence fixture must share one creation op. Two independent
|
|
163
|
-
`task add` calls for the same id make the later creation win every
|
|
164
|
-
field.
|
|
165
|
-
- An ordering-sensitive sync test must rename segments to pin ingest
|
|
166
|
-
order, since peers are discovered by `localeCompare` over random-UUID
|
|
167
|
-
filenames.
|
|
168
|
-
|
|
169
|
-
Treat a test that passes alone and fails under load as a concurrency
|
|
170
|
-
bug first. Drive wait and reaper tests from poll-loop seams, not fixed
|
|
171
|
-
timers, and wait for stable Ink output instead of sleeping.
|
|
172
|
-
|
|
173
|
-
## Distribution
|
|
174
|
-
|
|
175
|
-
One npm package, `mu`. `tsup` bundles three entries:
|
|
176
|
-
|
|
177
|
-
| Output | Role |
|
|
178
|
-
| --- | --- |
|
|
179
|
-
| `dist/cli.js` | `bin: { mu: ./dist/cli.js }` |
|
|
180
|
-
| `dist/index.js`, `dist/index.d.ts` | SDK and types |
|
|
181
|
-
| `dist/extension/mu-pi.js` | the pi extension `mu link pi` points at |
|
|
182
|
-
|
|
183
|
-
`skills/` and `docs/` ship alongside. There is no build step on the user's
|
|
184
|
-
machine. pi stays a peer dependency: the extension types pi's API
|
|
185
|
-
structurally. Per-role agent guidance lives in the user's repo, not in
|
|
186
|
-
mu. The rule for adding a dependency is in
|
|
187
|
-
[ROADMAP § Anti-feature pledges](../ROADMAP.md#anti-feature-pledges).
|